Beautifl - Flash Gallery

Thumbnail : 3D Pythagoras tree (anaglyph)
3D Pythagoras tree (anaglyph)
makc3d 2009-08-30 see code comments

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

// forked from 3D Pythagoras tree - anaglyph version

// Three-dimensional Pythagoras tree
// First implementation ever, to my best knowledge :)
//
// Click anywhere to regenerate.
// Check http://en.wikipedia.org/wiki/Pythagoras_tree for more info on subject.

package {

	import flash.display.*
	import flash.events.*
	import flash.geom.ColorTransform;

	import alternativ5.engine3d.*
	import alternativ5.engine3d.controllers.*
	import alternativ5.engine3d.core.*
	import alternativ5.engine3d.display.*
	import alternativ5.engine3d.materials.*
	import alternativ5.engine3d.primitives.*
	import alternativ5.types.*

	use namespace alternativa3d;

	[SWF(width=465,height=465,frameRate=30,backgroundColor=0)]
	public class PythagorasTree3D extends Sprite {

		// tree elements (center positions, left and up vectors)
		// similar to http://wonderfl.kayac.com/code/18d2f77aeee90b0a3ae76dc5509a60f8bacad276
		private var atA:Point3D = new Point3D, ltA:Point3D = new Point3D, upA:Point3D = new Point3D;
		private var atB:Point3D = new Point3D, ltB:Point3D = new Point3D, upB:Point3D = new Point3D;
		private var atC:Point3D = new Point3D, ltC:Point3D = new Point3D, upC:Point3D = new Point3D;
		private var atD:Point3D = new Point3D, ltD:Point3D = new Point3D, upD:Point3D = new Point3D;
		private var ltE:Point3D = new Point3D (-1, 0, 0), upE:Point3D = new Point3D (0, -1, 0);
		private var matD:Matrix3D = new Matrix3D;
		private var matE:Matrix3D = new Matrix3D;
		private var sA:Number, sB:Number, sC:Number;
		private var rA:Point3D = new Point3D, rB:Point3D = new Point3D, rC:Point3D = new Point3D;
		private function calculateElements (c1:Number, c2:Number):void
		{
			// using c1 and c2 in 0..1 range, select such a, b, c and d that a^2 + b^2 + c^2 = d^2
			// this choise is almost completely free (we only need to make sure that d != 0)
			var a:Number = 0.1 + c1, c:Number = 1.1 - c1; // +
			var b:Number = ((c2 > 0.5) ? (c2 - 0.499999) : (c2 - 0.500001)) * 2; // + or -
			var d:Number = Math.sqrt (a * a + b * b + c * c); // +

			// define corresponding tree elements in some convenient frame
			// we are constrained by Pythagoras theorem, but orientation of planes is arbitrary
			// additionally, we want our tree to coincide with 2D one in boundary case of b = 0
			atA.x = +a/2; atA.y = 0; atA.z = -a/2;
			ltA.x = +a/2; ltA.y = 0; ltA.z =  0;
			upA.x =  0;   upA.y = 0; upA.z = -a/2;

			atC.x = -c/2; atC.y = b; atC.z = +c/2;
			ltC.x =  0;   ltC.y = 0; ltC.z = -c/2;
			upC.x = -c/2; upC.y = 0; upC.z =  0;

			atD.x = +a/2; atD.y = +b/2; atD.z = +c/2;
			ltD.x = +a/2; ltD.y = -b/2; ltD.z = -c/2;
			upD.x = -c; upD.y = 0; upD.z = -a; upD.normalize (); upD.multiply (ltD.length);
			atD.subtract (upD);

			ltB.x = 0; ltB.y = -b/2; ltB.z = 0;
			upB.copy (upD); upB.normalize (); upB.multiply (Math.abs (b/2));
			atB.copy (upB); atB.y = +b/2;

			// find transformation that aligns D element with 2x2 plane frame (E)
			// this 2x2 condition is there to use getRotations () method later
			var lxuD:Point3D = Point3D.cross (ltD, upD);
			lxuD.normalize (); lxuD.multiply (ltD.length);
			matD.a = -ltD.x; matD.e = -ltD.y; matD.i = -ltD.z;
			matD.b = -upD.x; matD.f = -upD.y; matD.j = -upD.z;
			matD.c = lxuD.x; matD.g = lxuD.y; matD.k = lxuD.z;

			var lxuE:Point3D = Point3D.cross (ltE, upE);
			matE.a = -ltE.x; matE.e = -ltE.y; matE.i = -ltE.z;
			matE.b = -upE.x; matE.f = -upE.y; matE.j = -upE.z;
			matE.c = lxuE.x; matE.g = lxuE.y; matE.k = lxuE.z;

			matD.invert (); matD.combine (matE);

			// transform A, B and C elements
			atA.subtract (atD); atA.transform (matD); ltA.transform (matD); upA.transform (matD);
			atB.subtract (atD); atB.transform (matD); ltB.transform (matD); upB.transform (matD);
			atC.subtract (atD); atC.transform (matD); ltC.transform (matD); upC.transform (matD);

			// calculate scales and normalize left/up vectors
			sA = ltA.length; ltA.normalize (); upA.normalize ();
			sB = ltB.length; ltB.normalize (); upB.normalize ();
			sC = ltC.length; ltC.normalize (); upC.normalize ();

			// finally, calculate corresponding rotations (re-using matE/lxuE variables)
			lxuE = Point3D.cross (ltA, upA);
			matE.a = -ltA.x; matE.e = -ltA.y; matE.i = -ltA.z;
			matE.b = -upA.x; matE.f = -upA.y; matE.j = -upA.z;
			matE.c = lxuE.x; matE.g = lxuE.y; matE.k = lxuE.z;
			matE.getRotations (rA);

			lxuE = Point3D.cross (ltB, upB);
			matE.a = -ltB.x; matE.e = -ltB.y; matE.i = -ltB.z;
			matE.b = -upB.x; matE.f = -upB.y; matE.j = -upB.z;
			matE.c = lxuE.x; matE.g = lxuE.y; matE.k = lxuE.z;
			matE.getRotations (rB);

			lxuE = Point3D.cross (ltC, upC);
			matE.a = -ltC.x; matE.e = -ltC.y; matE.i = -ltC.z;
			matE.b = -upC.x; matE.f = -upC.y; matE.j = -upC.z;
			matE.c = lxuE.x; matE.g = lxuE.y; matE.k = lxuE.z;
			matE.getRotations (rC);
		}

		private var planes:Array = [], planesDone:Array = [];
		private function step ():void {
			var pD:Plane = Plane (planes.shift ());
			if (pD == null) return;

			planesDone.push (pD);

			var scale:Number =
				pD.transformation.a * pD.transformation.a +
				pD.transformation.e * pD.transformation.e +
				pD.transformation.i * pD.transformation.i;
			if (scale < 0.001) return;


			var pA:Plane = createPlane ();
			pA.rotationX = rA.x;  pA.rotationY = rA.y;  pA.rotationZ = rA.z;
			pA.scaleX    = sA;    pA.scaleY    = sA;    pA.scaleZ    = sA;
			pA.x         = atA.x; pA.y         = atA.y; pA.z         = atA.z;
			pD.addChild (pA); planes.push (pA);

			var pB:Plane = createPlane ();
			pB.rotationX = rB.x;  pB.rotationY = rB.y;  pB.rotationZ = rB.z;
			pB.scaleX    = sB;    pB.scaleY    = sB;    pB.scaleZ    = sB;
			pB.x         = atB.x; pB.y         = atB.y; pB.z         = atB.z;
			pD.addChild (pB); planes.push (pB);

			var pC:Plane = createPlane ();
			pC.rotationX = rC.x;  pC.rotationY = rC.y;  pC.rotationZ = rC.z;
			pC.scaleX    = sC;    pC.scaleY    = sC;    pC.scaleZ    = sC;
			pC.x         = atC.x; pC.y         = atC.y; pC.z         = atC.z;
			pD.addChild (pC); planes.push (pC);
		}

		private function regenerate (c1:Number, c2:Number):void {
			var p:Plane;

			// delete all the planes
			for each (p in planesDone) destroyPlane (p);
			planesDone.length = 0; planes.length = 0;

			// create base plane
			p = createPlane (); planes.push (p); scene.root.addChild (p);

			// do the math
			calculateElements (c1, c2);

			// mark location
			loc.x = stage.stageWidth * c1;
			loc.y = stage.stageHeight * c2;
		}

		private function destroyPlane (p:Plane):void {
			// this is supposed to make alternativa trash GC-friendly
			if (p.parent != null) p.parent.removeChild (p);
			if (p.hasSurface ("back")) p.setMaterialToSurface (null, "back");
			if (p.hasSurface ("front")) p.setMaterialToSurface (null, "front");
			p.moveAllFacesToSurface (null, true);
		}

		private function createPlane ():Plane {
			var p:Plane = new Plane (2, 2);
			p.cloneMaterialToAllSurfaces (new FillMaterial (0x00FF));
			return p;
		}

		private function interpolateColor (fromColor:uint, toColor:uint, progress:Number):uint {
			var q:Number = 1-progress;
			var fromR:uint = (fromColor >> 16) & 0xFF;
			var fromG:uint = (fromColor >>  8) & 0xFF;
			var fromB:uint =  fromColor        & 0xFF;
			var toR:uint = (toColor >> 16) & 0xFF;
			var toG:uint = (toColor >>  8) & 0xFF;
			var toB:uint =  toColor        & 0xFF;
			var resultR:uint = fromR*q + toR*progress;
			var resultG:uint = fromG*q + toG*progress;
			var resultB:uint = fromB*q + toB*progress;
			var resultColor:uint = resultR << 16 | resultG << 8 | resultB;
			return resultColor;
		}

		private function lightPlane (p:Plane):void {
			var face:Face = p.faces.peek () as Face;
			var dot:Number = (face.globalNormal.x + face.globalNormal.y - face.globalNormal.z) / Math.sqrt (3);
			if (dot < -1) dot = -1; if (dot > 1) dot = 1;
			var lum:Number = 0.6 + 0.4 * Math.abs (dot);
			var color1:uint = 0x40302 * int (63 * lum); // yellow-ish
			var color2:uint = 0x20400 * int (63 * lum); // green-ish
			var m:Matrix3D = p.transformation;
			var prog:Number = Math.sqrt (m.a * m.a + m.e * m.e + m.i * m.i);
			var color:uint = interpolateColor (color2, color1, prog);
			var mat1:FillMaterial = FillMaterial (Surface (p.surfaces ["front"]).material); mat1.color = color;
			var mat2:FillMaterial = FillMaterial (Surface (p.surfaces ["back"]).material); mat2.color = color;
		}

		private var scene:Scene3D;
		private var tripod:Object3D;
		private var viewL:View;
		private var viewR:View;
		private var loc:Shape;

		public function PythagorasTree3D () {
			stage.quality = "best";

			scene = new Scene3D; scene.root = new Object3D;
			viewL = new View; viewL.camera = new Camera3D;
			viewL.camera.z = -6; viewL.camera.rotationX = -0.6; viewL.camera.y = -9;
			tripod = new Object3D; scene.root.addChild (tripod); tripod.addChild (viewL.camera);
			viewL.width = 500; viewL.height = 465; addChild (viewL); viewL.x = -26;
			viewL.transform.colorTransform = new ColorTransform (1, 0, 0);

			viewR = new View; viewR.camera = new Camera3D;
			viewR.camera.x = 0.8; viewL.camera.addChild (viewR.camera);
			viewR.width = 500; viewR.height = 465; addChild (viewR);
			viewR.transform.colorTransform = new ColorTransform (0, 0.9, 1);
			viewR.blendMode = "add";

			var s:Sprite = new Sprite; s.buttonMode = s.useHandCursor = true;
			s.graphics.beginFill (0, 0); s.graphics.drawRect (0, 0, 465, 465);
			addChild (s); s.addEventListener (MouseEvent.CLICK, onClick);

			addEventListener (Event.ENTER_FRAME, onEnterFrame);

			loc = new Shape;
			loc.graphics.lineStyle ();
			loc.graphics.beginFill (0xFFFFFF);
			loc.graphics.drawCircle (0, 0, 2);
			addChild (loc); loc.addEventListener (MouseEvent.CLICK, onClick);

			regenerate (0.5, 0.5);

			stage.addEventListener (KeyboardEvent.KEY_UP, onKeyUp);
		}

		private function onClick (e:MouseEvent):void {
			regenerate (mouseX / stage.stageWidth, mouseY / stage.stageHeight);
			if (e.shiftKey)
				trace ("\t\t\tnew Point ("
				+ (mouseX / stage.stageWidth).toFixed (5)
				+ ", "
				+ (mouseY / stage.stageHeight).toFixed (5)
				+ "),");
		}

		private function onEnterFrame (e:Event):void {
			// add 3 more planes
			var lastPlaneDone:int = planesDone.length;
			var grow:Boolean = (planesDone.length < 15000);
			if (grow) {
				for (var s:int = 0; s < 90; s++) step ();
			}
			scene.calculate ();
			// apply lighting after global normals were set
			if (grow || (lastPlaneDone < planesDone.length)) {
				for (var p:int = lastPlaneDone; p < planesDone.length; p++)
					lightPlane (planesDone [p]);
				for (var i:int = Math.max (0, planes.length - 9*3); i < planes.length; i++)
					lightPlane (planes [i]);
			}
		}

		private var dae:XML =
		<COLLADA version="1.4.0" xmlns="http://www.collada.org/2005/11/COLLADASchema">
			<asset>
				<unit meter="0.01" name="centimeter"/>
				<up_axis>Z_UP</up_axis>
			</asset>
			<library_geometries>
				<geometry id="Plane-Geometry" name="Plane-Geometry">
					<mesh>
						<source id="Plane-Geometry-Position">
							<float_array count="36" id="Plane-Geometry-Position-array">1.00000 1.00000 0.00000 1.00000 -1.00000 0.00000 -1.00000 -1.00000 0.00000 -1.00000 1.00000 0.00000</float_array>
							<technique_common>
								<accessor count="4" source="#Plane-Geometry-Position-array" stride="3">
									<param type="float" name="X"></param>
									<param type="float" name="Y"></param>
									<param type="float" name="Z"></param>
								</accessor>
							</technique_common>
						</source>
						<source id="Plane-Geometry-Normals">
							<float_array count="3" id="Plane-Geometry-Normals-array">0.00000 0.00000 1.00000</float_array>
							<technique_common>
								<accessor count="1" source="#Plane-Geometry-Normals-array" stride="3">
									<param type="float" name="X"></param>
									<param type="float" name="Y"></param>
									<param type="float" name="Z"></param>
								</accessor>
							</technique_common>
						</source>
						<vertices id="Plane-Geometry-Vertex">
							<input semantic="POSITION" source="#Plane-Geometry-Position"/>
						</vertices>
						<polygons count="1">
							<input offset="0" semantic="VERTEX" source="#Plane-Geometry-Vertex"/>
							<input offset="1" semantic="NORMAL" source="#Plane-Geometry-Normals"/>
							<p>0 0 1 0 2 0 3 0</p>
						</polygons>
					</mesh>
				</geometry>
			</library_geometries>
			<library_visual_scenes>
				<visual_scene id="Scene" name="Scene" />
			</library_visual_scenes>
			<scene>
				<instance_visual_scene url="#Scene"/>
			</scene>
		</COLLADA>;

		private function onKeyUp (e:KeyboardEvent):void {
			// export collada 1.4
			var scene:XML = <visual_scene id="Scene" name="Scene" />;
			for (var i:int = 0; i < planesDone.length; i++) {
				var pi:Plane = Plane (planesDone [i]);
				var mi:Matrix3D = pi.transformation;
				var ni:XML = new XML (
				"<node layer=\"L1\" id=\"Plane" + i + "\" name=\"Plane" + i + "\">" +
					"<matrix>" +
						mi.a + " " + mi.b + " " + mi.c + " " + mi.d + " " +
						mi.e + " " + mi.f + " " + mi.g + " " + mi.h + " " +
						mi.i + " " + mi.j + " " + mi.k + " " + mi.l + " " +
						"0.0 0.0 0.0 1.0" +
					"</matrix>" +
					"<instance_geometry url=\"#Plane-Geometry\"/>" +
				"</node>"
				);
				scene.appendChild (ni);
			}

			// dump to flash log
			dae.children() [2].setChildren (scene);
			trace ("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
			trace (dae.toString ());
		}
	}
}