fc2ブログ

PV3D キューブの影を表示2

2008-02-26 | 20:34

マウスドラッグでキューブを回転できます。
前回は床の真上に光源があるという都合のいい前提だったので、各頂点のx,z座標がそのまま床の座標に対応していたが、今回は光源の向きが斜めで影が映る面も二つあるので、光源と頂点を結ぶ直線が面と交差する点を求める必要がある。

直線の方程式は
P = A + tV
(A:直線上の1点、V:方向ベクトル)

面の方程式は
N・(P - P0) = 0
(N:法線ベクトル, P0:平面上の1点)

この二つの式からまずtを求めて、直線と平面の交点であるPを求める。
(この例ではAは光源の座標、Vは光源と頂点を結ぶベクトル)

package {
    import flash.display.*;
    import flash.events.*;
    import flash.filters.*;
    import flash.geom.*;

    import org.papervision3d.core.*;
    import org.papervision3d.core.math.*;
    import org.papervision3d.core.geom.* ;
    import org.papervision3d.scenes.*;
    import org.papervision3d.objects.primitives.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.shadematerials.* ;
    import org.papervision3d.materials.utils.*;
    import org.papervision3d.view.*;
    import org.papervision3d.render.*;
    import org.papervision3d.lights.*;
    import org.papervision3d.materials.special.LineMaterial ;

    [SWF(width="600", height="400", backgroundColor="#ffffff", frameRate="15")]
        public class Shadow4 extends Sprite{
            private var renderer:BasicRenderEngine = new BasicRenderEngine();
            private var viewport:Viewport3D = new Viewport3D(600, 600, false, true);
            private var scene:Scene3D = new Scene3D();
            private var camera:Camera3D = new Camera3D();
            private var pointLight:PointLight3D;

            private var cube1:Cube;
            private var cube2:Cube;
            private var childCube:Cube;
            private var light:Sphere;

            private var bd:BitmapData;
            private var bd2:BitmapData;

            private var LEN:Number = 80;

            private var lastPoint:Point;
            private var mouseDown:Boolean = false;

            private var f:Number=0;

            private var initialPoints:Array = 
                [new Number3D(LEN, +LEN, LEN),
                new Number3D(LEN, +LEN, -LEN),
                new Number3D(-LEN, +LEN, -LEN),
                new Number3D(-LEN, +LEN, LEN),
                new Number3D(LEN, -LEN, LEN),
                new Number3D(LEN, -LEN, -LEN),
                new Number3D(-LEN, -LEN, -LEN),
                new Number3D(-LEN, -LEN, LEN)
                    ];

            public function Shadow4():void
            {
                stage.align = StageAlign.TOP_LEFT;
                stage.scaleMode = StageScaleMode.NO_SCALE ;

                addChild(viewport);

                camera.y = 1200;
                camera.z = -4400;
                camera.x = 0;
                camera.focus = 4400;
                camera.zoom = 1.5;

                pointLight = new PointLight3D(true);

                bd = new BitmapData(800, 800, false, 0xff);

                cube1 = new Cube(new MaterialsList({top: new BitmapMaterial(bd)}),800,800,10);
                cube1.y = 0;
                scene.addChild(cube1);

                bd2 = new BitmapData(800, 800, false, 0xff);

                cube2 = new Cube(new MaterialsList({back: new BitmapMaterial(bd2)}),800,10,800);
                cube2.y = 400;
                cube2.z = 400;
                scene.addChild(cube2);

                childCube = new Cube(new MaterialsList({
all:new PhongMaterial(pointLight, 0xFF88FF, 0x111111, 1)}), LEN*2, LEN*2, LEN*2);
                scene.addChild(childCube);
                childCube.y = 200;

                light = new Sphere(new ColorMaterial(0xff), 10);
                scene.addChild(light);
                pointLight.y = light.y = 350;

                stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:*):void{
                        mouseDown = true;
                        lastPoint = new Point(stage.mouseX, stage.mouseY);
                        });
                stage.addEventListener(MouseEvent.MOUSE_UP, function(e:*):void{
                        mouseDown = false;
                        lastPoint = new Point(stage.mouseX, stage.mouseY);
                        });
                stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
                stage.addEventListener(Event.ENTER_FRAME, function(e:*):void{
                        f++;
                        light.x = pointLight.x = Math.cos(-f/180*Math.PI)*300;
                        light.z = pointLight.z = Math.sin(-f/180*Math.PI)*300;
                        drawShadow(bd, new Number3D(0,1,0), new Number3D(0,0,0), 0xeeeeee);
                        drawShadow(bd2, new Number3D(0,0,-1), new Number3D(0,0,400), 0xdddddd, false);
                        renderer.renderScene(scene, camera, viewport);
                        });
            }

            private function onMouseMove(e:*):void{
                if(!mouseDown)
                    return;

                var xx:Number = stage.mouseX - lastPoint.x;
                var yy:Number = -stage.mouseY + lastPoint.y;
                if(yy == 00) return;

                var rotAxis:Number3D = new Number3D();

                rotAxis.z = 0;
                rotAxis.x = yy;
                rotAxis.y = -xx;

                // calculate the amount of rotation 
                var rotAngle:Number = Math.sqrt(xx*xx + yy*yy)/200*Math.PI;
                if(rotAngle == 0) return;

                rotAxis.normalize();
                var pos:Number3D = new Number3D(childCube.x, childCube.y, childCube.z);
                var m:Matrix3D = Matrix3D.translationMatrix(-childCube.x, -childCube.y, -childCube.z);
                m = Matrix3D.multiply(m, childCube.transform); 
                m = Matrix3D.multiply(Matrix3D.rotationMatrix(rotAxis.x, rotAxis.y, 0, rotAngle), m); 
                childCube.transform = 
                Matrix3D.multiply(Matrix3D.translationMatrix(pos.x, pos.y, pos.z), m);

                lastPoint = new Point(stage.mouseX, stage.mouseY);
            }


            private function drawShadow(bd:BitmapData, n:Number3D, p0:Number3D, c:int, zy:Boolean=true):void{
                var pointsShadow:Array = [];

                for each (var pp:Number3D in initialPoints){
                    // 頂点の座標に変換
                    var p:Number3D = pp.clone();
                    Matrix3D.multiplyVector(childCube.transform, p);
                    // 光源と頂点を結ぶベクトル
                    var v:Number3D = new Number3D(p.x - pointLight.x, p.y - pointLight.y, p.z - pointLight.z);
                    // 直線の方程式 P = A + tv における媒介変数t 
                    var t:Number = (n.x*p0.x + n.y*p0.y + n.z*p0.z - 
                            (n.x*pointLight.x + n.y*pointLight.y + n.z*pointLight.z)) /
                        (n.x*v.x + n.y*v.y + n.z*v.z);
                    if(t > 0){

                        // ちょっと美しくない
                        if(zy)
                            pointsShadow.push(new Point((pointLight.x + t * v.x) + 400, 400 - (pointLight.z + t * v.z)));
                        else
                            pointsShadow.push(new Point((pointLight.x + t * v.x) + 400, 800 - (pointLight.y + t * v.y)));
                    }
                }

                var hulls:Array = ConvexHull.getConvexHull(pointsShadow);

                if(hulls.length < 3)
                    return;

                var sp:Sprite = new Sprite();
                sp.graphics.lineStyle(5,0x0);
                sp.graphics.beginFill(0x0);
                sp.graphics.moveTo(hulls[i].x, hulls[i].y);
                for (var i:int = 1 ; i < hulls.length; i++){
                    sp.graphics.lineTo(hulls[i].x, hulls[i].y);
                }
                sp.graphics.lineTo(hulls[0].x, hulls[0].y);
                sp.graphics.endFill();

                bd.fillRect(new Rectangle(0,0,800,800), c);
                bd.draw(sp);
                bd.applyFilter(bd, new Rectangle(0,0,800,800), new Point(0,0), new BlurFilter(40,40,BitmapFilterQuality.LOW));

            }
        }
}

import flash.geom.*;
internal class ConvexHull{
    public static function getConvexHull(points:Array):Array {

        var result:Array = [];
        var index:int;
        var topIndex:int;

        if(points.length < 3)
            return result;

        // 先頭のインデックスを求める
        var p1:Point = new Point(-10000,-10000);
        var p2:Point = new Point(0,-10000);
        topIndex = index = getNextIndex(points, p2, p1);

        var cnt:int = 0;
        while(cnt++ <= points.length){
            p1 = p2;
            p2 = points[index];
            result.push(p2);

            index = getNextIndex(points, p2, p1);
            if(index == topIndex)
                break;
        }

        return result;
    }

    private static function distance( p1:Point, p2:Point ):Number{
        var dx:Number = p2.x - p1.x;
        var dy:Number = p2.y - p1.y;
        return Math.sqrt( dx*dx+ dy*dy );
    }

    private static function getNextIndex(points:Array, p2:Point, p1:Point):int{
        var minIndex:int = 0;
        var min:Number = Math.PI*2;
        var minLen:Number = -1;
        var v1:Point = new Point(p2.x - p1.x, p2.y - p1.y);

        for( var j:int =0; j < points.length; j++ ) {
            if(!(points[j].x == p2.x && points[j].y == p2.y)){
                var v2:Point = new Point(points[j].x - p2.x, points[j].y - p2.y);

                var rad:Number = getTheta(v1, v2);

                // 角度が同じ場合、近いほうを優先
                if(rad == min){
                    if(distance(points[j], p2) < minLen){
                        minIndex = j;
                        min = rad;
                        minLen = distance(points[j], p2);
                    }
                }else if(rad < min){
                    minIndex = j;
                    min = rad;
                    minLen = distance(points[j], p2);
                }
            }
        }
        return minIndex;
    }

    private static function getTheta(p1:Point, p2:Point):Number{
        var len1:Number =  Math.sqrt(p1.x*p1.x + p1.y*p1.y);
        var len2:Number =  Math.sqrt(p2.x*p2.x + p2.y*p2.y);
        if(len1 == 0 || len2==0)
            return 0;
        p1.x = p1.x/len1;
        p1.y = p1.y/len1;
        p2.x = p2.x/len2;
        p2.y = p2.y/len2;

        var dot:Number = p1.x*p2.x + p1.y*p2.y;
        var theta:Number = Math.acos(dot);
        return theta;
    }
}
スポンサーサイト



Comment

なんか良いですねー
今度使ってみようかしら。重さはどんなもんですか?

  • 2008-02-26 | 22:40 |
  • 有為 URL :
  • edit

「重さ」というのはCPUの負荷のことでしょうか?
(定量的な測り方を知らないんですがどうするんでしょう?)

ちなみにBlurFilterの品質によってCPUの負荷はだいぶ違うようです。

  • 2008-02-27 | 20:31 |
  • yamasv URL :
  • edit

Post a comment

Secret