Visualizing Papervision 3d with Flare

August 31st, 2008 § 3

So I've become quite interested in the Flare actionscript library for graphing and started playing around with it this weekend.  Using the example given here ::: Flare Dependency Graph , I wrote some perl that parses an actionscript source tree and creates a JSON file suitable for import into the Flare libraries.  So here is what Papervision looks like when graphed using Flare ::: 

Visualization of Papervision 3d  

 

 

Natural Object Rotation Using Papervision3D

December 27th, 2007 § 8

****NOTE :::: Click storming this cube will break its implementation :::: to see my point that aligns with this article use care, use one click at a time. :-)I've been getting familiar with the Papervision3D source tree lately and found myself messing around with the Cube.as class quite a bit. One of the first things I noticed about the manipulation of a cube with Papervision is that rotations are more difficult to predict than one would think. This stems from the notions of global and local axes; at first glance, I assumed that a papervision object would follow a rotation schema adhering to global axes, but the opposite is true; objects rotate via a local rotation schema. In less abstract terms the following applies:I render a cube whose axes are as follows:



Initial XYZ Orientation


I then proceed to rotate that cube about its y axis using the built-in yaw() method of the Cube class. My naivete initially led me to believe that subsequent rotation about the X axis would require the pitch() method of the Cube class. However since axes are local to a display object, the preceding yaw() method yielded a inversion of the Z and X axes. The follow diagram illustrates the transformation that has occurred:


XYZ Orientation after one transformation

So..... my project of presenting the user with a simple interface consisting of arrows allowing the rotation of a cube became a bit more difficult. I had to develop a method for tracking the perceived axes and the actual axes to which these perceived axes corresponded. After some thought and sketching I started to tinker with some methods to track cube axes via strings within an object. My object of course was named _objAxes, and its properties were x, y, and z.If we look at this object in its initial state we have the following :


Initial objAxes



and after the rotation about the y axis we have
objAxes after yaw() Transformation

Now for the sake of simulation, let us say that I now wish to rotate this cube along its X axis, as perceived by the camera. Initially I thought, "Ok, all I have to do is call the pitch() method of the Cube class and everything would be fine and dandy...." However, since the previous transformation of the Cube resulted inverted Z-X axes the pitch() method would result in the Cube rotating along its perceived Z axis instead of the X axis I intended. Herein lies the necessity of an axis tracking system.What I developed was a two-fold system for determining proper rotation of the object. The first is a decision making process for each type of rotation : Right, Left, Up, or Down. An event enters my Spin.as class and based on the event string sends it to the proper rotation class.The code is as follows:

 
public class SpinCube implements ISpin
{
	private var _objAxes 	: Object = { x : "x", y : "y", z : "z" };
	private var _cube 		: Cube ;
	private var _spr	 	: SpinRight;
	private var _spl	 	: SpinLeft;
	private var _spd	 	: SpinDown;
	private var _spu	 	: SpinUp;
	private var _cv 		: CheckVisibility;
 
	public function SpinCube( cube : Cube )
	{
		_cube 	= cube;
		_spr 	= new SpinRight( this );
		_spl = new SpinLeft ( this );
		_spd = new SpinDown( this );
		_spu = new SpinUp( this );
		_cv	 = new CheckVisibility( this );
	}
 
	public function spin( str : String ) : void
	{
		if( str == SpinGlobals.SPIN_RIGHT() )
		{
			_spr.spin();
		}
 
		else if ( str == SpinGlobals.SPIN_LEFT() )
		{
			_spl.spin();
		}
 
		else if ( str == SpinGlobals.SPIN_DOWN() )
		{
			_spd.spin();
		}
 
		else if ( str == SpinGlobals.SPIN_UP() )
		{
			_spu.spin();
		}
	}
 
public class SpinRight
{
 
	private var _ref 		: SpinCube;
	private var _cube 		: Cube;
	private var _objAxes 	: Object;
	private var _str	 	: String;
 
	public function SpinRight ( ref : SpinCube )
	{
		_ref 	= ref;
		_cube 	= _ref.getCube();
	}
 
	public function spin() : void
	{
		//GET the CURRENT rotation axes.
		_objAxes = _ref.getObjAxes();
		trace( " SpinCube ::: spin ( e ) :::: spinRight() running.");
 
		var rt:Timer = new Timer(1, 90 );
			rt.addEventListener( TimerEvent.TIMER, rightSpin );
			rt.addEventListener( TimerEvent.TIMER_COMPLETE	, rightSpinComplete );
			rt.start();
	}
 
	private function rightSpin( e : Event ) : void
	{
		if ( _objAxes[ "y" ] == "y")
		{
			_cube.yaw( 1 );
			_str = "yaw";
		}
 
		else if ( _objAxes[ "y" ] == "x" )
		{
			_cube.pitch( 1 );
			_str = "pitch";
		}
 
		else if ( _objAxes[ "y" ] == "z" )
		{
			_cube.roll( 1 );
			_str = "roll";
		}
	}
 
	private function rightSpinComplete( e : Event ) : void
	{
		_ref.processAxes(_str);
		trace("SPIN RIGHT:::: "+_str+" :::: AFTER ::::: ");
		_ref.dumpAxes();
	}
 


All that is going on here is the events emanating from the UI Arrow elements are being routed to their proper classes based on their .type property.So for the sake of simulation let us say the Spin class receives a SpinGlobals.SPIN_RIGHT() event the Spin class will call the spin() method on the _spr instance of the SpinRight class. The logic now gets interesting as we have to determine the local object axis of the perceived, from the camera point of view, Y axis.The SpinRight class reads as follows:

//PART 2
 
public class SpinRight
{
 
	private var _ref 		: SpinCube;
	private var _cube 		: Cube;
	private var _objAxes 	: Object;
	private var _str	 	: String;
 
	public function SpinRight ( ref : SpinCube )
	{
		_ref 	= ref;
		_cube 	= _ref.getCube();
	}
 
	public function spin() : void
	{
		//GET the CURRENT rotation axes.
		_objAxes = _ref.getObjAxes();
		trace( " SpinCube ::: spin ( e ) :::: spinRight() running.");
 
		var rt:Timer = new Timer(1, 90 );
			rt.addEventListener( TimerEvent.TIMER, rightSpin );
			rt.addEventListener( TimerEvent.TIMER_COMPLETE	, rightSpinComplete );
			rt.start();
	}
 
	private function rightSpin( e : Event ) : void
	{
		if ( _objAxes[ "y" ] == "y")
		{
			_cube.yaw( 1 );
			_str = "yaw";
		}
 
		else if ( _objAxes[ "y" ] == "x" )
		{
			_cube.pitch( 1 );
			_str = "pitch";
		}
 
		else if ( _objAxes[ "y" ] == "z" )
		{
			_cube.roll( 1 );
			_str = "roll";
		}
	}
 
	private function rightSpinComplete( e : Event ) : void
	{
		_ref.processAxes(_str);
		trace("SPIN RIGHT:::: "+_str+" :::: AFTER ::::: ");
		_ref.dumpAxes();
	}
 


Since a RightSpin Event has been passed along, the SpinRight class now has to determine the local Y axis of the object in order that it can call the proper method on the object. If the SpinRight class finds that the object's local Y axis value is "y" it calls the yaw() method on the object. However if it were to find the local Y axis to be "z", it would call the roll() method on the object and the object would rotate properly. Now, as this rotation occurs a temporary string is written indicating the method being used to perform the rotation on the object. This I realize could be optimized to only store the string once, but this is just a mockup of how to get natural rotation working. This string will be used when the TimerEvent.TIMER_COMPLETE fires the rightSpinComplete() function. What happens here is the string is sent back to the SpinCube class for processing to properly update the _objAxes values.The function that processes that string reads as follows:

  //part 3
 
	public function processAxes( _str : String ) : void
	{
		if ( _str == "yaw" )
		{
			trace("SPIN :::: yaw() :::: ");
			trace("SPIN :::: yaw() :::: BEFORE ::::: ");
			dumpAxes();
 
			if( _objAxes["y"] =="y" )
			{
				_objAxes = { y : _objAxes[ "y"], x : _objAxes[ "z" ] , z : _objAxes[ "x" ] };
			}
 
			else if ( _objAxes["x"] =="y" )
			{
				_objAxes = { y : _objAxes["z"], x : _objAxes["x"], z: _objAxes["y"] };
			}
 
			else if ( _objAxes["z"] == "y" )
			{
				_objAxes = { y : _objAxes["x"], x: _objAxes["y"], z : _objAxes["z"] };
			}
 
		}
 
		if ( _str == "pitch" )
		{
			trace("SPIN :::: pitch() :::: ");
			trace("SPIN :::: pitch() :::: BEFORE ::::: ");
			dumpAxes();
 
			if( _objAxes[ "x" ] == "x" )
			{
				_objAxes = { y : _objAxes[ "z" ] , x : _objAxes[ "x" ], z : _objAxes[ "y" ] };
			}
 
			else if ( _objAxes["y"] == "x" )
			{
				_objAxes = { y : _objAxes["y"], x : _objAxes["z"], z: _objAxes["x"] };
			}
 
			else if ( _objAxes["z"]== "x")
			{
				_objAxes = { y: _objAxes["x"], x: _objAxes["y"], z: _objAxes["z"] };
			}
 
		}
 
		if( _str == "roll" )
		{
			trace("SPIN :::: roll() :::: ");
			trace("SPIN :::: roll() :::: BEFORE ::::: ");
			dumpAxes();
 
			if( _objAxes[ "z" ] == "z" )
			{
				_objAxes = { y : _objAxes[ "x" ], x: _objAxes[ "y" ], z : _objAxes[ "z" ] };
			}
 
			else if(_objAxes[ "x" ] == "z" )
			{
				_objAxes = { y : _objAxes[ "z" ], x : _objAxes[ "x" ], z: _objAxes[ "y" ] };
			}
 
			else if ( _objAxes[ "y" ] == "z" )
			{
				_objAxes = { y : _objAxes[ "y" ], x : _objAxes[ "z" ], z: _objAxes["x"] };
			}
 
		}
 
trace("Cube After Transformation : " );
 
dumpAxes();
 
}


Since yaw() was called on the object and the Y axis was the local y axis before this transformation, the function substitutes _objAxes.x and _objAxes.z values. As far as I can tell through my testing, this processing should not fail to rewrite the object properly and perform all future transformations on the object as intended. Of course there are some caveats to this method of axis tracking; This will only work for rotations of 90 degress in any direction; for a cube this seems natural, but for other objects this may not be the case. Additionally the rotation tracking fails, at this point in time, to recognize the existence of a cube face whose image is upside-down. I am working on an algorithm to determine this state and parse accordingly, but at the current juncture it's not complete. Also I realize that the eventing in the swf example is a bit crude. It does not kill all events when the cube is in motion so therefore it is possible to click storm the cube and get strange rotations. But if you just use one click at a time to rotate my point should be illustrated. Thats about all for now. You can find full source for my spin classes attached to this post. If you have any comments or ideas for optimization of this technique drop me a line.

Sources :1. Natural Object Rotation2. The mailing list posts got me thinking about this problem : Papervision Mailing ListSource Code :Natural Object Rotation Classes.

Where Am I?

You are currently browsing the Papervision category at Meandering Thought.