2008-02-26 | 20:34
マウスドラッグでキューブを回転できます。
前回は床の真上に光源があるという都合のいい前提だったので、各頂点のx,z座標がそのまま床の座標に対応していたが、今回は光源の向きが斜めで影が映る面も二つあるので、光源と頂点を結ぶ直線が面と交差する点を求める必要がある。
直線の方程式は
P = A + tV
(A:直線上の1点、V:方向ベクトル)
面の方程式は
N・(P - P0) = 0
(N:法線ベクトル, P0:平面上の1点)
この二つの式からまずtを求めて、直線と平面の交点であるPを求める。
(この例ではAは光源の座標、Vは光源と頂点を結ぶベクトル)
前回は床の真上に光源があるという都合のいい前提だったので、各頂点の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
なんか良いですねー
今度使ってみようかしら。重さはどんなもんですか?
「重さ」というのはCPUの負荷のことでしょうか?
(定量的な測り方を知らないんですがどうするんでしょう?)
ちなみにBlurFilterの品質によってCPUの負荷はだいぶ違うようです。
Post a comment