zoukankan      html  css  js  c++  java
  • Flash与3D编程探秘(七) 3D物体框架

    日期:2008年11月

    从这篇文章开始,我将开始介绍3D物体及其在空间中运动和交互。这里提到的物体是指单个的实体,比如银河系中的一颗恒星,那么空间就是银河系了。不过,所有的一切都是相对的,当一个分子作为例子中的实体的时候,那么一个细胞也可以作为3D的空间来看待(一个细胞是由很多的分子组成),同理你可以知道细胞相对于一个生物(空间)来说也是一个物体。有些说多了,不过我想让你明白,我们用程序模拟一只小狗,或者一个人作为一个整体,但是不可能完全真实的模拟它。因为,人体由数不清的细胞组成,每一个细胞都是一个物体,做着自己的运动,除非使用计算机真实模拟着人体的每一个细胞以及它的运动,否则永远不可能得到一个真实模拟的人。但是使用现代的计算机科技是不可能模拟组成人体的所有细胞,那就更不用说组成每个细胞的分子。

    还是言归正传来看一个3D物体的例子,这也是第一个绘制一个3D物体的例子。这个程序里,创建一个正方体并且让它围绕着正方体的对角线交点自转,不过这个正方体还是由8个好朋友小P组成,每个顶点站一个,由它们来勾勒这个正方体的框架。

    (非常抱歉,下面的Flash文件不再支持)

    一个小P组成的正方体,鼠标掠过开始旋转

    动画制作步骤

    1. 首先在Flash IDE里绘制一个物体小P。

    2. 开始设置还是和以前一样,原点,摄像机,焦距等等,另外不要忘记创建一个旋转角度object,存放物体在x,y和z轴的旋转角度变量。

    // constants
    var PI = 3.1415926535897932384626433832795;

    // origin is the center of the view point in 3d space
    // everything scale around this point
    // these lines of code will shift 3d space origin to the center
    var origin = new Object();
    origin.x 
    = stage.stageWidth/2;
    origin.y 
    = stage.stageHeight/2;
    origin.z 
    = 0;

    // focal length of viewer's camera
    var focal_length = 300;

    // now create a scene object to hold the spinning box
    var scene = new Sprite();
    scene.x 
    = origin.x;
    scene.y 
    = origin.y
    this.addChild(scene);

    var axis_rotation 
    = new Object();
    axis_rotation.x 
    = 0;
    axis_rotation.y 
    = 0;
    axis_rotation.z 
    = 0;

    var camera 
    = new Object();
    camera.x 
    = 0;
    camera.y 
    = 0;
    camera.z 
    = 0;

    3. 写一个函数,用它来创建空间中的一个点,scale_point代表这个点在投射到2D平面上后位置缩放的比率。

    // this function construct a 3d vertex
    function vertex3d(x, y, z, scale = 1):Object
    {
        var point3d 
    = new Object();
        point3d.x 
    = x;
        point3d.y 
    = y;
        point3d.z 
    = z;
        point3d.scale_point 
    = scale;
        
    return point3d;
    }

    4. 下面发挥一下你的空间想象力,使用第3步的函数创建正方体的8个顶点,并且把它们添加到一个数组里。

    // we calculate all the vertex
    var len = 50;                    // half of the cube width
    // now create the vertexes for the cube
    var points = [
                    
    //        x        y        z
                    vertex3d(-len,    -len,     -len),            // rear upper left
                    vertex3d(len,    -len,     -len),            // rear upper right
                    vertex3d(len,    -len,     len),            // front upper right
                    vertex3d(-len,    -len,     len),            // front upper left
                    
                    vertex3d(
    -len,    len,     -len),            // rear lower left
                    vertex3d(len,    len,     -len),            // rear lower right
                    vertex3d(len,    len,     len),            // front lower right
                    vertex3d(-len,    len,     len),            // front lower left
                ];

    5. 初始化8个小P,并且把它们放在8个顶点(映射到xy轴上的点)所在的x和y位置。

    // init balls and put them on the screen
    for (var i = 0; i < points.length; i++)
    {
        var ball 
    = new Sphere();
        ball.x 
    = points[i].x;
        ball.y 
    = points[i].y;
        ball.z 
    = 0;
        scene.addChild(ball);
    }

    6. 这个函数你在摄像机空间旋转一篇文章中应该见过,函数的功能是把3D空间的点,映射到2D平面xy上。函数执行的步骤是这样的:

        a) 提前计算出x,y和z旋转角度的正余弦值。
        b) 使用for loop遍历物体所有的顶点。
        c) 使用计算出的正余弦和三角函数对三个轴的旋转分别进行计算,得出旋转后顶点的x,y和z。
        d) 然后计算出物体在2D平面上映射后的x和y值。
        e) 并且把这些2D点添加到一个新的数组里。

        f) 最后返回这个数组。

    function project_pts(points)
    {
        var projected 
    = [];
        
    // declare some variable for saving function call
        var sin_x = Math.sin(axis_rotation.x);
        var cos_x 
    = Math.cos(axis_rotation.x);
        var sin_y 
    = Math.sin(axis_rotation.y);
        var cos_y 
    = Math.cos(axis_rotation.y);
        var sin_z 
    = Math.sin(axis_rotation.z);
        var cos_z 
    = Math.cos(axis_rotation.z);
        
        var x, y, z,                
    // 3d x, y, z
            xy, xz,            // rotate about x axis
            yx, yz,            // rotate about y axis
            zx, zy,            // rotate about z axis
            scale;            // 2d scale transform
        
        
    for (var i = 0; i < points.length; i++)
        {
            x 
    = points[i].x;
            y 
    = points[i].y;
            z 
    = points[i].z;
            
            
    // here is the theroy:
            
    // suppose a is the current angle, based on given current_x, current_y on a plane
            
    // (can be x, y plane, or y, z plane or z, x plane), rotate angle b
            
    // then the new x would be radius*cos(a+b) and y would be  radius*sin(a+b)
            
    // radius*cos(a+b) = radius*cos(a)*cos(b) - radius*sin(a)*sin(b)
            
    // radius*sin(a+b) = radius*sin(a)*cos(b) + radius*cos(a)*sin(b)         // rotate about x axis
            xy = cos_x*- sin_x*z;
            xz 
    = sin_x*+ cos_x*z;
            
    // rotate about y axis
            yz = cos_y*xz - sin_y*x;
            yx 
    = sin_y*xz + cos_y*x;
            
    // rotate about z axis
            zx = cos_z*yx - sin_z*xy;
            zy 
    = sin_z*yx + cos_z*xy;
            
    // scale it
            scale = focal_length/(focal_length+yz-camera.z);
            x 
    = zx*scale - camera.x;                // get x position in the view of camera
            y = zy*scale - camera.y;                // get x position in the view of camera
            
            projected[i] 
    = vertex3d(x, y, yz, scale);
        }
        
    return projected;
    }

    这样就得到一个数组,包含所有需要的2D数据。并不困难,你完全可以把这一段代码叫做这个程序的3D引擎,它负责了所有点的数据在空间里旋转计算和输出。

    7. 下面是动画执行的循环函数。需要注意的一点,在以后的文章中我都将使用基于时间的运动。在这里你只要知道下面的公式就可以了:旋转角度=角速度X时间。使用上面的公式,递增物体围绕y轴和x轴的旋转角度。然后使用第6步的函数计算所有的3D顶点旋转后的位置并且得到映射后的2D点。剩下你应该能想到,就是把相应的小P定位到这些定点上,并且对小球进行缩放比率scale_point,最后不要忘记对小P进行z排序。

    function move(e:Event):void
    {
        
    // well we use time based movement in this tutorial
        var current_time = new Date().getTime();                // sampe the current time
        
    // increment the rotation around y axis
        axis_rotation.y += 0.0008*(current_time-start_time);
        
    // increment the rotation around x axis
        axis_rotation.x += 0.0006*(current_time-start_time);
        start_time 
    = current_time;                                // reset the start time
        
        var projected 
    = project_pts(points);        // 3d ponts to 2d transformation         
        
        
    // now we have all the data we need to position the balls
        for (var i = 0; i < scene.numChildren; i++)                // loop throught the scene
        {
            
    // positioning the ball
            scene.getChildAt(i).x = projected[i].x;
            scene.getChildAt(i).y 
    = projected[i].y;
            scene.getChildAt(i).z 
    = projected[i].z;
            scene.getChildAt(i).scaleX 
    = scene.getChildAt(i).scaleY = projected[i].scale_point;
        }
        
        swap_depth(scene);                
    // sort out the depth 
    }

    // bubble sort algo
    function swap_depth(container:Sprite)
    {
        
    for (var i = 0; i < container.numChildren - 1; i++)
        {
            
    for (var j = container.numChildren - 1; j > 0; j--)
            {
                
    if (Object(container.getChildAt(j-1)).z < Object(container.getChildAt(j)).z)
                {
                    container.swapChildren(container.getChildAt(j
    -1), container.getChildAt(j));
                }
            }
        }
    }

    // now add the event listener and spin the box
    this.addEventListener(Event.ENTER_FRAME, move);

    注意

    例子中物体沿着x和y轴旋转,但是并没有添加z轴旋转,你可以自己添加上,看看有什么不同。

    注意

    例子中我们使用了两个for loop,第一次遍历所有的顶点把3D点转化为2D点,第二个把相应的小P定位到这些2D点的位置。虽然这样看起来会降低执行速度,但是这样会使程序的流程一目了然。如果你已经非常熟练,你可以试着修改这两个函数提高执行速度。

    使用Flash绘制API

    上面的例子看起来不错,不过只有顶点有小球,我们看起来还不满意,那么接下来做一个动态绘制的正方体。这个例子里,基本的框架并没有什么变化,正方体所有的边都是用Flash的moveTo()和lineTo()来绘制。那么我就把需要更改代码的地方解释一下。

    (非常抱歉,下面的Flash文件不再支持)

    一个正方体的框架,鼠标掠过开始旋转

    制作步骤

    1. 基本上的代码和前面是一样的,同样需要设置场景,创建正方体的顶点,注意不要再在舞台上添加小P。

    2. 当把3D点映射到2D平面上后,使用黑线把正方体相邻的两个点连接起来。非常容易理解,不过注意,有很多种办法连接这些点,你可以先连接正方体顶面的点,然后底面的点,最后连接顶面和底面,不要局限于这几种,试着去发现新的方法,找到合适你思维方式的一种。

    function move(e:Event):void
    {
        
    // well we use time based movement in this tutorial
        var current_time = new Date().getTime();                // sampe the current time
        
    // increment the rotation around y axis
        axis_rotation.y += 0.0008*(current_time-start_time);
        
    // increment the rotation around x axis
        axis_rotation.x += 0.0006*(current_time-start_time);
        start_time 
    = current_time;                            // reset the start time
        
        var projected 
    = project_pts(points);        // 3d ponts to 2d transformation         
        
        
    // now we start drawing the cube
        with (scene.graphics)
        {
            clear();
            lineStyle(
    0.50x0F6F9F1);
            
    // top face
            moveTo(projected[0].x, projected[0].y);
            lineTo(projected[
    1].x, projected[1].y);
            lineTo(projected[
    2].x, projected[2].y);
            lineTo(projected[
    3].x, projected[3].y);
            lineTo(projected[
    0].x, projected[0].y);
            
    // bottom face
            moveTo(projected[4].x, projected[4].y);
            lineTo(projected[
    5].x, projected[5].y);
            lineTo(projected[
    6].x, projected[6].y);
            lineTo(projected[
    7].x, projected[7].y);
            lineTo(projected[
    4].x, projected[4].y);
            
    // vertical lines
            moveTo(projected[0].x, projected[0].y);
            lineTo(projected[
    4].x, projected[4].y);
            moveTo(projected[
    1].x, projected[1].y);
            lineTo(projected[
    5].x, projected[5].y);
            moveTo(projected[
    2].x, projected[2].y);
            lineTo(projected[
    6].x, projected[6].y);
            moveTo(projected[
    3].x, projected[3].y);
            lineTo(projected[
    7].x, projected[7].y);
        }
    }

    3. 还有一个地方需要改动,因为不再对顶点的物体进行缩放,所以就必须要传递scale_point这个属性。

    // this function construct a 3d vertex
    function vertex3d(x, y, z):Object
    {
        var point3d 
    = new Object();
        point3d.x 
    = x;
        point3d.y 
    = y;
        point3d.z 
    = z;
        
    return point3d;
    }


    建议

    试着把上面的两种框架构建方式结合在一起,制作一个旋转的物体被线连着,试一试一条螺旋体的模型,或者如果你想增加难度的话,你还可以做一个DNA链。

    (非常抱歉,下面的Flash文件不再支持)

    DNA链

    那么到目前为止,你已经知道如何使用框架构建一个方体,不过现实中物体总是有纹理和填充色的。你也许会想,使用Flash的beginFill()函数就可以给物体加上填充色了,这不是很简单。Hum,很接近不过如果要给物体上色的话,还有很多工作要做,后面的文章中将重点开始介绍着色筛选和相关内容。

    关于Time Based和Frame Based运动

    文章第一个例子中的制作步骤里,提到关于基于时间的运动公式(只要知道了物体运动的速度,那么根据牛顿第一运动定律就可以得出物体在某个时间点的位移):

    位移 = 时间 X 速度


    回想一下,前面的几篇文章里使用的都是基于祯的运动,然而基于祯的运动是不稳定的,它的公式是:

    位移 = 执行次数 X 速度

    基于祯的运动不管程序执行流逝了多少时间,只在function执行的时候给物体的x或者y加减一定的值。这种运动是不稳定的,所以我建议大家使用基于时间的运动,下面的两个动画分别用两种运动模式做成,点击一下动画就会在function执行时执行大量的junk运算,这时你就会看到两种运动的差异。而基于时间的运动中,当速度恒定时,物体会处在正确的位置;基于祯的运动,你就会看到物体运动慢下来很多, 并不能达到物体在某个时间点应该到达的位置。由于这个页面里还有另外3个使用大量CPU运算的动画,所以我把下面的动画移到另外一个页面了,点击这里到另外一个文章里查看下面的两个动画,如果感觉动画还是不够连贯的话,那么你可以下载这两个动画到本机察看

    上一篇          目录          下一篇

    非常抱歉,文中暂时不提供源文件下载,如果你需要源文件,请来信或者留言给我。

    作者:Yang Zhou
    出处:http://yangzhou1030.cnblogs.com
    本文版权归作者和博客园共有,转载未经作者同意必须保留此段声明。请在文章页面明显位置给出原文连接,作者保留追究法律责任的权利。
  • 相关阅读:
    洛谷P1469 找筷子(异或)
    洛谷P1464 Function(记忆化)
    洛谷P2895 [USACO08FEB]Meteor Shower S(BFS)
    细讲递归(recursion)
    博客美化总结(持续更新)
    OneDrive撸5T硬盘空间教程
    标准I/O库(详解)(Standard I/O Library)
    C++(SOCKET)简单爬虫制作
    #include <bits/stdc++.h>头文件
    Windows 10家庭版升级到专业版,系统蓝屏
  • 原文地址:https://www.cnblogs.com/yangzhou1030/p/1330015.html
Copyright © 2011-2022 走看看