Techblog

Tech Blog

Contributions by Tim

26 May Papervision3D 2.1 – alpha

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 :-)

38 Comments -

29 April Unproject with useProjectionMatrix = true

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

3 Comments - Tags: , , ,

14 March Floorplanner API, ASP.NET and Authorization

Posted by Tim in Floorplanner

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

1 Comment -

20 January Papervision3D forum

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

Read more at the Papervision3D blog.

No Comments - Tags: ,

9 December Papervision3D wins Innovation Of The Year award

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.

2 Comments - Tags: ,

23 November Alchemy – first looks

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;
}

14 Comments - Tags:

10 February Papervision3D – FrustumCamera3D

Posted by Tim in Papervision3D

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;
}
}

4 Comments -

5 December Papervision3D 2.0 : Public Alpha Release

Posted by Tim in CAD, Papervision3D

Drumroll!
Papervision3D 2.0 (alpha) was just released! Ralph Hauwert did the bulk of the work, with me standing by as a second brain trying to solve the issues we struggled with last few days. There’s lots of bugs to be fixed of course, but check out the new stuff:

- viewports
- shaded materials (flat, gouraud, phong, bump etc.)
- frustum culling
- ascollada : import Collada files using the ASCollada library.
- animation
- more!


Get the code using SVN.

Okay, now i’ll get some sleep!

No Comments -

4 September Papervision3D training in Amsterdam, Europe

Papervision3D training is coming to Europe!
The 2 day workshop will be in Amsterdam, The Netherlands on October 8th and 9th, and will be hosted by Ralph Hauwert.

Read more here.

1 Comment -

21 August Magic Carpet – APE

APE (Actionscript Physics Engine) is a free AS3 open source 2D physics engine for use in Flash and Flex, released under the MIT License. APE is written and maintained by Alec Cove.

Manuel Bua created a Magic Carpet using Papervison3D and APE. Best of all: source included!

Check the video Manuel made:

No Comments -