3D class &四元数

2006-03-16 09:58:14

l)对象和世界坐标 对象坐标是描述3D对象基本形状的xyz维。世界坐标是描述对象的xyz坐标,当对象被放置在一个指定的3D环境中某个位置和旋度上。所以称为世界坐标是因为3D环境被称为3D世界。 (2)摄影机和彩像面坐标 摄影机(Camera)坐标是xyz坐标,它描述该对象对于一个观察者如何出现在3D环境中的指定位置。摄影机位置被称为视点(观察点)(Viewpoint)。 影像面坐标是xy坐标,它将出现在一个两维的设置在摄影机和3D对象之间的影像面(或窗口)上。 (3)屏幕坐标 屏幕坐标是x y坐标,它能够在计算机显示屏幕上报绘出来。屏幕坐标也称为显示器(display)坐标,它是将影像面定标以适合显示屏幕的结果。 在这个3D操作过程中计算每组坐标都包括从矩阵数学导出的正弦(sine)和余弦(cosine)公式。 我们真的需要四元数吗? 作者是一个经验丰富的游戏开发者,数学学士。 第一部分:等价 最近我的一些顾客被要求在图形绘制里加入支持3D旋转的四元数。我进行了它的研究并为我的发现而惊讶。好象有一类四元数的狂热爱好者。四元数在书籍、文章、网上被广泛的证明。开发者用它们来开发游戏。但是它确实是对3D图形绘制程序有用的工具吗?在我看来,未必。 四元数是一个数学概念。他们包括虚数和第四维度,是一独特的代数分支,使用一种枯燥的乘法运算。规则如下:ij=k, ji=-k and i*i= -1,它可以创建一函数来设定在3D空间里旋转的点。四元数可以做到。它做被要求做的工作。但它是否可以做任何事情,而这些是传统的数学无法轻易作到的吗?我不认为是这样。 下面是一个常见的四元数旋转的矩阵表示. 其中,(x,y,z)是旋转轴上的单位向量,0是旋转的角度. 这是一标准的任意轴旋转的矩阵表示. 同样,(x,y,z)是旋转轴上的单位向量,0是旋转的角度. 第一个矩阵是基于四元数,第二个矩阵建立于传统的欧拉坐标系.作者应用了大量的数学公式来证明,他们是可以互相转化的.作者第二部分是基于R3(欧拉坐标系)的绕任意轴旋转的矩阵证明。它的目的就是两者的对比,来比较优劣。我突然懒的翻译以后的部分,我想简单的做一四元数介绍更为恰当,如果有感兴趣的爱好者,可以看原文理解。 序:一个标准的四元数的表达应该是q*q的平方根。对于我,四元数这个术语好象是世界之外的东西,象是关于量子论里的术语,拥有神秘的黑暗力量。如果你,同样被它黑暗的色彩迷惑,这篇文章,希望能带给你启迪。文章将教你如何应用四元数进行3D旋转,并使得你理解它的含义。 为什么使用四元数?回答它之前,先讨论一些向量表达。 欧拉法。这是最简单实用的方向表达。每一轴,都有一个特定的旋转角度。这样,我们有3个变量: x,y,z ←旋转角度基于默认系统坐标系。它的变化在0-360度之间。她们是旋转,倾斜,偏移的表示。方向是由3个角度的旋转复数的乘积得到(通过你定义的顺序)。旋转是由全局坐标系决定的。这意味着第一个旋转不能改变第二、第三个旋转的轴。从而造成"万向锁"的情况,我将在一会讨论。 任意轴表示法: 这种表示法的好处是避免了欧拉表示法造成的"万向锁"问题。它由任意轴的单位向量,以及旋转角度的变量构成。 x, y, z <-- 任意轴的单位向量表示 angle <-- 该轴的旋转角度 这些表示法有什么缺点吗? 万向锁: 欧拉系中的旋转涉及固定的全局轴。一个轴的旋转毫不影响另一个轴向的旋转。导致你失去自由的角度。这就是万向锁。比方,一个向量(平行于X轴)绕Y轴的旋转。。。。。。。。。。。。 插值问题: 尽管任意轴坐标系没有万向锁的问题,但当你需要在两个旋转中插入旋转的时候,会出现问题。计算出的内插值方向将不平滑,变成一种紊乱的运动。欧拉也有这样的问题。 正式开始 我们先需要建立一些假设。否则会造成很多数学上的混淆。 坐标系:假定为右手法则,象是OpenGL。如果你在使用Direct3D等左手法则系统,你将需要做一些转换。(bluepoint提示:在图形学和工程学的规则中,右手法则是表达和操作三维坐标的标准数学约定。不过只要您清楚所选坐标系的含义,并且做到前后一致,那么具体选择什么坐标系其实并不重要。)注意Direct3D 范例里有四元数库,你可以在使用前看一看。 [img]attachments/month_0603/3zr6_2004527155823673.jpg[/img] 旋转顺序: (bluepoint:由于矩阵乘法在通常情况下具有不可传递性,我们规定了以下顺序:)欧拉坐标系的旋转的次序是x,y,z;矩阵形式: RotX * RotY * RotZ <-- 很重要 [ 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 ] 矢量和点: 旋转矩阵 [ vx vy vz 1 ] 4*1 矢量 什么是四元数? 复数的虚部有一个i单位来构成,其中i*i=-1;四元数是复数的扩展。不同的是,它有3个单位属于-1的平方根,定义为i,j,k;这样,一个四元数可以表示为:q = w + xi + yj + zk。W是一个实数,其余的是虚数。另一种常用的表示:q=[ w,v ]。v = (x, y, z)(V代表向量,W是一个标量),尽管V称为向量,不要视其为典型的3D向量。它是4D空间的向量,无法直觉的想象到。 定义四元数 我们有两种方式: q= [1,(0, 0, 0)] q= [0,(0, 0, 0)](这种不是我们要使用的) 使用四元数 首先要指出四元数不是向量,所以不要把你预想的向量数学带进来。解释需要一些数学理论,请多多包涵。 我们先定义四元数的量级。 || q ||= Norm(q) =sqrt(w2 + x2 + y2 + z2) 单位四元数有下面的属性:w2 + x2 + y2 + z2=1 规范化四元数的表达,q = q / || q || = q / sqrt(w2 + x2 + y2 + z2) 单位四元数的特殊就在于它描绘了一个3D空间的方向。所以你可以用它来代替前面讨论的两种方法来表示方向。要应用它,你需要把四元数的表示方法转换一下。 观察四元数 你可以把四元数想象成4D空间里的旋转。(x,y,z)形成任意轴,w形成旋转的角度。所有的单位四元数形成了一个球状的4D空间。它是很难以直觉理解。注意:只有单位四元数才可以用来描述方向。我们的讨论就是基于这点。 四元数的转化形式 为了有效的使用四元数,需要把它们转换成另一种表示法。 四元数变为矩阵。 等价的旋转矩阵表示四元数的形式: Matrix = [ w2 + x2 - y2 - z2 2xy - 2wz 2xz + 2wy 2xy + 2wz w2 - x2 + y2 - z2 2yz - 2wx 2xz - 2wy 2yz + 2wx w2 - x2 - y2 + z2 ] 使用恒等式简化一下:w2 + x2 + y2 + z2 = 1 Matrix = [ 1 - 2y2 - 2z2 2xy - 2wz 2xz + 2wy 2xy + 2wz 1 - 2x2 - 2z2 2yz - 2wx 2xz - 2wy 2yz + 2wx 1 - 2x2 - 2y2 ] 四元数转化为任意轴形式 如果旋转轴是ax, ay, az),角度是theta ,那么角度angle= 2 * acos(w); ax= x / scale ay= y / scale az= z / scale 其中 scale = x2 + y2 + z2,另一种我发现的表达是scale = sin(acos(w)). 它们应该是等价的。如果scale=0,那么旋转角度就为0。 单位四元数表示3D空间的旋转度,那么多个四元数的乘积将是另一个旋转度的表示。 给出两个四元数: Q1=(w1, x1, y1, z1); Q2=(w2, x2, y2, z2); Q1 * Q2 =( w1.w2 - v1.v2, w1.v2 + w2.v1 + v1*v2); 其中v1= (x1, y1, z1) v2 = (x2, y2, z2) 需要注意的是q1 * q2 不等于 q2 * q1。 转变为四元数。 任意轴系到四元数 w = cos(theta/2) x = ax * sin(theta/2) y = ay * sin(theta/2) z = az * sin(theta/2) 欧拉系到四元数 你可以把角度分为3个独立的四元数,然后使用乘法来组合得到最终的四元数表达。 Qx = [ cos(a/2), (sin(a/2), 0, 0)] Qy = [ cos(b/2), (0, sin(b/2), 0)] Qz = [ cos(c/2), (0, 0, sin(c/2))] Q=Qx * Qy * Qz. 总结如下: 四元数是[x, y, z]里面的第四个元素,用来描述三个分量的向量。 四元数是使用矩阵方法进行3-D旋转的一种选择,四元数提供你 一个3-D物体沿着一个轴进行旋转的能力,但是真正发挥四元数 能力的操作是:合成与插值运算 (Composition and Interpolation)。 合成两个四元数的意思是:“先沿着一个轴旋转一个给定角度,然后再沿着另一个轴旋转一个给定的角度”。合成的两个四元数可以用以下标记来表示: Q=q1oq2 进行两个四元数的插值运算可以使程序运算出从同一个轴的一点到另一点的平滑且合理的路径。 所以,q1与q2进行插值运算是实现3D动画的一种简单方法。 记住下面的话:四元数是一实数和一矢量的和的表达式,有四个项,一个为实数项,另外三个为虚数项。 它是矩阵表达法的一种替换形式,尤其是和三维坐标旋转。 (在Diana Gruber答读者问的时候,有一些观点好象和用AS2创建3D类的作者Chad Corbin相左。理论上说来,你可以用其它的矩阵表示法来推导出旋转公式 -------------------------------------------------------------------- 用AS2.0创建3D类 Author: Chad Corbin Translator:bluepoint8101 目录: 1.简介 2.AS2.0概述 3.创建四元数 4.构建3D类 5.组合 这篇文章描述了如何使用flash的基于类的模式来创建3D效果。我的开始部分是以AS2.0里新的语法和特性为主,随后介绍了用四元数的方法做较快的3D运算。 并非是创建3D效果的示例,我着重于如何在3D引擎里创建类。我测试这些类并且示范怎么运用他们来创建3D场景。希望看过我的文章后,你可以应用或修改这些类来输出你自己的3D效果。 AS2.0简介 原谅我的偷懒,不过denger翻译person13上的AS2.0入门更详细些。有兴趣的可以参阅以下连接:http://dengjie.com/Joey_Lott/as2primer.swf http://dengjie.com/Joey_Lott/as2primer_2.swf http://dengjie.com/Joey_Lott/as2primer_3.swf 创建四元数 在开始这段翻译之前,介绍几篇关于四元数的文章,我摘录的翻译连接: 如果你花费很多时间来寻找一种创建3D对象或场景的方法,也许会发现四元数的存在。四元数是具有w,x,y,z组元的四维对象,描述旋转轴和角度。3D引擎里常用到它来快速的旋转空间里的点。因为四元数只包含四个变量,相比3D矩阵方式,它需求较少的内存和计算。这对于类似AS等的脚本语言更为理想。 略过烦琐的四元数数学论证,它们的来源,如何工作和方式不谈,我只举出一些需要用到的方程式: ca = Math.cos(angle/2); sa = Math.sin(angle/2); m = Math.sqrt(x*x + y*y + z*z); Qx = a/m * sa; Qy = b/m * sa; Qz = c/m * sa; Qw = ca; 其中,x,y,z构成旋转轴,angle是弧度形式的角度,Qx, Qy, Qz, 和 Qw就是我们要运算出的四元数表达。以上就是我们要在四元数类里用到的等式。 一个重要的特性就是你可以把他们相乘,相连接来合成代表两个轴和旋转的组合四元数。当你需要多重的旋转,串联节省了每个四元数单独计算旋转的时间。四元数的结合方程也很简单: Q2*Q1 = Q1w*Q2w – Q1x*Q2x – Q1y*Q2y – Q1z*Q2z, Q1w*Q2x + Q1x*Q2w + Q1y*Q2z – Q1z*Q2y, Q1w*Q2y + Q1y*Q2w + Q1z*Q2x – Q1x*Q2z, Q1w*Q2z + Q1z*Q2w + Q1x*Q2y – Q1y*Q2x 另一个重要的特性就是你可以把3D的点转换成四元数。只需要把x,y,z坐标赋值为四元数的x,y,z组元,并且设定w=0。然后就可以将它和四元数相乘来旋转空间的点。你做了下面的代码练习后,会明白它们如何实现。 创建3D类 这段描述如何去构建3D对象和环境的类。当你看每个类的时候,注意我是如何应用这些AS的新特性的。你将会注意到一些不熟悉的AS2.0特征,听我慢慢道来。 Drawable 第一段代码正是一个很简洁的接口。不管它是如何的短小,它扮演着一个重要的角色。可绘制的接口由一个函数申明组成。 interface com.lo9ic.Drawable { function draw():Void; } 所有绘画的类都执行这个接口。为什么你执行文件却是一片空白?注意:示例文件在路径com/lo9ic下,所以我们的接口前缀也包含com.lo9ic。 样式 样式类包含了绘画类的样式信息。这个类有五个私有属性,线宽,颜色,透明度,填充颜色,填充透明度;以及设定和访问它们的方法。 class com.lo9ic.Style { private var $linealpha, $lineweight, $fillalpha : Number; private var $linecolor, $fillcolor : String; function Style() { $lineweight = 0; $linealpha = 100; $fillalpha = 100; $linecolor = "0x000000"; $fillcolor = "0x000000"; } public function set linealpha(a:Number):Void { $linealpha = a; } public function set lineweight(a:Number):Void { $lineweight = a; } public function set linecolor(a:String):Void { $linecolor = a; } public function set fillalpha(a:Number):Void { $fillalpha = a; } public function set fillcolor(a:String):Void { $fillcolor = a; } public function get linealpha():Number { return $linealpha; } public function get lineweight():Number { return $lineweight; } public function get linecolor():String { return $linecolor; } public function get fillalpha():Number { return $fillalpha; } public function get fillcolor():String { return $fillcolor; } } 这段代码包含了AS2.0里的 implicit(内置) getter(获取)/setter(设定) 函数。遵循良好的面向对象编程,让你拥有获取和设定对象私有属性的方法。好处就是你可以不破坏依赖于这些属性的代码而修改内在属性的表达。注意有一个和类命名一样的函数。它就是构造函数。AS2.0里,构造函数初始化元素属性,并在类的实例创建的时候执行。 节点: 节点是3D空间中附着在线、曲线、多边形的终点和控制点。不仅仅是3D对象的附着点,节点经常改善3D引擎的效率。例如,你可以用4个点来定义立方体的每个面。6个面,就需要24个点来定义所有的面。但是如果外观面允许共享点的话,只需要8个点。将需要3D运算的点减少到1/3。 开始构建节点前,让我们熟悉一下要用到的属性和方法。第一,节点包含x,y,z三个相对于3D空间的坐标属性;第二,每个节点的旋转用到我前面提到的四元数;这意味着你要编写一个含有四元数参数的旋转方法。最后,一个用到3D空间到2D屏幕的投影关系的函数。(详细请参见Creating Real-Time 3D Objects with Macromedia Flash MX Dynamic Drawing Tools."-published by DEV ) 下面就是最终的节点代码: import com.lo9ic.Quaternion; class com.lo9ic.Node { private var $x, $y, $z, $xp, $yp, $zp:Number; private var $q1, $q2, $q3:Quaternion; function Node(a:Number, b:Number, c:Number) { $x = a ? a : 0; $xp = $x; $y = b ? b : 0; $yp = $y; $z = c ? c : 0; $zp = $z; } public function get x():Number { return $xp; } public function get y():Number { return $yp; } public function get z():Number { return $zp; } public function set x(a:Number):Void { $x = a; } public function set y(a:Number) :Void { $y = a; } public function set z(a:Number):Void{ $z = a; } public function rotate(q:Quaternion):Void { $q1 = q.copy(); $q1.invert(); $q2 = new Quaternion(); $q2.fromPoint($x, $y, $z); $q3 = q.copy(); $q2.concat($q1); $q3.concat($q2); $xp = $q3.x; $yp = $q3.y; $zp = $q3.z; } public function project(a:Number):Void { $xp = a*$xp/($zp-a); $yp = a*$yp/($zp-a); $zp = $zp; } } 四元数 四元数类创建和存储轴和角度信息。它包含4个参数的属性和获取、设定函数。还包含设定角度和旋转的方法——一个使用3点坐标,一个使用轴和角度。增加的3个连接、转化、复制四元数的旋转函数和节点类里的方法类似。 代码如下: class com.lo9ic.Quaternion { private var $x, $y, $z, $w:Number; function Quaternion(a:Number, b:Number, c:Number, d:Number) { $x = a ? a : 0; $y = b ? b : 0; $z = c ? c : 0; $w = d ? d : 1; } public function get x():Number { return $x; } public function get y():Number { return $y; } public function get z():Number { return $z; } public function get w():Number { return $w; } public function fromPoint(a:Number, b:Number, c:Number):Void { $x = a; $y = b; $z = c; $w = 0; } public function fromAxisAngle(a:Number, b:Number, c:Number, d:Number):Void { var ca = Math.cos(d/2); var sa = Math.sin(d/2); var m = Math.sqrt(a*a + b*b + c*c); $x = a/m * sa; $y = b/m * sa; $z = c/m * sa; $w = ca; } public function concat(q:Quaternion):Void { var w1 = $w; var x1 = $x; var y1 = $y; var z1 = $z; var w2 = q.w; var x2 = q.x; var y2 = q.y; var z2 = q.z; $w = w1*w2 - x1*x2 - y1*y2 - z1*z2 $x = w1*x2 + x1*w2 + y1*z2 - z1*y2 $y = w1*y2 + y1*w2 + z1*x2 - x1*z2 $z = w1*z2 + z1*w2 + x1*y2 - y1*x2 } public function invert():Void { $x = -$x; $y = -$y; $z = -$z; } public function copy():Quaternion { return new Quaternion($x, $y, $z, $w); } } Line 首先使用可绘制接口的类是线段类。定义3D空间里的线条的类用了两个代表终点的节点。同是包含了剪辑视觉展现的风格和属性。象前面做的一样,要为每一个私有变量属性创建设定和获取的方法。 import com.lo9ic.Node; import com.lo9ic.Style; import com.lo9ic.Drawable; class com.lo9ic.Line implements Drawable { private var $startnode, $endnode:Node; private var $style:Style; private var $clip:MovieClip; function Line(a:Node, b:Node) { $startnode = a ? a : new Node(); $endnode = b ? b : new Node(); $style = new Style(); } public function set startnode(a:Node):Void { $startnode = a; } public function set endnode(a:Node):Void { $endnode = a; } public function get startnode():Node { return $startnode; } public function get endnode():Node { return $endnode; } public function set style(a:Style):Void { $style = a ? a:$style; } public function get style():Style { return $style; } public function set clip(a:MovieClip):Void { $clip = a; } public function draw():Void { $clip.clear(); $clip.moveTo($startnode.x, $startnode.y); $clip.lineStyle($style.lineweight, $style.linecolor, $style.linealpha); $clip.lineTo($endnode.x, $endnode.y); $clip.swapDepths(10000+($endnode.z+$startnode.z)/2); } } 线条类的draw()方法使用了drawing API 来依照剪辑属性指定的样式绘制线条。最后一段代码修正了剪辑的深度以保证它出现在Z值较小的对象前面。这个堆栈的对象更靠近3D环境里的高的层,为了场景的真实性。 曲线 将线条类扩展为曲线类,是因为他们共享着很多的属性和方法。同样也要导入可绘制对象类。 import com.lo9ic.Node; import com.lo9ic.Style; import com.lo9ic.Line; import com.lo9ic.Drawable; class com.lo9ic.Curve extends Line implements Drawable { private var $controlnode : Node; function Curve(a:Node, b:Node, c:Node) { $startnode = a ? a : new Node(); $endnode = b ? b : new Node(); $controlnode = c ? c : new Node(); } public function set controlnode(a:Node):Void { $controlnode = a; } public function get controlnode():Node { return $controlnode; } public function draw():Void { $clip.clear(); $clip.moveTo($startnode.x, $startnode.y); $clip.lineStyle($style.lineweight, $style.linecolor, $style.linealpha); $clip.curveTo($controlnode.x, $controlnode.y, $endnode.x, $endnode.y); $clip.swapDepths(10000+($endnode.z+$startnode.z+$controlnode.z)/3); } } 多边形 在3D空间里多边形是使用顶点节点的平直表面。在3D环境里,多边形常常组合起来形成更复杂的表面和物体。同样引进接口,和样式、剪辑属性。 import com.lo9ic.Drawable; import com.lo9ic.Node; import com.lo9ic.Style; class com.lo9ic.Polygon implements Drawable { private var $nodes:Array; private var $style:Style; private var $clip:MovieClip; private var $i, $j, $z:Number; function Polygon() { $nodes = new Array(); $style = new Style(); } public function addNode(a:Node, b:Boolean) { $nodes.push({node:a, control:b}); } public function set style(a:Style):Void { $style = a ? a:$style; } public function get style():Style { return $style; } public function set clip(a:MovieClip):Void { $clip = a; } public function draw():Void { $i = 1; $j = 1; $z = 0; $clip.clear(); $clip.moveTo($nodes[0].node.x, $nodes[0].node.y); $clip.lineStyle($style.lineweight, $style.linecolor, $style.linealpha); $clip.beginFill($style.fillcolor, $style.fillalpha); while( $i <= $nodes.length) { $j = $i%$nodes.length; if(!$nodes[$j].control && !$nodes[$i-1].control) { $clip.lineTo($nodes[$j].node.x, $nodes[$j].node.y); } else if ($nodes[$i-1].control) { $clip.curveTo($nodes[$i-1].node.x, $nodes[$i-1].node.y, $nodes[$j].node.x, $nodes[$j].node.y); } $z += $nodes[$j].node.z; $i++; } $clip.endFill(); $clip.swapDepths(10000+$z/$nodes.length); } } 代替了终点和控制点属性,多边形类使用节点数组来定义边角。addNode() 提供了给数组增加节点的方法,随意你指定一个控制节点。draw() 方法在这些数组里循环,用直线或曲线边界连接接点并填充区域形成表面.draw() 方法的执行相比较线和曲线类来说,更为复杂. 场景 场景类,最后一个讲解的物件,管理3D环境里的所有对象的表现.这个类包含两个管理节点和可绘制对象的数组,一个提供旋转的四元数,"F"属性是我在节点类提及的投影要素。场景类本身并不显现,但它包含的对象会,所以场景类同样实现可绘制接口。addNode() 和addObject() 方法允许你给场景增加节点,线段,曲线,多边形;draw()函数通过节点和对象数组循环,调用场景里对象的绘制函数。 import com.lo9ic.Style; import com.lo9ic.Quaternion; import com.lo9ic.Node; import com.lo9ic.Drawable; class com.lo9ic.Scene implements Drawable { private var $f:Number; private var $nodes:Array; private var $objects:Array; private var $quaternion:Quaternion; private var $clip:MovieClip; private var $i:Number; function Scene() { $f = 300; $nodes = new Array(); $objects = new Array(); $quaternion = new Quaternion(); } public function get f():Number { return $f; } public function set f(a:Number):Void { $f = a; } public function set quaternion(a:Quaternion):Void { $quaternion = a; } public function get quaternion():Quaternion { return $quaternion; } public function set clip(a:MovieClip):Void { $clip = a; } public function get clip():MovieClip { return $clip; } public function addNode(a:Node):Void { $nodes.push(a); } public function addObject(arawable):Void { $objects.push(a); } public function draw():Void { $i = 0; while($i<$nodes.length) { $nodes[$i].rotate($quaternion); $nodes[$i].project($f); $i++; } $i = 0; while($i<$objects.length) { $objects[$i].draw(); $i++; } } } 为线段、曲线、多边形使用接口, 从而用一个简单的函数简化了给场景增加对象的过程。这些类的接口的实现,还减少了draw()函数循环的个数。(bluepoint提示,为单独的一个类建立接口,没有多大意思。当管理一个大的项目,很多类有个共同的性质的时候,构建一个接口就体现它的效率。) 组合 写完以上的代码,你已经可以创建一个3D场景。我的汽车模型的思想主要是组合节点、线条、多边形构成车的轮廓。避免你一行行代码研究,我在每个地方加了注释 [swf]attachments/month_0603/b91e_3dclasses_fig01.swf[/swf] // 导入类和接口(通配符代替) import com.lo9ic.*; // 创建场景类并置中 var mySceneClip:MovieClip = cr&#101;ateEmptyMovieClip("SceneMC", 1); mySceneClip._x = 275; mySceneClip._y = 200; mySceneClip._yscale = mySceneClip._xscale = 250; // cr&#101;ate an instance of the Scene class and set the clip and projection factor properties var myScene:Scene = new Scene(); myScene.clip = mySceneClip; myScene.f = 200; //创建在线条和多边形里应用的风格; var bodyStyle:Style = new Style(); bodyStyle.fillcolor = "0x660000"; bodyStyle.linecolor = "0x660000"; var grillStyle:Style = new Style(); grillStyle.fillcolor = "0x666666"; grillStyle.linealpha = 0; var windowStyle:Style = new Style(); windowStyle.fillcolor = "0x000033"; windowStyle.fillalpha = 20; windowStyle.linecolor = "0x660000"; windowStyle.linealpha = 100; windowStyle.lineweight = 2; var antennaStyle:Style = new Style(); var roofStyle:Style = new Style(); var interiorStyle:Style = new Style(); interiorStyle.fillcolor = "0x5F453A"; interiorStyle.linealpha = 0; var tireStyle:Style = new Style(); tireStyle.fillcolor = "0x333333"; tireStyle.linealpha = 0; // 车的躯干节点 var n0:Node = new Node(-47,20,0); var n1:Node = new Node(-50,20,15); var n2:Node = new Node(50,20,15); var n3:Node = new Node(47,20,0); var n4:Node = new Node(-47,-20,0); var n5:Node = new Node(-50,-20,15); var n6:Node = new Node(50,-20,15); var n7:Node = new Node(47,-20,0); // 窗户,车顶和车蓬节点 var n8:Node = new Node(-22,-20,15); var n9:Node = new Node(-22,20,15); var n10:Node = new Node(15,20,15); var n11:Node = new Node(15,-20,15); // 车顶节点 var n12:Node = new Node(14,17,30); var n13:Node = new Node(-12,17,30); var n14:Node = new Node(-12,-17,30); var n15:Node = new Node(14,-17,30); // 天线节点 var n16:Node = new Node(23,20,15); var n17:Node = new Node(23,20,35); // 车轮节点 var n18:Node = new Node(20,20,0); var n19:Node = new Node(20,20,-10); var n20:Node = new Node(30,20,-10); var n21:Node = new Node(40,20,-10); var n22:Node = new Node(40,20,0); var n23:Node = new Node(20,-20,0); var n24:Node = new Node(20,-20,-10); var n25:Node = new Node(30,-20,-10); var n26:Node = new Node(40,-20,-10); var n27:Node = new Node(40,-20,0); var n28:Node = new Node(-20,20,0); var n29:Node = new Node(-20,20,-10); var n30:Node = new Node(-30,20,-10); var n31:Node = new Node(-40,20,-10); var n32:Node = new Node(-40,20,0); var n33:Node = new Node(-20,-20,0); var n34:Node = new Node(-20,-20,-10); var n35:Node = new Node(-30,-20,-10); var n36:Node = new Node(-40,-20,-10); var n37:Node = new Node(-40,-20,0); // 为场景增加节点 for (var i:Number = 0; i<38; i++) { myScene.addNode(this["n"+i]); } // cr&#101;ate movie clips for the body panels, windows and antenna var clip:MovieClip; for (var i:Number = 0; i<17; i++) { this["clip"+i] = mySceneClip.cr&#101;ateEmptyMovieClip("mc"+i, i+1); } // cr&#101;ate polygons for the body panels and windows, set the clip properties // and add the polygons to the scene var poly:Polygon; for (var i:Number = 0; i<16; i++) { this["poly"+i] = new Polygon(); this["poly"+i].clip = this["clip"+i]; myScene.addObject(this["poly"+i]); } // 为车身部分应用自定义风格 for (var i:Number = 0; i<5; i++) { this["poly"+i].style = bodyStyle; } // apply the grillstyle to the grill this["poly5"].style = grillStyle; // 为车顶部分应用自定义风格 this["poly6"].style = roofStyle; // 为车窗部分应用自定义风格 for (var i:Number = 7; i<11; i++) { this["poly"+i].style = windowStyle; } // 为车内部分应用自定义风格 this["poly11"].style = interiorStyle; // 为车轮胎部分应用自定义风格 for (var i:Number = 12; i<16; i++) { this["poly"+i].style = tireStyle; } // 为多边形增加节点 // right body panel poly0.addNode(n0); poly0.addNode(n1); poly0.addNode(n2); poly0.addNode(n3); // left body panel poly1.addNode(n4); poly1.addNode(n5); poly1.addNode(n6); poly1.addNode(n7); // trunk panel poly2.addNode(n0); poly2.addNode(n1); poly2.addNode(n5); poly2.addNode(n4); // 车厢 poly3.addNode(n1); poly3.addNode(n4); poly3.addNode(n8); poly3.addNode(n9); // 车蓬 poly4.addNode(n11); poly4.addNode(n6); poly4.addNode(n2); poly4.addNode(n10); // grill poly5.addNode(n2); poly5.addNode(n3); poly5.addNode(n7); poly5.addNode(n6); // 车顶 poly6.addNode(n12); poly6.addNode(n13); poly6.addNode(n14); poly6.addNode(n15); // 车窗 poly7.addNode(n9); poly7.addNode(n10); poly7.addNode(n12); poly7.addNode(n13); poly8.addNode(n8); poly8.addNode(n11); poly8.addNode(n15); poly8.addNode(n14); poly9.addNode(n8); poly9.addNode(n9); poly9.addNode(n13); poly9.addNode(n14); poly10.addNode(n11); poly10.addNode(n10); poly10.addNode(n12); poly10.addNode(n15); // 轮胎 poly12.addNode(n18); poly12.addNode(n19, true); poly12.addNode(n20); poly12.addNode(n21, true); poly12.addNode(n22); poly13.addNode(n23); poly13.addNode(n24, true); poly13.addNode(n25); poly13.addNode(n26, true); poly13.addNode(n27); poly14.addNode(n28); poly14.addNode(n29, true); poly14.addNode(n30); poly14.addNode(n31, true); poly14.addNode(n32); poly15.addNode(n33); poly15.addNode(n34, true); poly15.addNode(n35); poly15.addNode(n36, true); poly15.addNode(n37); // interior poly11.addNode(n8); poly11.addNode(n9); poly11.addNode(n10); poly11.addNode(n11); // 为天线创建线条,并加到场景里 var antenna = new Line(n16, n17); antenna.clip = this["clip16"]; antenna.style = antennaStyle; myScene.addObject(antenna); // 为车旋转初始位置创建四元数 var myQuat:Quaternion = new Quaternion(); myQuat.fromAxisAngle(1,0,0,-Math.PI/4); myScene.quaternion.concat(myQuat); // 设定四元数的轴和旋转角度, 和已经存在的四元数结合, 绘制场景 function run():Void { x = Math.sin(getTimer()/5000); y = Math.cos(getTimer()/5000); myQuat.fromAxisAngle(0,1,3,.1); myScene.quaternion.concat(myQuat); myScene.draw(); } // 为旋转和绘制场景设定延时; var id:Number = setInterval(run, 20);