Author Archive

Blender 2.5 alpha 2 – Collada export bug

Saturday, March 27th, 2010

Blender 2.5 is a great application to create 3D stuff. Best of all: its free!

Even better: Blender 2.5 alpha 2 seems to have a much enhanced Collada importer/exporter. Blender versions before 2.5 used a python script for import and export. That script was okay, but failed on more complex scenes using rigs / animations. I believe Blender now uses Collada DOM or OpenCOLLADA.

But after trying the Collada export I found a bug in Blender 2.5 alpha 2 r27226 which I filed in Blender’s bugtracker here (you need an account there). Seems export of “translation” goes sour.

@collada asked me to describe the bug in public, so here is the bugreport I filed:


Blender 2.5 alpha 2 - 2.52.0 - r27226
Mac OSX 10.5.8

The .blend is a simple cube animation with 2 keys, translation and rotation only.

A typical exported translation channel looks like this (see attached collada):
<channel source="#Cube_location_Y-sampler" target="Cube/location"/>

According to the Collada 1.4.1 spec the @target atrribute here says:the locY-sampler targets *all* values of the element
with @sid="location".
In this case however we need to target only *one* value.
See "Address syntax" in Chapter 3 of the Collada 1.4.1 spec.

The targeted scene graph node looks like this (see attached collada)
<node id="Cube" type="NODE">
<translate sid="location">6.423927 -0.08766067028 1.473982</translate>
[...]

A channel targets a "sid" => see the @sid attribute on the node's translation element.

the @target property of the channel should be like:
Cube/location.X
Cube/location.Y
Cube/location.Z
or
Cube/location(0)
Cube/location(1)
Cube/location(2)

When only specifying Cube.location importers can't determine which value to target of the translation element (x, y or z).

I should note that target=”Cube/location” IS valid in theory (when the channel’s sampler specifies triplets of values). But in the case described the target is not valid afaik.

Here are the files: blender25_collada_bug

UPDATE : the bug has been fixed. Thank you Arystanbek Dyussenov! Guess you have to compile Blender 2.5 from source until the bug fix makes it into a release.

Introducing js3ds – a Javascript parser for .3ds

Wednesday, January 27th, 2010

Came home very tired today and saw this tweet by @Sirokos. Now, that of course was a challenge!

In the near future I’m planning to do some experiments with WebGL and Javascript. Of course I need to be able to load some cool 3DS models then. So… started coding an hour ago and presto! Basics are done within the hour! I admit: I’ve done a Actionscript version some years ago (coded it for Papervision).

The javascript version is very basic still: only meshes (vertices, faces and uv’s) for now. Materials etc. will follow if I find time.

Again: have fun!

Introducing ASBlender

Monday, January 25th, 2010

As all 3D modelers probably know: exporting models from 3D applications and use them in Flash (Papervision3D etc.) can be frustrating. Stuff like animations, matrices, etc. sometimes don’t work as expected. So I thought: why not read the native file format of a 3D app instead? That way we get access to the native data as used by a 3D app. Not some ‘twisted’ data as presented by exporters.

Don’t get me wrong: most exporters of course do their job very well, but… exporters ‘interpret’. In other words: exporters think in my place. Often exporter are ‘right’, but sometimes they go wrong (or present data in some awkward form). Then it would be cool to have control over the data yourself.

The workflow becomes very simple. We don’t have to export anymore, Flash could simply read the 3D app’s latest saved file. We can interpret the 3D app’s data as we wish. I like that ;-) . On the bad side: native files contains lots of data we probably don’t need, like the 3D app’s viewport size etc. Then again: how cool is that?

Enter ASBlender, a library I slapped together in a few days to read Blender‘s .blend file format and parse it to AS3. Blender is a very cool (open source, free!) 3D application which can compete with MAX, Maya, Cinema4D etc. The Blender UI is kind of weird when you first see it, but after a – admittedly – steep learning curve, it rocks.

Blender 2.5 - Eclipse - FDT

More info, code and a wiki is available at github.

The code is very basic and can be improved for sure, so comments, criticism and suggestions are more then welcome.

Have fun!

Papervision3D 2.1 – alpha

Tuesday, May 26th, 2009

Just committed rev. 911 to the Papervision3D SVN trunk. Rev. 911 and upwards will become Papervision3D 2.1 because the changes made are quite big.
Major changes where made to the DAE, MD2 and animation classes.

NOTE:
This revision is considerably different then previous revisions. Use with care!
At this point its not advised to use rev. 911 for production.

This revision fixes several issues regarding the DAE class:

  1. vertex-animation
  2. nested animations
  3. Cinema4D support
  4. morph-weight animation
  5. splines
  6. cloning
  7. play(), play(“clipName”), stop(), pause(), resume()
  8. more…

The whole org.papervision3d.core.animation.* package has been revamped completely to allow for the changes in the DAE class.

DAE Example:

var autoPlay : Boolean = false; // don't play animations automatically
 
var dae : DAE = new DAE( autoPlay, "myCollada" );
 
dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, onDaeComplete);
dae.addEventListener(FileLoadEvent.LOAD_PROGRESS, onDaeLoadProgress);
 
// optionally pass materials to DAE
// NOTE: here's a change with previous revs :
// 1. lookup the <material> elements in the COLLADA file (inside <library_materials>).
// 2. write down / remember the @id attribute of the <material> element.
// 3. materials.addMaterial( myMaterial, materialElementID ).
// ==> this will probably change in future revs
var materials : MaterialsList = new MaterialsList();
 
// If textures fail to load optionally add some search-paths 
// (relative to the swf):
dae.addFileSearchPath( "images" );
dae.addFileSearchPath( "textures" );
 
// set to true if you get a script-timeout error
var asyncParsing : Boolean = false;
 
// load it!
dae.load( "/path/to/dae", materials, asyncParsing );
 
/**
 * The DAE has loaded completely
 */
private function onDaeComplete(event : FileLoadEvent) : void
{
     var dae : DAE = event.target as DAE;
 
     // add to scene
     scene.addChild( dae );
 
     // start playing animation (if any available)
     // other animation controls include :
     // 1. play( "clipName ")
     // 2. stop()
     // 3. pause()
     // 4. resume()
     // 5. playing (getter: bool indicating if playing)
     dae.play();
 
     // lets create a clone
     // NOTE: DAE#clone() is somewhat bugged still, 
     // but seems to work in most cases
     var clone : DAE = dae.clone() as DAE;
 
     // add clone to scene
     scene.addChild( clone );
 
     // move it a bit
     clone.x = 200;
}
 
private function onDaeLoadProgress(event : FileLoadEvent) : void
{
 
}

MD2 Example:

var md2 : MD2 = new MD2();
 
var material : MaterialObject3D = new WireframeMaterial();
 
md2.addEventListener(FileLoadEvent.LOAD_COMPLETE, onMD2Complete);
 
md2.load("/path/to/md2", material);
 
scene.addChild(md2);
 
private function onMD2Complete(event : FileLoadEvent) : void
{
       var md2 : MD2 = event.target as MD2;
 
       md2.play();
       // or play some clip :
       // md2.play( "run" )
}

Animation:

Click the image below to show an example of the new animation controls.
Animation Test
Download the source of this example here.

The DAE and MD2 class implement IAnimatable, IAnimationProvider and IControllerProvider, which can be found in the org.papervision3d.core.animation.* package.

As so much has changed I’m sure some bugs are introduced. Please let me know!

PS: I’ll be on vacation until june 8th, so its unlikely I’ll be able to fix any bugs before that time.
PS2: Many people have helped by submitting code-snips, reporting bugs etc. I’ll credit you all when I’m back :-)

Unproject with useProjectionMatrix = true

Wednesday, April 29th, 2009

I just updated Papervision3D to allow the CameraObject3D#unproject method to work when CameraObject3D#useProjectionMatrix = true.

Papervision3D has two methods of ‘projecting’ vectors onto the screen:

  1. useProjectionMatrix = false, this is the default ‘fast’ method
  2. useProjectionMatrix = true, this is the method where projection is done by a matrix

Note that Papervision3D switches to method #2 in ‘ortho mode’.

So what is the difference exactly between these two kinds of projection? First we need to define what projection is. Projection is what adds ‘perspective’ to a scene and makes sure your scene fits to the viewport. Its the last step in the so-called ‘render pipeline’.

We can derive the ‘fore shortening’ effect of perspective in several ways. Method #1 is simple, effective and fast. It uses the camera’s focus and zoom values. Method #2 uses a more classic way of projection : it uses a dedicated matrix. Tech buffs: very much like OpenGL’s gluPerspective.

Andy Zupko has a great post on unproject using method #1.

We will discuss unproject using method #2, which works slightly different.

The difference is that when #useProjectionMatrix is set to true, then CameraObject3D#unproject returns a point in world-space, rather then a ray. Let me explain with some code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// we want to use a projection matrix
camera.useProjectionMatrix = true;
 
// '0' indicates we want a point on the near plane
var pointOnNearPlane : Number3D = camera.unproject( screenX, screenY, 0 );
 
// '1' indicates we want a point on the far plane
var pointOnFarPlane : Number3D = camera.unproject( screenX, screenY, 1 );
 
// Construct the ray's direction
var dir : Number3D = Number3D.sub( pointOnFarPlane, pointOnNearPlane );
 
// Normalize
dir.normalize();
 
// So, now you have a ray with its origin at 'pointOnNearPlane',
// and direction 'dir'

Optimization: could save some cycles by doing (*not* in ortho mode!, see below):

1
2
3
4
5
6
7
var camPosition : Number3D = new Number3D( camera.x, camera.y, camera.z );
// Construct the ray's direction
var dir : Number3D = Number3D.sub( pointOnFarPlane, camPosition );
// Normalize
dir.normalize();
// So, now you have a ray with its origin at the camera's position
// and direction 'dir'

Why can’t we use above optimization in ortho mode?
In perspective mode all ‘rays’ originate from the camera’s position: the ‘view cone’ has shape of a pyramid. Hence we can assume ‘pointOnNearPlane’ to coincide with the camera’s position.
In ortho mode however the ‘view cone’ has the shape of a cube, all rays are parallel. Hence we need to unproject *two* points to construct our ray.

Now simply follow Andy Zupko ‘s post to drag objects around.
Or perform ‘picking’, or…

Update:
Created a little demo to visualize what I’m ranting about, check it out here

Floorplanner API, ASP.NET and Authorization

Saturday, March 14th, 2009

For those struggling to (Basic) authenticate (as I have been…) with the Floorplanner API using ASP.NET, here’s a snip to get you going:

VB.NET:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
' This snip shows how to add a new user, 
' see floorplanner.com/api for details
Dim uri As Uri = new Uri(ADD_USER_URL)
Dim request as HttpWebRequest =  CType(WebRequest.Create(uri),HttpWebRequest)
 
' Build XML for the POST body (in this case from a custom User class)
Dim xmlStr As String = User.ToXmlString()
 
' Grab the bytes
Dim xmlBytes As Byte() = Encoding.UTF8.GetBytes(xmlStr)
 
request.Method = "POST"       
request.ContentLength = xmlBytes.Length
request.ContentType = "application/xml"
 
' Basic Authorization
' Make sure PreAuthenticate is set to True, else the 
' Authorization header will *not* be sent, and an error will be thrown
request.Credentials = New NetworkCredential(API_KEY, API_PASS)
request.PreAuthenticate = True
 
' Alternatively use a more old-fashioned method
' Dim basicAuth As String = Convert.ToBase64String(Encoding.ASCII.GetBytes(API_KEY & ":" & API_PASS))
' request.Headers.Add("Authorization", "Basic " & basicAuth)
 
' Add our payload
Dim reqStream As Stream = request.GetRequestStream
reqStream.Write(xmlBytes, 0, xmlBytes.Length)
reqStream.Close
 
Try
     Dim resp As HttpWebResponse = CType(request.GetResponse(),HttpWebResponse)
     Dim reader As StreamReader = New StreamReader(resp.GetResponseStream)
     ' do something with the result XML string
     Dim userXml As String = reader.ReadToEnd
Catch e As WebException
     ' handle the error
End Try

Papervision3D forum

Tuesday, January 20th, 2009

On request of many: Papervision3D now has a forum : http://forum.papervision3d.org/

Read more at the Papervision3D blog.

Papervision3D wins Innovation Of The Year award

Tuesday, December 9th, 2008

Papervision3D has won the INNOVATION OF THE YEAR in this year’s .net Awards!
Other nominees included Google Android and App Engine, Microsoft Telescope, Open Social and Silverlight 2. I’m very proud to be part of the Papervision3D team!

dotnet innovation award 2008

Read more on the Papervision3D blog.

Alchemy – first looks

Sunday, November 23rd, 2008

Adobe has recently released a preview version of Alchemy. From their site:

Welcome the preview release of codename “Alchemy.” Alchemy is a research project that allows users to compile C and C++ code that is targeted to run on the open source ActionScript Virtual Machine (AVM2). The purpose of this preview is to assess the level of community interest in reusing existing C and C++ libraries in Web applications that run on Adobe® Flash® Player and Adobe AIR®.

So, what does this mean? This means that we can use existing C/C++ code and compile that down to AS3. Initially I though that this implied a hefty increase in code execution speed, but as all is compiled down to AS3 this is not true in most cases. Code will only run faster if you ‘stay’ on the C-side and only return to ‘AS3′ when your C code is done processing. The reason is that AS3 method-calls are sloooow (params need to be ‘unboxed’ etc.)! When in C this slowness doesn’t occur and hence execution speed will be faster (Adobe claims a potential speed increase by a factor 2 to 10 I beleive). Wow! That of course made me wonder whether Alchemy would be usefull for 3D engines like Papervision3D, more soon! :-)

Branden Hall did a nice writeup on Alchemy explaining above better then me.

I couldn’t resist myself and started off immediately with something I always wanted to code for our 3D engine: Polygon Triangulation with support for holes.

I installed the Alchemy Toolkit, got the Flex 3.2 SDK and got some C code from the Department of Computer Science, UNC Chapel Hill.

Setting up the toolkit was bit tricky, but with help from this page I finally succeeded to get my environment right (OSX). Then I hit ‘make’ and presto! Got my swc! Download the swc and sample code or view a live example.

Some notes on swc usage:
1] ‘outer’ polygons must be defined anti-clockwise
2] ‘inner’ polygons (holes) must be defined clock-wise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// import
import cmodule.triangulation.CLibInit;
 
// initialize
var loader:CLibInit = new CLibInit;
var lib:Object = loader.init();
 
// @vertices is an array of XY-pairs: [ [], [x0, y0], [x1, y1], ...]
//                NOTE: @vertices[0] should always be [] 
// @contours is an array containing the number of points of each polygon
//                => [4, 3, 3, 3] indicates 4 polygons with the first poly having 4 points, the second 3, etc.
// @ncontours is the number of polygons (in our example: 4)
//
// @return An array of indices into the vertices array in form: [ [p0, p1, p2], [p0, p1, p2], ...]
var indices : Array = lib.triangulate( vertices, contours, ncontours );

Here’s the relevant C code which was simply added to tri.c :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
static AS3_Val triangulate(void* self, AS3_Val args)
{
	int ncontours = 0;
	int ccount;
	int npoints, first, last, n, nmonpoly;
	register int i;
	int op[SEGSIZE][3], ntriangles;
	AS3_Val dataVal;
	AS3_Val contourVal;
	AS3_Val retVal;
	AS3_Val temp;
 
	/* initialze the AS3 values */
	dataVal = AS3_Undefined();
	contourVal = AS3_Undefined();
	retVal = AS3_Undefined();
	temp = AS3_Undefined();
 
	//parse the arguments.
	AS3_ArrayValue( args, "AS3ValType, AS3ValType, IntType", &dataVal, &contourVal, &ncontours );
 
	//if no argument is specified
	if(dataVal == NULL || contourVal == NULL || ncontours <= 0)
	{
		AS3_Trace( AS3_String("Invalid input data!") );
		return AS3_Null();
	}
 
	ccount = 0;
	i = 1;
 
	while (ccount < ncontours)
   	{
		int j;
		int k;
		//fscanf(infile, "%d", &npoints);
 
		npoints = AS3_IntValue( AS3_Get(contourVal, AS3_Int(ccount)) );
 
		first = i;
		last = first + npoints - 1;
		for (j = 0, k = 1; j < npoints; j++, i++, k += 2)
		{
			//fscanf(infile, "%lf%lf", &seg[i].v0.x, &seg[i].v0.y);
			temp = AS3_Get(dataVal, AS3_Int(i) );
 
			seg[i].v0.x = AS3_NumberValue( AS3_Get(temp, AS3_Int(0)) );
			seg[i].v0.y = AS3_NumberValue( AS3_Get(temp, AS3_Int(1)) );
 
			if (i == last)
			{
				seg[i].next = first;
				seg[i].prev = i-1;
				seg[i-1].v1 = seg[i].v0;
			}
			else if (i == first)
			{
				seg[i].next = i+1;
				seg[i].prev = last;
				seg[last].v1 = seg[i].v0;
			}
			else
			{
				seg[i].prev = i-1;
				seg[i].next = i+1;
				seg[i-1].v1 = seg[i].v0;
			}
 
			seg[i].is_inserted = FALSE;
		}
 
		ccount++;
	}
 
	n = i - 1;
	initialise(n);
	construct_trapezoids(n);
	nmonpoly = monotonate_trapezoids(n);
	ntriangles = triangulate_monotone_polygons(n, nmonpoly, op);
 
	retVal = AS3_Array("AS3ValType", NULL);
 
	for (i = 0; i < ntriangles; i++)
   	{
		AS3_Val data = AS3_Array("IntType, IntType, IntType", op[i][0], op[i][1], op[i][2] );
 
		AS3_Set(retVal, AS3_Int(i), data);
	}
 
	return retVal;
}
 
//entry point for code
int main()
{
	//define the methods exposed to ActionScript
	//typed as an ActionScript Function instance
	AS3_Val echoMethod = AS3_Function( NULL, echo );
	AS3_Val triMethod = AS3_Function( NULL, triangulate );
 
	// construct an object that holds references to the functions
	AS3_Val result = AS3_Object( "echo: AS3ValType", echoMethod );
	AS3_SetS(result, "triangulate", triMethod);
 
	// Release
	AS3_Release( echoMethod );
	AS3_Release( triMethod );
 
	// notify that we initialized -- THIS DOES NOT RETURN!
	AS3_LibInit( result );
 
	// should never get here!
	return 0;
}

Papervision3D – FrustumCamera3D

Sunday, February 10th, 2008

Yesterday I updated the FrustumCamera3D class of Papervision3D. Time for a short tutorial on this camera!

The main difference between this camera and the other 2 cameras (Camera3D and FreeCamera3D) is that this camera uses a ‘real’ projection matrix, analogue to OpenGL. The FrustumCamera3D has 2 types of projection matrices: perspective and orthographic.

Checkout the example belonging to this tutorial (well: the code below IS the tutorial :-) .

 
package {
 
import flash.display.Sprite;
import flash.events.*;
import flash.geom.Point;
import flash.text.TextField;
import flash.text.TextFormat;
 
import org.papervision3d.Papervision3D;
import org.papervision3d.cameras.*;
import org.papervision3d.core.math.*;
import org.papervision3d.core.proto.*;
import org.papervision3d.materials.*;
import org.papervision3d.materials.utils.*;
import org.papervision3d.objects.*;
import org.papervision3d.objects.primitives.*;
import org.papervision3d.render.*;
import org.papervision3d.scenes.*;
import org.papervision3d.view.*;
 
/**
 * @author timknip
 */
public class TutoFrustumCamera3D extends Sprite {
 
	/** The PV3D scene to render */
	public var scene : Scene3D;
 
	/** This PV3D camera */
	public var camera : FrustumCamera3D;
 
	/** The PV3D renderer */
	public var renderer : BasicRenderEngine;
 
	/** The PV3D viewport */
	public var viewport : Viewport3D;
 
	/** Show info */
	public var status : TextField;
 
	/**
	 * Constructor.
	 */
	public function TutoFrustumCamera3D() : void {
		init();
	}
 
	/**
	 * Init.
	 */
	private function init() : void {
 
		// add a textfield
		initStatusLine();
 
		// used by orbiting / zooming
		_lastMouse = new Point();
 
		// create a viewport
		viewport = new Viewport3D(800, 600);
 
		// add
		addChild(viewport);
 
		// create a frustum camera with FOV=60, near=10, far=10000
		camera = new FrustumCamera3D(viewport, 60, 10, 10000);
 
		// create a renderer
		renderer = new BasicRenderEngine();
 
		// create the scene
		scene = new Scene3D();
 
		// init the scene
		initScene();
 
		// mouse listeners
		stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
		stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
		stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
 
		// key listener
		stage.addEventListener(KeyboardEvent.KEY_UP, keyUpHandler);
 
		// render on each frame
		addEventListener(Event.ENTER_FRAME, render);
 
		showInfo();
	}
 
	/**
	 * Init the scene.
	 */
	private function initScene() : void {
 
		var materials : MaterialsList = new MaterialsList();
 
		materials.addMaterial(new WireframeMaterial(0xff0000), "front");
		materials.addMaterial(new WireframeMaterial(0x0000ff), "back");
		materials.addMaterial(new WireframeMaterial(0xffff00), "left");
		materials.addMaterial(new WireframeMaterial(0x00ffff), "right");
		materials.addMaterial(new WireframeMaterial(0x00ff00), "top");
		materials.addMaterial(new WireframeMaterial(0xff00ff), "bottom");
 
		var cube : Cube = new Cube(materials);
 
		scene.addChild(cube);
	}
 
	/**
	 * Add a textfield.
	 */
	private function initStatusLine() : void {
		status = new TextField();
		addChild(status);
		status.x = status.y = 5;
		status.width = 500;
		status.height = 200;
		status.multiline = true;
		status.selectable = false;
		status.defaultTextFormat = new TextFormat("Arial", 12, 0xffff00);
		status.text = "";
	}
 
		/**
	 * Render!
	 */
	private function render( event : Event = null ) : void {
 
		// orbit the camera
		camera.orbit(_camTarget, _camPitch, _camYaw, _camDist);
 
		// render
		renderer.renderScene(scene, camera, viewport);
	}
 
	/**
	 * Show some info;
	 */
	private function showInfo() : void {
		var msg : String = "usage:\ndrag to rotate, drag-shift to zoom\n";
		msg += "- o: toggle ortho / perspective mode\n";
		msg += "- v: increase / decrease(+shift) FOV\n";
		msg += "- f: increase / decrease(+shift) camera far-plane\n";
		msg += "- n: increase / decrease(+shift) camera near-plane\n\n";
 
		msg += "camera\n- projection mode: " + (camera.ortho?"ortho":"perspective") + "\n";
		msg += "- fov: " + camera.fov + " near: " + camera.near + " far: " + camera.far + "\n";
 
		status.text = msg;
	}
 
	/**
	 *
	 * @param	event
	 */
	private function keyUpHandler( event : KeyboardEvent ) : void {
 
		switch(event.keyCode) {
			case 70: // f
				if(!camera.ortho) {
					if(event.shiftKey)
						camera.far -= 10;
					else
						camera.far += 10;
				}
				break;
 
			case 78: // n
				if(!camera.ortho) {
					if(event.shiftKey)
						camera.near -= 10;
					else
						camera.near += 10;
				}
				break;
 
			case 79: // o
				camera.ortho = !camera.ortho;
				break;
 
			case 86: // v
				if(!camera.ortho) {
					if(event.shiftKey)
						camera.fov -= 5;
					else
						camera.fov += 5;
				}
				break;
 
			default:
				break;
		}
 
		showInfo();
	}
 
	/**
	 *
	 * @param	event
	 */
	private function mouseDownHandler( event : MouseEvent ) : void {
		_lastMouse.x = event.stageX;
		_lastMouse.y = event.stageY;
		if(event.shiftKey)
			_zooming = true;
		else
			_orbiting = true;
	}
 
	/**
	 *
	 * @param	event
	 */
	private function mouseMoveHandler( event : MouseEvent ) : void {
		var dx:Number = _lastMouse.x - event.stageX;
		var dy:Number = _lastMouse.y - event.stageY;
 
		if(_orbiting)	{
			_camYaw += dx * (Math.PI/180);
 
			_camPitch -= dy * (Math.PI/180);
			_camPitch = _camPitch < 0.001 ? 0.001 : _camPitch;
			_camPitch = _camPitch > Math.PI/2 ? Math.PI/2 : _camPitch;
 
			_lastMouse.x = event.stageX;
			_lastMouse.y = event.stageY;
		} else if(!camera.ortho && _zooming) {
			if(_camDist - dy > 10)
				_camDist -= dy;
		} else if(camera.ortho && _zooming) {
			// this is the essential bit to zoom the camera in ortho-mode!
			camera.orthoScale += (dy * 0.0005);
		}
 
		render();
 
		event.updateAfterEvent();
	}
 
	/**
	 *
	 * @param	event
	 */
	private function mouseUpHandler( event : MouseEvent ) : void {
		_orbiting = _zooming = false;
	}
 
	/** Are we orbiting the camera? */
	private var _orbiting			: Boolean = false;
 
	/** Are we zooming the camera? */
	private var _zooming			: Boolean = false;
 
	/** A Point to keep track of mouse coords */
	private var _lastMouse			: Point;
 
	/** Camera Yaw */
	private var _camYaw				: Number = -Math.PI/2;
 
	/** Camera Pitch */
	private var _camPitch			: Number = Math.PI/2;
 
	/** Camera distance */
	private var _camDist			: Number = 2000;
 
	/** Camera target */
	private var _camTarget			: DisplayObject3D = DisplayObject3D.ZERO;
}
}