NeHe OpenGL教程 第三十七课:卡通映射

转自【翻译】NeHe OpenGL 教程

前言

声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢。

 

NeHe OpenGL第三十七课:卡通映射

卡通映射:

什么是卡通了,一个轮廓加上少量的几种颜色。使用一维纹理映射,你也可以实现这种效果。
 
看到人们仍然e-mail我请求在文章中使用我方才在GameDev.net上写的源代码,还看到文章的第二版(在那每一个API附带源码)不是在中途完成之前连贯的结束。我已经把这篇指南一并出租给了NeHe(这实际上是

写文章的最初意图)因此你们所有的OpenGL领袖可以玩转它。对模型的选择表示抱歉,但是我最近一直在玩Quake 2。
注释:这篇文章的源代码可以在这里找到:
http://www.gamedev.net/reference/programming/features/celshading.
这篇指南实际上并不解释原理,仅仅解释代码。在上面的连接中可以发现为什么它能工作。现在不断地大声抱怨STOP E-MAILING ME REQUESTS FOR SOURCE CODE!!!!
首先,我们需要包含一些额外的头文件。第一个(math.h)我们可以使用sqrtf (square root)函数,第二个用来访问文件。

#include <math.h>      
#include <stdio.h>      

现在我们将定义一些结构体来帮助我们存贮我们的数据(保存好几百浮点数组)。第一个是tagMATRIX结构体。如果你仔细地看,你将看到我们正象包含一个十六个浮点数的1维数组~一个2维4×4数族一样存储那个矩阵

。这下至OpenGL存储它的矩阵的方式。如果我们使用4x4数组,这些值将发生错误的顺序。
 
typedef struct tagMATRIX     // 保存OpenGL矩阵的结构体
{
 float Data[16];     // 由于OpenGL的矩阵的格式我们使用[16
}
MATRIX;

第二是向量的类。 仅存储X,Y和Z的值 
  
typedef struct tagVECTOR     // 存储一个单精度向量的结构体
{
 float X, Y, Z;     // 向量的分量
}
VECTOR;

第三,我们持有顶点的结构。每一个顶点仅需要它的法线和位置(没有纹理的现行纵坐标)信息。它们必须以这样的次序被存放,否则当它停止装载文件的事件将发生严重的错误(我发现艰难的情形:(教我分块出租我

的代码。)。
 
typedef struct tagVERTEX     // 存放单一顶点的结构
{
 VECTOR Nor;     // 顶点法线
 VECTOR Pos;     // 顶点位置
}
VERTEX;

最后是多边形的结构。我知道这是存储顶点的愚蠢的方法,要不是它完美工作的简单的缘故。通常我愿意使用一个顶点数组,一个多边形数组,和包括一个在多边形中的3个顶点的指数,但这比较容易显示你想干什么。 
  
typedef struct tagPOLYGON     // 存储单一多边形的结构
{
 VERTEX Verts[3];     // 3个顶点结构数组
}
POLYGON;

优美简单的材料也在这里了。为每一个变量的一个解释考虑那个注释。 
  
bool  outlineDraw = true;    // 绘制轮廓的标记
bool  outlineSmooth = false;    // Anti-Alias 线段的标记
float  outlineColor[3] = { 0.0f, 0.0f, 0.0f };  // 线段的颜色
float  outlineWidth = 3.0f;    // 线段的宽度

VECTOR  lightAngle;     // 灯光的方向
bool  lightRotate = false;    // 是否我们旋转灯光的标记

float  modelAngle = 0.0f;    // 模型的Y轴角度
bool     modelRotate = false;     // 旋转模型的标记

POLYGON  *polyData = NULL;     // 多边形数据
int  polyNum  = 0;    // 多边形的编号

GLuint  shaderTexture[1];     // 存储纹理ID

这是得到的再简单不过的模型文件格式。 最初的少量字节存储在场景中的多边形的编号,文件的其余是tagPOLYGON结构体的一个数组。正因如此,数据在没有任何需要去分类到详细的顺序的情况下被读出。 
  
BOOL ReadMesh ()      // 读“model.txt” 文件
{
 FILE *In = fopen ("Data\\model.txt", "rb");  // 打开文件

 if (!In)
  return FALSE;    // 如果文件没有打开返回 FALSE

 fread (&polyNum, sizeof (int), 1, In);  // 读文件头,多边形的个数

 polyData = new POLYGON [polyNum];   // 分配内存

 fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In);// 把所有多边形的数据读入

 fclose (In);     // 关闭文件

 return TRUE;     // 工作完成
}

一些基本的数学函数而已。DotProduct计算2个向量或平面之间的角,Magnitude函数计算向量的长度,Normalize函数缩放向量到一个单位长度。
 
inline float DotProduct (VECTOR &V1, VECTOR &V2)  //计算两个向量之间的角度
{
 return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z;  
}

inline float Magnitude (VECTOR &V)    // 计算向量的长度
{
 return sqrtf (V.X * V.X + V.Y * V.Y + V.Z * V.Z); 
}

void Normalize (VECTOR &V)     // 创建一个单位长度的向量
{
 float M = Magnitude (V);    

 if (M != 0.0f)     // 确保我们没有被0隔开
 {
  V.X /= M;     
  V.Y /= M;
  V.Z /= M;
 }
}

这个函数利用给定的矩阵旋转一个向量。请注意它仅旋转这个向量——与向量的位置相比它算不了什么。它用来当旋转法线确保当我们在计算灯光时它们停留在正确的方向上。 
  
void RotateVector (MATRIX &M, VECTOR &V, VECTOR &D)  // 利用提供的矩阵旋转一个向量
{
 D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8]  * V.Z); 
 D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9]  * V.Z); 
 D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z); 
}

引擎的第一个主要的函数…… 初始化,按所说的精确地做。我已经砍掉了在注释中不再需要的代码段。 
  
// 一些GL 初始代码和用户初始化从这里开始
BOOL Initialize (GL_Window* window, Keys* keys)
{

这3个变量用来装载着色文件。在文本文件中为了单一的线段线段包含了空间,虽然shaderData存储了真实的着色值。你可能奇怪为什么我们的96个值被32个代替了。好了,我们需要转换greyscale 值为RGB以便

OpenGL能使用它们。我们仍然可以以greyscale存储这些值,但向上负载纹理时我们至于R,G和B成分仅仅使用同一值。 
  
 char Line[255];      // 255个字符的存储量
 float shaderData[32][3];     // 96个着色值的存储量
 g_window = window; g_keys  = keys;
 FILE *In = NULL;      // 文件指针

当绘制线条时,我们想要确保很平滑。初值被关闭,但是按“2”键,它可以被toggled on/off。 
  
 glShadeModel (GL_SMOOTH);    // 使用色彩阴影平滑
 glDisable (GL_LINE_SMOOTH);    // 线条平滑初始化不可用

 glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 提高计算精度  
 glClearColor (0.7f, 0.7f, 0.7f, 0.0f);  // 设置为灰色背景 
 glClearDepth (1.0f);    // 设置深度缓存值
   glEnable (GL_DEPTH_TEST);  // 启用深度测试
   glDepthFunc (GL_LESS);  // 设置深度比较函数
 glShadeModel (GL_SMOOTH);  // 启用反走样
   glDisable (GL_LINE_SMOOTH);

glEnable (GL_CULL_FACE);    // 启用剔除多边形功能

我们使 OpenGL灯光不可用因为我们自己做所以的灯光计算。 

 glDisable (GL_LIGHTING);    // 使 OpenGL 灯光不可用

这里是我们装载阴影文件的地方。它简单地以32个浮点值ASCII码存放(为了轻松修改),每一个在separate线上。 

 In = fopen ("Data\\shader.txt", "r");   // 打开阴影文件

 if (In)       // 检查文件是否打开
 {
  for (i = 0; i < 32; i++)    // 循环32次
  {
   if (feof (In))    // 检查文件是否结束
    break;

   fgets (Line, 255, In);   // 获得当前线条

这里我们转换 greyscale 值为 RGB, 正象上面所描述的。 

   // 从头到尾复制这个值
   shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = atof (Line);
  }

  fclose (In);     // 关闭文件
 }

 else
  return FALSE;     
  
现在我们向上装载这个纹理。同样它清楚地规定,不要使用任何一种过滤在纹理上否则它看起来奇怪,至少可以这样说。GL_TEXTURE_1D被使用因为它是值的一维数组。 

 glGenTextures (1, &shaderTexture[0]);   // 获得一个自由的纹理ID

 glBindTexture (GL_TEXTURE_1D, shaderTexture[0]);  // 绑定这个纹理。 从现在开始它变为一维

 // 使用邻近点过滤
 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
 glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

 // 设置纹理
 glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData);

现在调整灯光方向。我已经使得它向下指向Z轴正方向,这意味着它将正面碰撞模型 

 lightAngle.X = 0.0f;     
 lightAngle.Y = 0.0f;     
 lightAngle.Z = 1.0f;    

 Normalize (lightAngle);

读取Mesh文件,并返回 
 return ReadMesh ();     // 读取Mesh文件,并返回
}

与上面的函数相对应…… 卸载,删除由Initalize 和 ReadMesh 创建的纹理和多边形数据。 
  
void Deinitialize (void)
{
 glDeleteTextures (1, &shaderTexture[0]);  // 删除阴影纹理

 delete [] polyData;    // 删除多边形数据
}

主要的演示循环。所有这些用来处理输入和更新角度。控制如下:
<SPACE> =锁定旋转

1 = 锁定轮廓绘制
2 = 锁定轮廓 anti-aliasing

<UP> =增加线宽
<DOWN> = 减小线宽
 
void Update (DWORD milliseconds)    // 这里执行动作更新
{
 if (g_keys->keyDown [' '] == TRUE)   // 空格是否被按下
 {
  modelRotate = !modelRotate;   // 锁定模型旋转开/关

  g_keys->keyDown [' '] = FALSE;
 }

 if (g_keys->keyDown ['1'] == TRUE)   // 1是否被按下
 {
  outlineDraw = !outlineDraw;   // 切换是否绘制轮廓线

  g_keys->keyDown ['1'] = FALSE;
 }

 if (g_keys->keyDown ['2'] == TRUE)   // 2是否被按下
 {
  outlineSmooth = !outlineSmooth;  // 切换是否使用反走样

  g_keys->keyDown ['2'] = FALSE;
 }

 if (g_keys->keyDown [VK_UP] == TRUE)   // 上键增加线的宽度
 {
  outlineWidth++;     

  g_keys->keyDown [VK_UP] = FALSE;
 }

 if (g_keys->keyDown [VK_DOWN] == TRUE)  // 下减少线的宽度
 {
  outlineWidth--;     

  g_keys->keyDown [VK_DOWN] = FALSE;
 }

 if (modelRotate)     // 是否旋转
  modelAngle += (float) (milliseconds) / 10.0f; // 更新旋转角度
}

你一直在等待的函数。Draw 函数做每一件事情——计算阴影的值,着色网孔,着色轮廓,等等,这是它作的。 
  
void Draw (void)
{

TmpShade用来存储当前顶点的色度值。所有顶点数据同时被计算,意味着我们只需使用我们能继续使用的单个的变量。
TmpMatrix, TmpVector 和 TmpNormal同样被用来计算顶点数据,TmpMatrix在函数开始时被调整一次并一直保持到Draw函数被再次调用。TmpVector 和 TmpNormal则相反,当另一个顶点被处理时改变。

 float TmpShade;      // 临时色度值

 MATRIX TmpMatrix;      // 临时 MATRIX 结构体
 VECTOR TmpVector, TmpNormal;    // 临时 VECTOR结构体

清除缓冲区矩阵数据 

 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //  清除缓冲区
 glLoadIdentity ();     // 重置矩阵

首先检查我们是否想拥有平滑的轮廓。如果是,我们就打开anti-alaising 。否则把它关闭。简单! 

 if (outlineSmooth)     // 检查我们是否想要 Anti-Aliased 线条
 {
  glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // 启用它们
  glEnable (GL_LINE_SMOOTH);   
 }

 else      // 否则不启用
  glDisable (GL_LINE_SMOOTH);  
  
然后我们设置视口。我们反向移动摄象机2个单元,之后以一定角度旋转模型。注:由于我们首先移动摄象机,这个模型将在现场旋转。如果我们以另一种方法做,模型将绕摄象机旋转。
我们之后从OpenGL中取最新创建的矩阵并把它存储在 TmpMatrix。

 glTranslatef (0.0f, 0.0f, -2.0f);   // 移入屏幕两个单位
 glRotatef (modelAngle, 0.0f, 1.0f, 0.0f);  // 绕Y轴旋转这个模型

 glGetFloatv (GL_MODELVIEW_MATRIX, TmpMatrix.Data); // 获得产生的矩阵

戏法开始了。首先我们启用一维纹理,然后启用着色纹理。这被OpenGL用来当作一个look-up表格。我们之后调整模型的颜色(白色)我选择白色是因为它亮度高并且描影法比其它颜色好。我建议你不要使用黑色:)

 // 卡通渲染代码
 glEnable (GL_TEXTURE_1D);    // 启用一维纹理
 glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // 锁定我们的纹理

 glColor3f (1.0f, 1.0f, 1.0f);    // 调整模型的颜色

现在我们开始绘制那些三角形。尽管我们看到在数组中的每一个多边形,然后旋转它的每一个顶点。第一步是拷贝法线信息到一个临时的结构中。因此我们能旋转法线,但仍然保留原来保存的值(没有精确降级)。

 glBegin (GL_TRIANGLES);     // 告诉 OpenGL 我们即将绘制三角形

  for (i = 0; i < polyNum; i++)   // 从头到尾循环每一个多边形
  {
   for (j = 0; j < 3; j++)   // 从头到尾循环每一个顶点
   {
    TmpNormal.X = polyData[i].Verts[j].Nor.X; // 用当前顶点的法线值填充TmpNormal结构
    TmpNormal.Y = polyData[i].Verts[j].Nor.Y; 
    TmpNormal.Z = polyData[i].Verts[j].Nor.Z;

第二,我们通过初期从OpenGL中攫取的矩阵来旋转这个法线。我们之后规格化因此它并不全部变为螺旋形。 

    // 通过矩阵旋转
    RotateVector (TmpMatrix, TmpNormal, TmpVector);

    Normalize (TmpVector);  // 规格化这个新法线

第三,我们获得那个旋转的法线的点积灯光方向(称为lightAngle,因为我忘了从我的旧的light类中改变它)。我们之后约束这个值在0——1的范围。(从-1到+1)

    // 计算色度值
    TmpShade = DotProduct (TmpVector, lightAngle);

    if (TmpShade < 0.0f)
     TmpShade = 0.0f; // 如果负值约束这个值到0

第四,对于OpenGL我们象忽略纹理坐标一样忽略这个值。阴影纹理与一个查找表一样来表现(色度值正成为指数),这是(我认为)为什么1D纹理被创造主要原因。对于OpenGL我们之后忽略这个顶点位置,并不断重

复,重复。至此我认为你已经抓到了概念。 

    glTexCoord1f (TmpShade); // 规定纹理的纵坐标当作这个色度值
    // 送顶点
    glVertex3fv (&polyData[i].Verts[j].Pos.X);
      }
  }

 glEnd ();      // 告诉OpenGL 完成绘制

 glDisable (GL_TEXTURE_1D);    // 1D 纹理不可用

现在我们转移到轮廓之上。一个轮廓能以“它的相邻的边,一边为可见,另一边为不可见”定义。在OpenGL中,这是深度测试被规定小于或等于(GL_LEQUAL)当前值的地方,并且就在那时所有前面的面被精选。我们同

样也要混合线条,以使它看起来不错:)
那么,我们使混合可用并规定混合模式。我们告诉OpenGL与着色线条一样着色backfacing多边形,并且规定这些线条的宽度。我们精选所有前面多边形,并规定测试深度小于或等于当前的Z值。在这个线条的的颜色被

规定后,我们从头到尾循环每一个多边形,绘制它的顶点。我们仅需忽略顶点位置,而不是法线或着色值因为我们需要的仅仅是轮廓。

 // 轮廓代码
 if (outlineDraw)      // 检查看是否我们需要绘制轮廓
 {
  glEnable (GL_BLEND);    // 使混合可用
  // 调整混合模式
  glBlendFunc (GL_SRC_ALPHA ,GL_ONE_MINUS_SRC_ALPHA);

  glPolygonMode (GL_BACK, GL_LINE);   // 绘制轮廓线
  glLineWidth (outlineWidth);    // 调整线宽

  glCullFace (GL_FRONT);    // 剔出前面的多边形

  glDepthFunc (GL_LEQUAL);    // 改变深度模式

  glColor3fv (&outlineColor[0]);   // 规定轮廓颜色

  glBegin (GL_TRIANGLES);    // 告诉OpenGL我们想要绘制什么

   for (i = 0; i < polyNum; i++)  // 从头到尾循环每一个多边形
   {
    for (j = 0; j < 3; j++)  // 从头到尾循环每一个顶点
    {
     // 送顶点
     glVertex3fv (&polyData[i].Verts[j].Pos.X);
    }
   }

  glEnd ();      // 告诉 OpenGL我们已经完成

这样以后,我们就把它规定为以前的状态,然后退出 

  glDepthFunc (GL_LESS);    // 重置深度测试模式

  glCullFace (GL_BACK);    // 重置剔出背面多边形

  glPolygonMode (GL_BACK, GL_FILL);   // 重置背面多边形绘制方式

  glDisable (GL_BLEND);    //  混合不可用
 }
}
原文及其个版本源代码下载:

http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=37

转载于:https://www.cnblogs.com/arxive/p/6239540.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/394267.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SDN交换机在云计算网络中的应用场景

SDN的技术已经发展了好几年了&#xff0c;而云计算的历史更长&#xff0c;两者的结合更是作为SDN的一个杀手级应用在近两年炒得火热&#xff0c;一些知名咨询公司的关于SDN逐年增加的市场份额的论断&#xff0c;也主要是指SDN在云计算网络中的应用。 关于SDN在云计算网络中的应…

sql server 里面怎么支持数字使用双引号_国查:用中文编写SQL

这两天被 文言(wenyan-lang)刷屏了&#xff0c;这个项目在于使用文言文进行编程&#xff0c;我打算蹭个热度&#xff0c;把年初的作品再捞一捞&#xff0c;即中文SQL。1. 文言Wenyan&#xff1a;吾有一數。曰三。名之曰「甲」。為是「甲」遍。吾有一言。曰「「問天地好在。」」…

革新以太网交换机架构 全光网络的风刮进园区

全光网络的风正在刮进园区网&#xff0c;众所周知&#xff0c;光纤入户发展迅速&#xff0c;随着PON&#xff08;无源光纤网络&#xff09;技术在运营商通信网络的大规模使用&#xff0c;PON相关产业链逐步成熟&#xff0c;这也使得PON技术逐步在企业园区网得到应用。 基于铜线…

漫谈单点登录(SSO)(淘宝天猫)(转载)

1. 摘要 &#xff08; 注意&#xff1a;请仔细看下摘要&#xff0c;留心此文是否是您的菜&#xff0c;若浪费宝贵时间&#xff0c;深感歉意&#xff01;&#xff01;&#xff01;&#xff09; SSO这一概念由来已久&#xff0c;网络上对应不同场景的成熟SSO解决方案比比皆是&…

越狱第一至五季/全集迅雷下载

越狱 第一季 Prison Break Season 1 (2005) 本季看点&#xff1a;迈克尔斯科菲尔德是一头陷于绝境欲拼死一搏的怒狮——他的哥哥林肯巴罗斯被认定犯有谋杀罪被投入了福克斯河监狱的死囚牢。虽然所有的证据都指出林肯就是凶手&#xff0c;迈克尔坚信兄长是无辜的。林肯的死刑执行…

java -jar 默认参数_JAVA入门学习指南,建议收藏

如果你不懂Java 并且想认真学习接触了解一下Java的语法&#xff0c;建议把这篇文章收藏了&#xff0c;多看几遍&#xff0c;应该可以初步掌握Java 大部分基础的语法 。 让我们出发吧&#xff01;ps:本文有点长&#xff0c;耐心阅读 。〇&#xff0c;编程环境工程项目推荐使用ID…

【RabbitMQ】 WorkQueues

消息分发 在【RabbitMQ】 HelloWorld中我们写了发送/接收消息的程序。这次我们将创建一个Work Queue用来在多个消费者之间分配耗时任务。 Work Queues&#xff08;又称为&#xff1a;Task Queues&#xff09;的主要思想是&#xff1a;尽可能的减少执行资源密集型任务时的等待时…

笑看职场什么程序员才抢手,什么样的程序员涨薪多?

​程序员&#xff0c;怎么才算合格&#xff0c;不好说吧&#xff1b;他就像销售一样&#xff0c;一名销售员&#xff0c;比如网络销售卖茶叶&#xff0c;他卖茶叶很厉害呀&#xff0c;可是你让他去销售房地产&#xff0c;就算他有点销售的基础&#xff0c;也要重新去学怎么销售…

Android画布Canvas裁剪clipRect,Kotlin

Android画布Canvas裁剪clipRect&#xff0c;Kotlin private fun mydraw() {val originBmp BitmapFactory.decodeResource(resources, R.mipmap.pic).copy(Bitmap.Config.ARGB_8888, true)val newBmp Bitmap.createBitmap(originBmp.width, originBmp.height, Bitmap.Config.A…

调查|73%的公司正使用存在漏洞的超期服役设备

本文讲的是调查&#xff5c;73%的公司正使用存在漏洞的超期服役设备&#xff0c;一份新近的调查覆盖了北美350家机构的212000台思科设备。结果显示&#xff0c;73%的企业正在使用存在漏洞、超期服役的网络设备。该数字在上一年仅为60%。 Softchoice公司思科部门业务主管大卫魏格…

深度装机大师一键重装_笔记本怎么重装系统?笔记本自己如何重装系统?

如何给笔记本重装系统呢?笔记本系统使用时间长了难免会运行缓慢&#xff0c;我们第一反应就是重装系统笔记本了。但是很多小白用户们就惆怅了&#xff0c;不知道笔记本怎么重装系统&#xff0c;怎么进行重装系统笔记本呢?首先&#xff0c;笔记本电脑可以重置系统&#xff0c;…

XMLHttpRequest状态码及相关事件

1.创建一个XMLHttpRequest对象 2.对XMLHttpRequest对象进行事件的监听(定义监听事件的位置不影响 3.对XMLHttpRequest对象的状态码 状态 名称描述0Uninitialized初始化状态。XMLHttpRequest 对象已创建或已被 abort() 方法重置1Open open() 方法已调用&#xff0c;但是 send()…

人工智能时代号角已吹响 COMPUTEX如何凝聚AI这股力量?

当前谈到人工智能&#xff08;AI&#xff09;&#xff0c;或许大家最直接的反应是Google的AlphaGo&#xff0c;但比起“遥不可及”的围棋机器人&#xff0c;其实AI早就融入人们生活&#xff0c;就像苹果手机中的语音助手Siri&#xff0c;如此“平易近人”。从自动驾驶、机器人、…

unity开宝箱动画_[技术博客]Unity3d 动画控制

在制作游戏时&#xff0c;导入的箱子模型本身自带动画。然而&#xff0c;它的动画是一个从打开到关闭的完整过程&#xff0c;并且没有给出控制打开关闭的方法。最直接的想法是对该动画进行拆分&#xff0c;再封装成不同的动画状态&#xff0c;但是不巧的是&#xff0c;这个动画…

如何把一个软件嵌入另一个软件_自动化正在成为一个“软件”行业

摘要在智能制造时代&#xff0c;自动化行业正在成为一个软件行业&#xff0c;它正在改变着整个产业的未来&#xff0c;也将为制造业带来更为广阔的空间。自动化正在成为一个“软件”行业&#xff0c;在智能时代&#xff0c;软件正在成为自动化行业竞争的关键。自动化已然成为软…

python怎么显示求余的除数_Python算术运算符及用法详解

算术运算符也即数学运算符&#xff0c;用来对数字进行数学运算&#xff0c;比如加减乘除。下表列出了 Python 支持所有基本算术运算符。表 1 Python 常用算术运算符运算符说明实例结果加12.45 1527.45-减4.56 - 0.264.3*乘5 * 3.618.0/除法(和数学中的规则一样)7 / 23.5//整除…

HTML td 标签的 colspan 属性

表格单元横跨两列的表格&#xff1a; <table border"1"><tr><th>Month</th><th>Savings</th></tr><tr><td colspan"2">January</td></tr><tr><td colspan"2">Fe…

Kotlin的Lambda表达式以及它们怎样简化Android开发(KAD 07)

作者&#xff1a;Antonio Leiva 时间&#xff1a;Jan 5, 2017 原文链接&#xff1a;https://antonioleiva.com/lambdas-kotlin/ 由于Lambda表达式允许更简单的方式建模式函数&#xff0c;所以它是Kotlin和任何其他现代开发语言的最强工具之一。 在Java6中&#xff0c;我们仅能下…

Pyhon进阶9---类的继承

类的继承 基本概念 定义 格式如下 继承中的访问控制 class Animal:__CNOUT 0HEIGHT 0def __init__(self,age,weight,height):self.__CNOUT self.__CNOUT 1self.age ageself.__weight weightself.HEIGHT heightdef eat(self):print({} eat.format(self.__class__.__name__…

quartz教程二

转载于:https://www.cnblogs.com/mumian2/p/10729901.html