Simple Visitor Pattern Example

Maybe someone will find it useful. More info.

package experiments.patterns
{
	import flash.display.Sprite;

	/**
	 * 	Visitor pattern (AS3)
	 * 	@author Alex Logashov
	 */
	public class VisitorPattern extends Sprite
	{

		public function VisitorPattern()
		{
			// traces: Name is "MyElement"
			// traces: Visiting: [object OtherElement]
			new VisitorDemo();
		}

	}
}

// Base visitor interface
internal interface IVisitor
{
	function visit(element:IElement):void;
}

// Base visitor interface
internal interface IElement
{
	function accept(visitor:IVisitor):void;
}

// Custom interface
internal interface IPrintName
{
	function getName():String;
}

// Custom object
internal class MyElement implements IElement, IPrintName
{
	public function getName():String
	{
		return "Name is "MyElement"";
	}

	public function accept(visitor:IVisitor):void
	{
		visitor.visit(this);
	}
}

// Custom object
internal class OtherElement implements IElement
{
	public function accept(visitor:IVisitor):void
	{
		visitor.visit(this);
	}
}

// Custor visitor
internal class NamePrinter implements IVisitor
{
	public function visit(element:IElement):void
	{
		if (element is IPrintName) {
			trace(IPrintName(element).getName());
		} else {
			trace("Visiting: " + String(element));
		}
	}
}

// Demo class
internal class VisitorDemo
{
	function VisitorDemo()
	{
		var el:MyElement = new MyElement;

		var np:NamePrinter = new NamePrinter;
		np.visit(el);

		// OR
		// el.accept(np);

		var oe:OtherElement = new OtherElement;
		oe.accept(np);
	}
}
Posted in ActionScript

Simple Abstract Factory Example

It’s just another boring Wednesday night. More info about Abstract Factory Pattern is here.

package experiments.patterns
{
	import flash.display.Sprite;

	/**
	 * 	Abstact class factory (AS3)
	 * 	@author Alex Logashov
	 */
	public class AbstractFactory extends Sprite
	{
		// Factory definition
		private var factory:IFactory;

		public function AbstractFactory()
		{
			// Define some global variable like through additional compiler options: -define=CONFIG::Type,"two")
			// var type:String = CONFIG::Type; 

			// Or just use local variable
			var type:String = "two";

			// Depending on type define factory instance
			switch(type) {
				case "one":
					factory = new Factory1;
					break;
				case "two":
					factory = new Factory2;
					break;
			}

			init();
		}

		private function init():void
		{
			// Displays green square
			var btn:IButton = factory.getButton();
			addChild(btn as Sprite);
		}

	}

}

import flash.display.Sprite;

// Facotry base product interface
internal interface IButton
{
	function draw():void;
}

// Base factory interface
internal interface IFactory
{
	function getButton():IButton;
}

// Factory object #1
internal class Factory1 implements IFactory
{
	public function getButton():IButton
	{
		return new Factory1Button();
	}
}

// Factory #1 product object (Red Button)
internal class Factory1Button extends Sprite implements IButton
{
	function Factory1Button()
	{
		draw();
	}

	public function draw():void
	{
		graphics.beginFill(0xff0000);
		graphics.drawRect(0, 0, 100, 100);
	}
}

// Factory object #2
internal class Factory2 implements IFactory
{
	public function getButton():IButton
	{
		return new Factory2Button();
	}
}

// Factory #2 product object (Green Button)
internal class Factory2Button extends Sprite implements IButton
{
	function Factory2Button()
	{
		draw();
	}

	public function draw():void
	{
		graphics.beginFill(0x00ff00);
		graphics.drawRect(0, 0, 100, 100);
	}
}
Posted in ActionScript

JavaScript Quicksort

I just thought I’d do one. Try it at jsFiddle

var out = document.getElementById("output");
var arr = [7, 5, 3, 1, 2, 0, 8, 9, 4, 6];

var i = 0;
var l = arr.length;

out.innerHTML = "Original: ";
for (; i > 1];
    while (l <= r) {
        while (array[l] 

p) r--; if (l left) quicksort(array, left, r); if (l < right) quicksort(array, l, right); } i = 0; l = arr.length; quicksort(arr, i, l - 1); out.innerHTML += "Sorted: "; for (; i < l; i++) out.innerHTML += (arr[i] + " ");

Posted in JavaScript

JavaScript Prototypes

One of the cool JavaScript features is prototype object which is part of every function created including Object() itself. So to save some typing you can do something like this (see complete example):

Object.prototype.setX = function(value) {
    this.style.left = value + "px";
}

Object.prototype.setY = function(value) {
    this.style.top = value + "px";
}

Object.prototype.setBG = function(value) {
    this.style.background = value;
}

var b = document.getElementById("box");
b.setBG("gray");
b.setY(20);
b.setX(100);

Basically you can save few extra characters when setting left, top or background properties. And you can delegated repetitive tasks, like typing “px” when declaring position plus avoid typing quotation marks when you do that. So that’s cool. Of course in JS you can also set “right” and “bottom” position and many other properties which can be abstracted through prototype object as well.

Of course everything that inherits modified Object (basically every object you create from this point) will inherit setX, setY and setBG functions. Consider the following code:

var func = function(){
    
}
var f = new func();
f.setX(10);

It will throw an error because style property which is part of HTMLElement object is undefined inside func object. To avoid errors you can override setX function. The following code won’t generate error:

<prevar func = function(){
this.setX = function(){}; // Option 1
}
func.prototype.setX = function(){}; // Option 2.
var f = new func();
f.setX(10);

To avoid overriding and errors altogether when dealing with specific object (div element in this case) you can do something like this:

HTMLDivElement.prototype.setX = function(value) {
    this.style.left = value + "px";
}

HTMLDivElement.prototype.setY = function(value) {
    this.style.top = value + "px";
}

HTMLDivElement.prototype.setBG = function(value) {
    this.style.background = value;
}

var b = document.getElementById("box");
b.setBG("gray");
b.setY(50);
b.setX(100);

In above code HTMLDivElement is used instead of Object. HTMLElement can be used as well since HTMLDivElement inherits from it.

You may also want to work with properties instead of methods. This can be achieved via getters and setters.

Object.defineProperties(HTMLDivElement.prototype, {
    "x": {
        get: function() {
             return parseInt(this.style.left);
        },
        set: function(value) {
             this.style.left = value + "px";
        }
    },
    
    "y": {
        get: function() {
            return parseInt(this.style.top);
        },
        set: function(value) {
             this.style.top = value + "px";
        }
    },
    
    "bg": {
        get: function() {
            return this.style.background
        },
        set: function(value) {
            this.style.background = value;    
        }
    },
    
    "width": {
        get: function() {
             return parseInt(this.style.width);   
        },
        set: function(value) {
            this.style.width = value + "px";
        }
    },
    
    "height": {
        get: function() {
             return parseInt(this.style.height);   
        },
        set: function(value) {
             this.style.height = value + "px";   
        }
    }
});


var b = document.createElement("div");
document.body.appendChild(b);

b.style.position = "relative";

b.bg = "gray";
b.width = 100;
b.height = 100;
b.x = 50;
b.y = 100;

console.log("background:", b.bg);
console.log("width:", b.width);
console.log("height:", b.height);
console.log("x:", b.x);
console.log("y:", b.y);

Above code will produce the same output as previous one, only this time methods are substituted by properties.

Posted in JavaScript

Bezier Curve Tracing

Movement can look a bit blocky (see this post). One of the ways to create smooth motion is to use Bezier curves. I am aware that there is PathAnimatior class inside Away3D library, but my approach is more generic and can be used with almost any 3D library.

PathMovement.as

package shiftarray.demos.away3d 
{
	import away3d.cameras.lenses.PerspectiveLens;
	import away3d.containers.ObjectContainer3D;
	import away3d.containers.View3D;
	import away3d.core.base.Object3D;
	import away3d.debug.AwayStats;
	import away3d.entities.Mesh;
	import away3d.entities.SegmentSet;
	import away3d.materials.ColorMaterial;
	import away3d.primitives.Cone;
	import away3d.primitives.data.Segment;
	import away3d.primitives.LineSegment;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Vector3D;
	
	/**
	 * ...
	 * @author Alex Logashov
	 */
	public class PathMotion extends View3D 
	{
		
		private var cameraController:HoverDragController;
		// Position zero
		private var origin:Vector3D;
		// Lines container
		private var lines:SegmentSet;
		// Array of generated points
		private var points:Vector.;
		// Array of generated bezier curves
		private var curves:Vector.;
		// Object to follow along the curve
		private var cone:ObjectContainer3D;
		// Used for animation (0 -> 1)
		private var tValue:Number = 0;
		
		public function PathMotion() 
		{
			init();
		}
		
		private function init():void 
		{
			backgroundColor = 0xffffff;
			addChild(new AwayStats(this));
			
			origin = new Vector3D;
			
			generatePoints();
			initLight();
			initCamera();
			init3D();
			
			if (stage) onStage(null);
			else 
				addEventListener(Event.ADDED_TO_STAGE, onStage);
		}
		
		private function generatePoints():void 
		{
			var i:int = 0, 
				l:int = 12, 
				xStep:Number = 100,  
				xOffset:Number = (l >> 1) * xStep * -1;
			points = new [];
			for (; i < l; i++)
				points[i] = new Vector3D(i * xStep + xOffset, Math.random() * 300 - 150, Math.random() * 200 - 100);
		}
		
		private function initLight():void 
		{
			
		}
		
		private function initCamera():void 
		{	
			camera.lens = new PerspectiveLens();
			camera.z = -500;
			camera.y = 500;
			camera.lookAt(origin);
		}
		
		private function init3D():void 
		{
			// Lines container
			lines = new SegmentSet;
			scene.addChild(lines);
			
			initCurves();
			drawCurve();
			
			// By default cone is Y-Axis oriented
			// To orient cone along Z-Axis, place it inside ObjectContainer3D and apply rotations
			cone = new ObjectContainer3D;
			var inner:Cone = new Cone(new ColorMaterial(0xff6600));
			inner.rotationZ = -90;
			inner.rotationX = 90;
			inner.scale(.25);
			cone.addChild(inner);
			
			scene.addChild(cone);
		}
		
		private function drawCurve():void 
		{
			var i:int = 0, l:int = curves.length;
			var ii:Number, ll:int = 1;
			var c:BezierCurve, p:Vector3D, pp:Vector3D, a:Mesh;
			for (; i < l; ++i) {
				c = curves[i];
				for (ii = 0; ii < ll; ii += .1) {
					p = c.getT(ii);
					if (!pp) pp = p.clone();
					lines.addSegment(new LineSegment(pp, p, 0x333333, 0x333333, 3));
					pp = p.clone();
				}
			}
		}
		
		private function initCurves():void 
		{
			curves = new Vector.;
			var p0:Vector3D, p1:Vector3D, p2:Vector3D = points[0].clone();
			var l:int = points.length - 1;
			var i:int = 1;
			var c:int;
			while (i < l) {
				p0 = p2.clone();
				p1 = points[i].clone();
				p2 = points[++i].clone();
				if (i !== l) {
					p2.x = (p1.x + p2.x) / 2;
					p2.y = (p1.y + p2.y) / 2;
					p2.z = (p1.z + p2.z) / 2;
				}
				curves[c++] = new BezierCurve(p0, p1, p2);
			}
		}
		
		public function positionOnCurve(t:Number, path:Vector., object:Object3D, align:Boolean = true):Vector3D
		{
			var i:int, 
				l:int = curves.length,
				curve:BezierCurve;
			
			// Total distance of all curves
			var totalDistance:Number = 0;
			
			// Calclulate total length
			for (; i < l; ++i) {
				curve = curves[i];
				totalDistance += curve.length;
			}
			
			// Distance of t argument
			var tDistance:Number = totalDistance * t;
			
			var ct:Number,
				v:Vector3D,
				c:Number = 0;
			
			// Calculate point
			for (i = 0; i = tDistance) {
					c = tDistance - c;
					// t value on curve relative to t of total distance
					ct = c / curve.length;
					// Get point on curve
					v = curve.getT(ct);
					// Position object
					object.position = v;
					// Optionally align object along the curve
					if (align) {
						var q:Vector3D = new Vector3D;
						q.x = curve.p1.x + ct * (curve.p2.x - curve.p1.x);
						q.y = curve.p1.y + ct * (curve.p2.y - curve.p1.y);
						q.z = curve.p1.z + ct * (curve.p2.z - curve.p1.z);
						
						object.lookAt(q);
					}
					
					break;
				}
				
				c += curve.length;
			}
			
			return v;
		}
		
	
		private function onStage(e:Event):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, onStage);
			stage.addEventListener(Event.RESIZE, resize);
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.frameRate = 60;
			
			cameraController = new HoverDragController(camera, stage);
			
			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			
			resize();
		}
		
		private function onEnterFrame(e:Event):void 
		{
			tValue = tValue > 1 ? 0 : tValue + .001;
			positionOnCurve(tValue, curves, cone, true);
			
			render();
		}
		
		private function resize(e:Event = null):void 
		{
			width = stage.stageWidth;
			height = stage.stageHeight;
		}
		
	}

}
import away3d.cameras.Camera3D;
import away3d.core.base.Object3D;
import away3d.entities.Mesh;
import away3d.materials.ColorMaterial;
import away3d.materials.MaterialBase;
import away3d.primitives.Cube;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Vector3D;

internal class BezierCurve
{
	
	private var _p0:Vector3D;
	private var _p1:Vector3D;
	private var _p2:Vector3D;
	
	public var length:Number;
	
	function BezierCurve(p0:Vector3D, p1:Vector3D, p2:Vector3D)
	{
		this._p0 = p0;
		this._p1 = p1;
		this._p2 = p2;
		
		length = BezierCurve.getBezierLength(this);
	}
	
	// http://segfaultlabs.com/docs/quadratic-bezier-curve-length
	static public function getBezierLength(bezier:BezierCurve):Number
	{
		var a:Vector3D = new Vector3D; 
		var b:Vector3D = new Vector3D;
		var c:Vector3D = new Vector3D;
		
		var p0:Vector3D = bezier.p0;
		var p1:Vector3D = bezier.p1;
		var p2:Vector3D = bezier.p2;
		
		a.x = p0.x - 2 * p1.x + p2.x;
		a.y = p0.y - 2 * p1.y + p2.y;
		a.z = p0.z - 2 * p1.z + p2.z;
		
		b.x = 2 * (p1.x - a.x);
		b.y = 2 * (p1.y - a.y);
		b.z = 2 * (p1.z - a.z);
		
		var A:Number = 4 * (a.x * a.x + a.y * a.y + a.z * a.z);
		
		if (A == 0) return 0;
		
		var B:Number = 4 * (a.x * b.x + a.y * b.y + a.z * b.z);
		var C:Number = b.x * b.x + b.y * b.y + b.z * b.z;
		
		var Sabc:Number = 2 * Math.sqrt(A + B + C);
		var A2:Number = Math.sqrt(A);
		var A32:Number = 2 * A * A2;
		var C2:Number = 2 * Math.sqrt(C);
		var BA:Number = B / A2;
		
		return (A32 * Sabc + A2 * B * (Sabc - C2) + (4 * C * A - B * B) * Math.log(2 * A2 +BA + Sabc) / (BA + C2)) / (4 * A32);
	}
	
	public function toString():String
	{
		return "[" + p0.toString() + ", " + p1.toString() + ", " + p2.toString() + "]";
	}
	
	public function getT(t:Number):Vector3D
	{
		var v:Vector3D = new Vector3D;
		
		v.x = _p0.x + t * (2 * (1 - t) * (_p1.x - _p0.x) + t * (_p2.x - _p0.x));
		v.y = _p0.y + t * (2 * (1 - t) * (_p1.y - _p0.y) + t * (_p2.y - _p0.y));
		v.z = _p0.z + t * (2 * (1 - t) * (_p1.z - _p0.z) + t * (_p2.z - _p0.z));
		
		return v;
	}
	
	public function get p2():Vector3D 
	{
		return _p2;
	}
	
	public function set p2(value:Vector3D):void 
	{
		_p2 = value;
		length = BezierCurve.getBezierLength(this);
	}
	
	public function get p1():Vector3D 
	{
		return _p1;
	}
	
	public function set p1(value:Vector3D):void 
	{
		_p1 = value;
		length = BezierCurve.getBezierLength(this);
	}
	
	public function get p0():Vector3D 
	{
		return _p0;
	}
	
	public function set p0(value:Vector3D):void 
	{
		_p0 = value;
		length = BezierCurve.getBezierLength(this);
	}
}

internal class Anchor
{
	static private var _mesh:Mesh;
	
	static public function clone():Mesh
	{
		return Mesh(mesh.clone());
	}
	
	static public function get mesh():Mesh 
	{
		if (!_mesh) {
			var material:MaterialBase = new ColorMaterial(0x0066ff);
			_mesh = new Cube(material, 10, 10, 10);
		}
		
		return _mesh;
	}
}

// Class is from away3d-examples-broomstick Github repository
// http://github.com/away3d/away3d-examples-broomstick
internal class HoverDragController
{
	private var _stage : Stage;
	private var _target : Vector3D;
	private var _camera : Camera3D;
	private var _radius : Number = 1000;
	private var _speed : Number = .005;
	private var _dragSmoothing : Number = .1;
	private var _drag : Boolean;
	private var _referenceX : Number = 0;
	private var _referenceY : Number = 0;
	private var _xRad : Number = 0;
	private var _yRad : Number = .5;
	private var _targetXRad : Number = 0;
	private var _targetYRad : Number = .5;
	private var _targetRadius : Number = 1000;

	/**
	 * Creates a HoverDragController object
	 * @param camera The camera to control
	 * @param stage The stage that will be receiving mouse events
	 */
	public function HoverDragController(camera : Camera3D, stage : Stage)
	{
		_stage = stage;
		_target = new Vector3D();
		_camera = camera;

		stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
		stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
		_stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
		stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
	}

	/**
	 * Amount of "lag" the camera has
	 */
	public function get dragSmoothing() : Number
	{
		return _dragSmoothing;
	}

	public function set dragSmoothing(value : Number) : void
	{
		_dragSmoothing = value;
	}

	/**
	 * The distance of the camera to the target
	 */
	public function get radius() : Number
	{
		return _targetRadius;
	}

	public function set radius(value : Number) : void
	{
		_targetRadius = value;
	}

	/**
	 * The amount by which the camera be moved relative to the mouse movement
	 */
	public function get speed() : Number
	{
		return _speed;
	}

	public function set speed(value : Number) : void
	{
		_speed = value;
	}

	/**
	 * Removes all listeners
	 */
	public function destroy() : void
	{
		_stage.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
		_stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
		_stage.removeEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
		_stage.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
	}

	/**
	 * The center of attention for the camera
	 */
	public function get target() : Vector3D
	{
		return _target;
	}

	public function set target(value : Vector3D) : void
	{
		_target = value;
	}

	/**
	 * Update cam movement towards its target position
	 */
	private function onEnterFrame(event : Event) : void
	{
		if (_drag) updateRotationTarget();

		_radius = _radius + (_targetRadius - _radius)*dragSmoothing;
		_xRad = _xRad + (_targetXRad - _xRad)*dragSmoothing;
		_yRad = _yRad + (_targetYRad - _yRad)*dragSmoothing;

		// simple spherical position based on spherical coordinates
		var cy : Number = Math.cos(_yRad)*_radius;
		_camera.x = _target.x - Math.sin(_xRad)*cy;
		_camera.y = _target.y - Math.sin(_yRad)*_radius;
		_camera.z = _target.z - Math.cos(_xRad)*cy;
		_camera.lookAt(_target);
	}

	/**
	 * If dragging, update the target position's spherical coordinates
	 */
	private function updateRotationTarget() : void
	{
		var mouseX : Number = _stage.mouseX;
		var mouseY : Number = _stage.mouseY;
		var dx : Number = mouseX - _referenceX;
		var dy : Number = mouseY - _referenceY;
		var bound : Number = Math.PI * .5 - .05;

		_referenceX = mouseX;
		_referenceY = mouseY;
		_targetXRad += dx * _speed;
		_targetYRad += dy * _speed;
		if (_targetYRad > bound) _targetYRad = bound;
		else if (_targetYRad < -bound) _targetYRad = -bound;
	}

	/**
	 * Start dragging
	 */
	private function onMouseDown(event : MouseEvent) : void
	{
		_drag = true;
		_referenceX = _stage.mouseX;
		_referenceY = _stage.mouseY;
	}

	/**
	 * Stop dragging
	 */
	private function onMouseUp(event : MouseEvent) : void
	{
		_drag = false;
	}

	/**
	 * Updates camera distance
	 */
	private function onMouseWheel(event:MouseEvent) : void
	{
		_targetRadius -= event.delta*5;
	}


}

I’ve commented the code to provide some description of the class. Few things to note:

  • By definition Quadratic Bezier curve must have the minimum of 3 points (2 anchors and 1 control point)
  • In this example curves are generated from points specified in the array
  • The only points used for anchors are first and last, the rest are control points
  • Code generates necessary anchor points between specified control points for you

The last one is pretty important to understand this demo. Bezier curve takes 3 points, so lets say you specify 4 points (p0, p1, p2, p3). So p0 and p3 are going to be the first and last anchors respectively. p1 and p2 are going to be control points and one more anchor point is going to be generated between p1 and p2 (for example q0). So the first curve is going to be (p0, p1, q0) and the second curve is going to be (q0, p2, p3). Hope it’s not too confusing.

To wrap it up I want to mention that Bezier curves are pretty easy once you understand them. In the next post I will show you show you can build Bezier curve without fancy formulas just using linear interpolation.

Posted in ActionScript

Ribbons Revisited

Decided to revisit my old blog post about ribbons in Away3D. This time using version 4.

Note: Using lights will throw runtime error, so no shading yet. Not sure if it’s my mistake, but I will look into that.

Flash Player 11 is required to view the demo. Demo is using eaze-tween for property transitions.

Ribbon.as

package shiftarray.away3d.primitives 
{
	import away3d.core.base.data.UV;
	import away3d.core.base.data.Vertex;
	import away3d.core.base.Geometry;
	import away3d.core.base.SubGeometry;
	import away3d.entities.Mesh;
	import away3d.materials.MaterialBase;
	import away3d.tools.FaceHelper;
	
	/**
	 * ...
	 * @author Alex Logashov
	 */
	
	public class Ribbon extends Mesh 
	{
		private var __geometry:Geometry;
		private var _material:MaterialBase;
		private var width:Number;
		private var numSegments:Number;
		
		private var v0:Vertex, 
					v1:Vertex, 
					v2:Vertex, 
					v3:Vertex;
		
		private var uv0:UV, 
					uv1:UV, 
					uv2:UV, 
					uv3:UV;
		
		private var tx:Number = 0, 
					ty:Number = 0, 
					tz:Number = 0;
		
		/**
		 * 
		 *	@param	material: Ribbon material
		 *	@param	width: Thickness of ribbon
		 *	@param	numSegments: Maximum number of triagles x2 (each segment contains 2 triangles)
		 */
		public function Ribbon(material:MaterialBase, width:Number = 50, numSegments:Number = 150) 
		{
			this._material = material;
			this.width = width;
			this.numSegments = numSegments * 6;
			
			init();
			
			super(_material, __geometry);
		}
		
		private function init():void 
		{
			var subGeometry:SubGeometry = new SubGeometry;
			subGeometry.autoDeriveVertexNormals = true;
			subGeometry.autoDeriveVertexTangents = true;
			subGeometry.updateVertexData(new []);
			subGeometry.updateIndexData(new []);
			subGeometry.updateUVData(new []);
			
			__geometry = new Geometry;
			__geometry.addSubGeometry(subGeometry);
			
			v0 = v2 = new Vertex;
			v1 = v3 = new Vertex;
			v2.x = -width >> 1;
			v3.x = width >> 1;
			
			uv0 = new UV(0, 1);
			uv1 = new UV(1, 1);
			uv2 = new UV(0, 0);
			uv3 = new UV(1, 0);
		}
		
		/**
		 *	Force mesh update 
		 */
		public function update():void
		{
			var il:int = geometry.subGeometries[0].indexData.length;
			if (il > numSegments) {
				FaceHelper.removeFace(this, 0, 0);
				FaceHelper.removeFace(this, 0, 0);
			}
			
			v0 = v2.clone();
			v1 = v3.clone();
			
			v2.x = tx - (width >> 1);
			v2.y = ty;
			v2.z = tz;
			
			v3.x = tx + (width >> 1);
			v3.y = ty;
			v3.z = tz;
			
			FaceHelper.addFace(this, v2, v1, v0, uv2, uv1, uv0, 0);
			FaceHelper.addFace(this, v2, v3, v1, uv2, uv3, uv1, 0);
		}
		
		/**
		 *	Extends existing triangles to specified position
		 *	@param	x
		 *	@param	y
		 *	@param	z
		 */
		public function lineTo(x:Number, y:Number, z:Number):void
		{
			tx = x;
			ty = y;
			tz = z;
			
			update();
		}
		
		override public function get x():Number { return tx; }
		override public function set x(value:Number):void 
		{ 
			tx = value; 
		}
		
		override public function get y():Number { return ty; }
		override public function set y(value:Number):void 
		{ 
			ty = value; 
		}
		
		override public function get z():Number { return tz; }
		override public function set z(value:Number):void 
		{ 
			tz = value; 
		}
		
	}

}

Ribbons.as (demo)

package shiftarray.demos.away3d 
{
	import away3d.containers.ObjectContainer3D;
	import away3d.debug.AwayStats;
	import away3d.filters.DepthOfFieldFilter3D;
	import away3d.materials.ColorMaterial;
	import aze.motion.eaze;
	import flash.display.StageQuality;
	import flash.events.Event;
	import flash.geom.Vector3D;
	import shiftarray.away3d.containers.BasicView3D;
	import shiftarray.away3d.primitives.Ribbon;
	
	/**
	 * ...
	 * @author Alex Logashov
	 */
	public class Ribbons extends BasicView3D 
	{
		
		// Limit ribbons movement to specified value for x, y and in 3D space
		private const BOUNDS:Number = 1000;
		private const COLORS:Array = [ 0x00ff44, 0x0099FF, 
									   0xffcc00, 0xff0000, 
									   0xd800ff, 0x00cc00,  
									   0xff6600, 0xff2e00, 
									   0x8000ff, 0x00d4ff];
		
		// Dummy objects used for changing ribbons positions in 3D space
		private var leads:Vector.;
		private var ribbons:Vector.;
		
		override protected function init():void
		{
			addChild(new AwayStats(this));
			backgroundColor = 0xf7f7f7;
			super.init();
		}
		
		override protected function initCamera():void
		{
			super.initCamera();
			camera.lens.far = 7000;
		}
		
		override protected function init3D():void
		{
			leads = new Vector.;
			ribbons = new Vector.;
			
			var lead:Vector3D; 
			var ribbon:Ribbon;
			var	material:ColorMaterial;
			
			var i:int, l:int = COLORS.length; 
			for (; i < l; ++i) {
				material = new ColorMaterial(COLORS[i], .85);
				material.bothSides = true;
				
				ribbon = new Ribbon(material);
				ribbon.moveTo(randomWithinBounds, randomWithinBounds, randomWithinBounds);
				ribbons.push(ribbon);
				scene.addChild(ribbon);
				
				lead = new Vector3D;
				leads.push(lead);
				
				tween(lead);
			}
		}
		
		private function tween(v:Vector3D):void 
		{
			var t:Number = random(.5, 5);
			eaze(v).to(t, {  x:[[v.x, randomWithinBounds, randomWithinBounds, randomWithinBounds]]
							,y:[[v.y, randomWithinBounds, randomWithinBounds, randomWithinBounds]]
							,z:[[v.z, randomWithinBounds, randomWithinBounds, randomWithinBounds]] } )
				   .onComplete(tween, v);
		}
		
		override protected function onStage(e:Event):void
		{
			super.onStage(null);
			
			stage.quality = StageQuality.LOW;
		}
		
		override protected function onEnterFrame(e:Event):void
		{
			var i:int, l:int = ribbons.length;
			for (; i < l; ++i) {
				
				// Ribbons will line to their associated lead object
				ribbons[i].x += (leads[i].x - ribbons[i].x) / 25;
				ribbons[i].y += (leads[i].y - ribbons[i].y) / 25;
				ribbons[i].z += (leads[i].z - ribbons[i].z) / 25;
				
				ribbons[i].update()
			}
			
			super.onEnterFrame(e);
		}
		
		private function random(min:Number, max:Number):Number
		{
			return min + (max - min) * Math.random();
		}
		
		private function get randomWithinBounds():Number
		{
			return random( -BOUNDS, BOUNDS);
		}
		
	}

}

BasicView3D

package shiftarray.away3d.containers 
{
	import away3d.containers.ObjectContainer3D;
	import away3d.containers.View3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Vector3D;
	
	/**
	 * ...
	 * @author Alex Logashov
	 */
	public class BasicView3D extends View3D 
	{
		
		protected var origin:Vector3D = new Vector3D;
		
		private var autoStart:Boolean;
		
		private var __width:Number;
		private var __height:Number;
		
		public function BasicView3D(width:Number = 0, height:Number = 0, autoStart:Boolean = true) 
		{
			this.autoStart = autoStart;
			this.__width = width;
			this.__height = height;
			
			init();
		}
		
		protected function init():void 
		{
			initLights();
			initCamera();
			init3D();
			
			if (stage) onStage(null);
			else
				addEventListener(Event.ADDED_TO_STAGE, onStage);
		}
		
		protected function initLights():void { }
		
		protected function initCamera():void 
		{
			camera.z = -1000;
			camera.lookAt(origin);
		}
		
		protected function init3D():void { }
		
		protected function onStage(e:Event):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, onStage);
			stage.addEventListener(Event.RESIZE, resize);
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.frameRate = 60;
			
			if (autoStart) startRendering();
			
			resize();
		}
		
		protected function onEnterFrame(e:Event):void 
		{
			singleRender();
		}
		
		protected function resize(e:Event = null):void 
		{
			width = __width == 0 ? stage.stageWidth : __width;
			height = __height == 0 ? stage.stageHeight : __height;
		}
		
		public function singleRender():void
		{
			render();
		}
		
		public function startRendering():void
		{
			addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
		}
		
		public function stopRendering():void
		{
			removeEventListener(Event.ENTER_FRAME, onEnterFrame);
		}
		
		public function addObject(object:ObjectContainer3D):ObjectContainer3D
		{
			return scene.addChild(object);
		}
		
	}

}
Posted in ActionScript | 6 Comments

Away3D with Box2D Physics

In this demo I’m combining 3D rendering with 2D physics. Away3D is used for rendering and WCK – Box2D alchemy port is used for physics. Mouse joints are enabled, so it’s possible to drag objects around.

Flash Player 11 is required to view the demo.

package shiftarray.demos.away3d 
{
	import away3d.cameras.lenses.PerspectiveLens;
	import away3d.containers.View3D;
	import away3d.debug.AwayStats;
	import away3d.entities.Mesh;
	import away3d.lights.DirectionalLight;
	import away3d.materials.ColorMaterial;
	import away3d.materials.methods.HardShadowMapMethod;
	import away3d.primitives.Cube;
	import away3d.primitives.Plane;
	import away3d.primitives.Sphere;
	import Box2DAS.Common.b2Def;
	import Box2DAS.Common.V2;
	import Box2DAS.Dynamics.b2Body;
	import Box2DAS.Dynamics.b2Fixture;
	import Box2DAS.Dynamics.b2World;
	import Box2DAS.Dynamics.Joints.b2MouseJoint;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	
	/**
	 * ...
	 * @author Alex Logashov
	 */
	
	public class Physics2D extends View3D 
	{
		
		private const WORLD_SCALE:Number = 30;
		private const RAD2DEG:Number = (180 / Math.PI);
		
		private var world:b2World;
		private var mouseJoint:b2MouseJoint;
		
		private var mouseDown:Boolean = false;
		private var origin:Vector3D;
		private var projectionPoint:Point;
		private var light:DirectionalLight;
		private var sphere:Sphere;
		private var cube:Cube;
		
		public function Physics2D() 
		{
			init();
		}
		
		private function init():void 
		{
			backgroundColor = 0xffffff;
			
			addChild(new AwayStats(this));
			
			origin = new Vector3D;
			projectionPoint = new Point;
			
			initLights();
			initCamera();
			init3D();
			initPhysics();
			
			if (stage) onStage(null);
			else
				addEventListener(Event.ADDED_TO_STAGE, onStage);
				
		}
		
		private function initLights():void 
		{
			light = new DirectionalLight;
			light.direction = new Vector3D(0, -.985, .174)
			scene.addChild(light);
		}
		
		private function initCamera():void 
		{
			camera.lens = new PerspectiveLens(45);
			camera.z = -1500;
			camera.y = 750;
			camera.lookAt(origin);
		}
		
		private function init3D():void 
		{
			var fm:ColorMaterial = new ColorMaterial(0xffffff);
			fm.shadowMethod = new HardShadowMapMethod(light);
			fm.ambient =  .50; 
			fm.specular = .75;
			fm.lights = [light];
			
			var f:Plane = new Plane(fm, 4000, 500, 1, 1, false);
			f.castsShadows = true;
			scene.addChild(f);
			
			var mm:ColorMaterial = new ColorMaterial(0x0099ff);
			mm.shadowMethod = new HardShadowMapMethod(light);
			mm.ambient =  .25; 
			mm.specular = .25;
			mm.lights = [light];
			
			sphere = new Sphere(mm);
			sphere.castsShadows = true;
			
			cube = new Cube(mm);
			cube.castsShadows = true;
		}
		
		private function initPhysics():void 
		{
			world = new b2World(new V2(0.0, -10.0), true);
			b2Def.initialize();
			
			b2Def.polygon.SetAsBox(2000 / WORLD_SCALE, 20 / WORLD_SCALE, new V2(0, -20 / WORLD_SCALE));
			b2Def.polygon.create(world.m_groundBody);
			
			var mesh:Mesh;
			var scale:Number;
			b2Def.body.type = b2Body.b2_dynamicBody;
			for (var i:int; i  .5) {
					mesh = Mesh(sphere.clone());
					mesh.scale(scale);
					b2Def.body.userData = mesh;
					b2Def.circle.m_radius = scale * 50 / WORLD_SCALE;
					b2Def.fixture.shape = b2Def.circle;
				} else {
					mesh = Mesh(cube.clone());
					mesh.scale(scale);
					b2Def.body.userData = mesh;
					b2Def.polygon.SetAsBox(scale * 50 / WORLD_SCALE, scale * 50 / WORLD_SCALE);
					b2Def.fixture.shape = b2Def.polygon;
				}
				
				b2Def.fixture.density = 1;
				var b:b2Body = b2Def.body.create(world);
				b2Def.fixture.create(b);
				scene.addChild(mesh);
			}
		}
		
		private function updateMouseJoint():void 
		{
			var p:V2 = new V2(projectionPoint.x / WORLD_SCALE, projectionPoint.y / WORLD_SCALE);
			var b:b2Body;
			if (mouseDown && mouseJoint == null) {
				b = getBodyAtPoint(p);
				if(b) {
					b2Def.mouseJoint.bodyA = world.m_groundBody;
					b2Def.mouseJoint.bodyB = b;
					b2Def.mouseJoint.target.v2 = p;
					b2Def.mouseJoint.collideConnected = true;
					b2Def.mouseJoint.maxForce = 500 * b.GetMass();
					mouseJoint = world.CreateJoint(b2Def.mouseJoint) as b2MouseJoint;
					b.SetAwake(true);
				}
			} else if (!mouseDown && mouseJoint != null) {
				world.DestroyJoint(mouseJoint);
				mouseJoint = null;
			} else if (mouseJoint) mouseJoint.SetTarget(p);
		}
		
		public function getBodyAtPoint(point:V2):b2Body {
			var body:b2Body = null;
			
			world.QueryPoint(function(f:b2Fixture):Boolean {
				var b:b2Body = f.GetBody();
				if(b.IsDynamic()) {
					body = b;
					return false;
				}
				return true; }, point);
			
			return body;
		}
		
		private function projectMousePosition():void 
		{
			var mv:Vector3D = unproject(stage.mouseX, stage.mouseY);
			var cp:Vector3D = camera.position;
			var dm: Number = mv.z;
			var dc: Number = cp.z;
	
			var d:Number = dm / ( dm - dc );
			
			var v:Vector3D = new Vector3D;
			v.x = mv.x + ( cp.x - mv.x ) * d;
			v.y = mv.y + ( cp.y - mv.y ) * d;
			
			projectionPoint.x = v.x;
			projectionPoint.y = v.y;
			
		}
		
		private function random(min:Number, max:Number):Number
		{
			return min + (max - min) * Math.random();
		}
		
		private function onStageMouseDown(e:MouseEvent):void 
		{
			mouseDown = true;
			stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown);
			stage.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUp);
		}
		
		private function onStageMouseUp(e:MouseEvent):void 
		{
			mouseDown = false;
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown);
			stage.removeEventListener(MouseEvent.MOUSE_UP, onStageMouseUp);
		}
		
		private function onStage(e:Event):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, onStage);
			stage.addEventListener(Event.RESIZE, resize);
			
			stage.addEventListener(MouseEvent.MOUSE_DOWN, onStageMouseDown);
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.frameRate = 60;
			
			addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
			
			resize();
		}
		
		private function onEnterFrame(e:Event):void 
		{
			
			var bb:b2Body = world.GetBodyList();
			while (bb) {
				
				if (bb.GetUserData() is Mesh) {
					var mesh:Mesh = bb.GetUserData() as Mesh;
					mesh.x = bb.GetPosition().x * WORLD_SCALE;
					mesh.y = bb.GetPosition().y * WORLD_SCALE;
					mesh.rotationZ = bb.GetAngle() * RAD2DEG;
					
					if (mesh.y < -1500)
						bb.SetTransform(new V2( random( -250 / WORLD_SCALE, 250 / WORLD_SCALE), 
												random( 500 / WORLD_SCALE, 1500 / WORLD_SCALE)), 
										bb.GetTransform().angle);
				}
				
				bb = bb.GetNext();
			}
			
			
			projectMousePosition();
			updateMouseJoint();
			
			world.Step(1 / 60, 10, 10);
			render();
		}
		
		private function resize(e:Event = null):void 
		{
			width = stage.stageWidth;
			height = stage.stageHeight;
		}
		
	}

}
Posted in ActionScript | 6 Comments