fc2ブログ

PV3D キューブの影を表示

2008-02-25 | 21:14

マウスドラッグでキューブを回転できます。
PV3D 2.0で影を表示したい場合、複数のカメラを用意して、一台で下からオブジェクトを映した映像を使って影を表示するというやり方があるようだが、
http://mrdoob.com/blog.php?postid=461
このデモでは影の座標を計算して表示している。
キューブの回転行列はtransformプロバティに格納されているので、その行列を使えばキューブの8頂点の座標を計算する事ができる。
(このデモでは)影を映す面はxz平面に平行(y座標は固定)なので、求めた座標のx,z座標がキューブの頂点に対応する平面上の点であり、その点集合の凸包が影の輪郭になる。
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="600", backgroundColor="#ffffff")]
        public class Shadow 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 parentCube:Cube;
            private var childCube:Cube;

            private var bd:BitmapData;

            private var LEN:Number = 100;

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

            public function Shadow():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();
                pointLight.y = 300;
                pointLight.z = -100;

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

                parentCube = new Cube(new MaterialsList({all: new BitmapMaterial(bd)}),800,800,10);
                parentCube.y = -200;
                scene.addChild(parentCube);

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

                stage.addEventListener(KeyboardEvent.KEY_DOWN , function(e:KeyboardEvent):void{
                        rotate(e.keyCode);
                        });

                drawLines();
                renderer.renderScene(scene, camera, viewport);

                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);
            }

            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();
                childCube.transform = 
                    Matrix3D.multiply(Matrix3D.rotationMatrix(rotAxis.x, rotAxis.y, 0, rotAngle), 
                    childCube.transform);

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

                drawLines();
                renderer.renderScene(scene, camera, viewport);
            }

            private function rotate(keyCode:int):void{
                switch (keyCode){
                    case 37 :// ←
                        childCube.yaw(1);
                        break;
                    case 39 :// →
                        childCube.yaw(-1);
                        break;
                    case 38 :// ↑
                        childCube.pitch(1);
                        break;
                    case 40 :// ↓
                        childCube.pitch(-1);
                        break;
                }

                drawLines();
                renderer.renderScene(scene, camera, viewport);
            }

            private function drawLines():void{
                var points: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)
                        ];
                var pointsShadow:Array = [];

                for each (var p:Number3D in points){
                    Matrix3D.multiplyVector3x3(childCube.transform, p);
                    pointsShadow.push(new Point(p.x + 400, 400 - p.z));
                }

                points = ConvexHull.getConvexHull(pointsShadow);
                if(points.length < 4)
                    trace(points.length);

                var sp:Sprite = new Sprite();
                sp.graphics.lineStyle(5,0x0);
                sp.graphics.beginFill(0x0);

                bd.fillRect(new Rectangle(0,0,800,800), 0xeeeeee);
                sp.graphics.moveTo(points[i].x, points[i].y);
                for (var i:int = 1 ; i < points.length; i++){
                    sp.graphics.lineTo(points[i].x, points[i].y);
                }
                sp.graphics.lineTo(points[0].x, points[0].y);
                sp.graphics.endFill();
                bd.draw(sp);
                bd.applyFilter(bd, new Rectangle(0,0,800,800), new Point(0,0), new BlurFilter(30,30,BitmapFilterQuality.HIGH));

            }
        }
}

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

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

        // 先頭のインデックスを求める
        var p1:Point = new Point(-100,-100);
        var p2:Point = new Point(0,-100);
        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

Post a comment

Secret