Papervision Collada Interactivity Demo


I've received a number of requests for an example of how to use my collada interactivity helper functions, so I threw this little demo together. Note that this will only work with simple collada scenes containing a single object.
The first step is commonly overlooked and is crucial for Papervision3D interactivity. We have to enable interactivity on our viewport instance either through its constructor or setting the property directly. In the demo, I set it in the viewport constructor (4th parameter):

  1. viewport = new Viewport3D(800, 600, false, true, true, true);
Next we create our collada instance that we want to interact with. We have to wait until the collada object and its material has completely loaded before we can initialize interactivity so, we need to listen for a FileLoadEvent.LOAD_COMPLETE.cow = new Collada("cow.dae");

  1. scene.addChild(cow);
  2. // before we can add event listeners we need to make sure the
  3. // object is instantiated completely
  4. cow.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleColladaComplete);
When the LOAD_COMPLETE event is fired we can begin to enable interactivity on the object. In order for the object to dispatch mouse events, the "interactive" property of its material must be set to true. For primative objects, it's very simple to get a handle to their material because you usually explicitly instantiate the material or you can get a handle to it using myPrimativeObj.material. For Collada objects, you can explicitly specify a material and apply it to the object using a MaterialList. Or your .dae file will have the material image file specified internally and the Collada class will instantiate a BitmapFileMaterial automatically. I find the second way to be the most flexible because the designer can name the UV mapped image whatever they'd like and the name can change without breaking the app. However, this way does make getting a handle to the material a little trickier. This is where my helper functions come in. For simple (single object) collada files, these functions return the DirectObject3D instance of the child object and the Material3D instance.

  1. // first we need to get a handle to the child object
  2. var childObj:DisplayObject3D = getColladaDisplayObj(cow);
  3. // using the child object we get a handle to its material
  4. var childMat:MaterialObject3D = getColladaNodeMaterial(childObj);
getColladaDisplayObj() uses a nasty String parse method to retrieve the name of the first collada child object. It then uses Collada.getChildByName() and returns a handle to the child DisplayObject3D. I personally would love to see a better solution for this, but for now it works!

  1. var childname:String = String(tempCO.childrenList()).substring(0,String(tempCO.childrenList()).indexOf(": "));
  2. returnObj = tempCO.getChildByName(childname);
getColladaMaterial() derives the string name of the first material associated with the given child object. Here I use the Actionscript 3 Core Library's StringUtil class to trim off any leading or trailing space characters from the material name (I'm not sure why they are there but they are). Then I use Collada.getMaterialByName() with the material's name to return a handle to the object's Material3D instance.

  1. var materialName:String = StringUtil.trim(String(colladaNode.materials));
  2. trace("materialName: '" + materialName + "'");
  3. returnVal = colladaNode.getMaterialByName(materialName);
Finally, now that we have handles to the Material and the child object we set the material's interactive flag to true and add our event listeners:

  1. if (childMat != null) {
  2. // now we have to tell the material to dispatch InteractiveScene3D events
  3. childMat.interactive = true;
  4. // Now we can add our listeners to the child object
  5. childObj.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, handleCowOver);
  6. childObj.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, handleCowOut);
  7. childObj.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, handleCowClick);
  8. }
As an added bonus, this demo shows how you can easily apply filters to individual objects in the scene. First you get a handle to the object's layer using Viewport3D.getChildLayer and pass it the instance of the object. Next you create an array of filters and pass that array to the "filters" property of the ViewportLayer.

  1. private function handleCowOver(event:InteractiveScene3DEvent):void {
  2. var cowLayer:ViewportLayer = viewport.getChildLayer(cow, true, true);
  3. cowLayer.filters = [new GlowFilter(0xffee11, 1, 10, 10, 6, 3, false, false)];
  4. }
  5. private function handleCowOut(event:InteractiveScene3DEvent):void {
  6. var cowLayer:ViewportLayer = viewport.getChildLayer(cow, true, true);
  7. cowLayer.filters = null;
  8. }
The full source for the demo can be downloaded here.

Comments

Unknown said…
Thanks a million Michael!

Very usefull, real-life demo + source code :)
Unknown said…
Hello, Michael.
Nice example.
How would i go about acsessing multiple objects (children), a more complex collada file ?


cheers, dani
CodeJockey said…
For complex objects you have a couple options. This example depends on you having some information about the collada scene prior to using it. When I get time I will write a example that enables interactivity on a complex object with multiple materials.

For now you have to know the names of each child node in the collada scene and then do myColladaObj.getChildByName("myNodeName");
Once you have a reference to the child node, you can use the getColladaMaterial() function from the example if there is a single material and pass it the child node you retrieved.
Strider S.D.K. said…
hey, i've looking and looking everywhere... how can I apply a cellshader effect to a collada object?
CodeJockey said…
You can simply create an instance of the shaded material and then apply it to the DisplayObject3D like so:

myDisplayObject3DInstance.material = myCellShadedMaterial;
Fabse said…
that´s a great tutorial.

2 questions:

- I tried to change the used Collada Class to the DAE class. After that, the filter doesn´t work anymore. How to use the DAE class for that ? (i got some models [".dae" files] which won´t work with the Collada Class)

- How could i set the alpha for the layer or for the object (not touching the material) ?
CodeJockey said…
There seems to be a problem retrieving a handle to the DAE material. I'm looking into it and will post back when I have it sorted out.
CodeJockey said…
OK, replace the last two methods with these new methods and that should work for DAE.

public function getColladaDisplayObj(tempCO:DisplayObject3D):DisplayObject3D {
var returnObj:DisplayObject3D;

try {
var container:DisplayObject3D = tempCO.getChildByName("COLLADA_Scene", true);

var childname:String = String(container.childrenList()).substring(0,String(container.childrenList()).indexOf(": "));

returnObj = container.getChildByName(childname);

} catch(errObject:Error) {
trace(errObject.message);
}

return returnObj;
}

private function getColladaNodeMaterial(colladaNode:DisplayObject3D):MaterialObject3D {
var returnVal:MaterialObject3D;

try {
for each (var mat:Object in colladaNode.materials.materialsByName) {
trace("mat: " + mat);
returnVal = MaterialObject3D(mat);
}

} catch(errObject:Error) {
trace(errObject.message);
}

return returnVal;
}
camden_kid said…
Nice.

Also, just to say that I've picked quite a few good tips from your blog.

Thanks.
Hazardu5 said…
Just wanted to say thanks heaps for this code example! You've provided the answer to a problem that has caused hours of frustration trying to figure out why I couldn't add interactivity to Collada models :)
AaronLWilson said…
First off thanks for this great example. It rocks, and works perfectly when using the Collada class. However I also have .DAEs (v 1.4.1 from lightwave) that only render with the DAE class. I noticed that someone had a similar issue and you graciously offered a solution, but I could not make it work.

In your new function, it seems that the property "materials" on colladaNode is still null. I messed with it a bit and found that colladaNode.materialsList() returns an empty array. This is strange because I'm certain that its the deepest node (it has no children) and of course there is a visible material. colladaNode.material returns a Wireframe material, but setting this to interactive=true yields no result.

If you have it working with cow.dae, perahps you can let me know if I'm missing something. Thanks for your time.

Aaron
CodeJockey said…
AaronLWilson, I would need to take a look at your .dae file in order to provide any useful suggestions.
AaronLWilson said…
No worries, I was using your cow.dae and a few other colladas i've made with lightwave, blender and sketchup. The way these various files perform inside the DAE class varies a ton. Howeer I was able to add interactivity to most using this ugly recursive function to edit the faces directly. you probably don't need it but I'll post in case it helps anyone.

//Recurse through the faces of the child node to access materials
private function materialsToInteractive(object:DisplayObject3D):void
{
if (object.children){
for each(var child:DisplayObject3D in object.children){
materialsToInteractive(child);
}
}
//no material
if (!object.material){
return;
}
// The object is a shape, skip
if (!object.geometry.faces[0]) {
return;
}
var original:MaterialObject3D;
if (object.geometry.faces[0].material){
original = object.geometry.faces[0].material;
}else{
original = object.material;
}
//set it here
original.interactive = true;

for each( var triangle:Triangle3D in object.geometry.faces ){
triangle.material.interactive = true;
}
}
CodeJockey said…
AaronLWilson, WOW! Thanks for posting that! I haven't run into a situation where my DAE code hasn't worked yet, but I will definitely keep that in my bag-o-tricks! Could you share an example .dae file so that I can see where my code fails?
alltechneeds said…
Nice crafty demo, I prototype your example and modified my existing code, you can view it via www.hiphopinstitute.com, it should be up tommorrow 02/18/2009.
Unknown said…
Hello,

i want to use the DAE Class.
I also try to use the code from CodeJockey from the 21 Dec and the code from AaronLWilson. But it will not work.

The Structur in my DAE is following:
COLLADA_Scene
-Model
--mesh1
-Camera

If i put my DAE in this loop...

for each (var mat:Object in colladaNode.materials.materialsByName) {
trace("mat: " + mat);
}

.. i get a ColorMaterial and a BitmapMaterial.

But in the materialsToInteractive function from AaronLWilson, i only get four "no material".

Here my Code:

private const COLLADAS : Array = new Array( "collada/Sout.dae" , "collada/EG.dae" , "collada/1_OG.dae" , "collada/2_OG.dae" , "collada/3_OG.dae" , "collada/4_OG.dae" , "collada/5_OG.dae" , "collada/6_OG.dae" );

private var _viewport : Viewport3D;
private var _renderer : BasicRenderEngine;
private var _scene : Scene3D;
private var _camera : Camera3D;

private var _daes : Array;
private var _materials : Array;
private var _bitmaploader : BitmapLoader;


public function Main() : void
{
stage.quality = StageQuality.BEST;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;

_daes = new Array();
_materials = new Array();

_bitmaploader = new BitmapLoader();
_bitmaploader.addEventListener( Event.COMPLETE , onLoaded );
_bitmaploader.fillStack( "images/Sout.jpg" );
_bitmaploader.fillStack( "images/EG.jpg" );
_bitmaploader.fillStack( "images/1_OG.jpg" );
_bitmaploader.fillStack( "images/2_OG.jpg" );
_bitmaploader.fillStack( "images/3_OG.jpg" );
_bitmaploader.fillStack( "images/4_OG.jpg" );
_bitmaploader.fillStack( "images/5_OG.jpg" );
_bitmaploader.fillStack( "images/6_OG.jpg" );
_bitmaploader.load();
}


private function onLoaded( e : Event ) : void
{
_bitmaploader.removeEventListener(Event.COMPLETE, onLoaded);

for( var i:Number = 0; i<_bitmaploader.getLength(); i++ ) {
var bitmapMat : BitmapMaterial = new BitmapMaterial( _bitmaploader.getBitmapAt( i ) );
_materials.push( bitmapMat );
}

stage.addEventListener( Event.ENTER_FRAME , renderStuff );

init();
}


private function init() : void
{
_viewport = new Viewport3D( 900 , 650 , false, true, true, true );
addChild( _viewport );

_renderer = new BasicRenderEngine();
_scene = new Scene3D();
_camera = new Camera3D();
_camera.zoom = -700;

for( var i:Number = 0; i<_bitmaploader.getLength(); i++ ) {
var materialslist : MaterialsList = new MaterialsList();
materialslist.addMaterial( new ColorMaterial( 0xAAAAAA , 1 ) , "material0" );
materialslist.addMaterial( _materials[ i ] , "material1" );

var dae : DAE = new DAE();
dae.load( COLLADAS[ i ] , materialslist );
_daes.push( dae );

if( i == _bitmaploader.getLength()-1 ) {
dae.addEventListener( FileLoadEvent.LOAD_COMPLETE , daesLoaded );
}
}
}


private function daesLoaded( f : FileLoadEvent ) : void
{
trace( "================= Colladas Loaded" );
for( var i:Number=0; i<_bitmaploader.getLength(); i++ ) {

_daes[ i ].x = 55;
_daes[ i ].y = 40-i*10;
_daes[ i ].rotationX = 355;
_daes[ i ].rotationZ = 180;
_scene.addChild( _daes[ i ] );

materialsToInteractive( _daes[ i ] );

var listenerObj : DisplayObject3D = getColladaDisplayObj( _daes[ i ] );

listenerObj.addEventListener( InteractiveScene3DEvent.OBJECT_PRESS , showEtage );
listenerObj.addEventListener( InteractiveScene3DEvent.OBJECT_OVER , showEtage );
listenerObj.addEventListener( InteractiveScene3DEvent.OBJECT_CLICK , showEtage );
}
}


private function showEtage( i : InteractiveScene3DEvent ) : void
{
trace( "Click" );
}


private function renderStuff( e : Event ) : void
{
_renderer.renderScene( _scene , _camera , _viewport );
}


public function getColladaDisplayObj(tempCO : DisplayObject3D) : DisplayObject3D {
var returnObj : DisplayObject3D;

try {
var container : DisplayObject3D = tempCO.getChildByName("COLLADA_Scene", true);

var childname : String = String(container.childrenList()).substring(0, String(container.childrenList()).indexOf(": "));
trace("getColladaDisplayObj" + childname);


returnObj = container.getChildByName(childname);
} catch(errObject : Error) {
trace(errObject.message);
}

return returnObj;
}


//Recurse through the faces of the child node to access materials
private function materialsToInteractive(object : DisplayObject3D) : void {
if (object.children) {
for each(var child:DisplayObject3D in object.children) {
materialsToInteractive(child);
}
}
//no material
if (!object.material) {
trace("no Material");
return;
}
// The object is a shape, skip
if (!object.geometry.faces[0]) {
return;
}
var original : MaterialObject3D;
if (object.geometry.faces[0].material) {
original = object.geometry.faces[0].material;
} else {
original = object.material;
}
//set it here
original.interactive = true;

for each( var triangle:Triangle3D in object.geometry.faces ) {
triangle.material.interactive = true;
}
}
AaronLWilson said…
Hey Code Jockey, I meant to give you those DAEs that I was working with but you know how it goes... Anyway please help yourself here:

http://www.technic9.com/random/interactiveDAEs.zip

there are 2 daes in here both exported out of lightwave. These are the 2 that I've had total success with (interactivity, textures, shaders, shadows, etc)

Hey Switchback,
A quick look at your code looks good, but I did a few things different that might help. First try alternative DAEs, the ones I posted work for me...
Also you might want to add your FileLoadEvent.LOAD_COMPLETE to your DAE instance before calling load(). Also I think each DAE should have its own LOAD_COMPLETE listener instead of applying it to only one then trying to loop through them all in the handler. Otherwise they might not "really" be loaded by the time your handler method fires...

Try providing materialsToInteractive() the node "COLLADA_Scene" like this:

materialsToInteractive(listenerObj);

nam'sayin?

Something else I learned is that you don't really have to match the material name in your DAE to your AS instantiated materials, just use 'all' and it works for every DAE like this:

materialslist.addMaterial( new ColorMaterial( 0xAAAAAA , 1 ) , "all" );

good luck man, hope this helps.


Hey alltechneeds,
Super dope example at HipHopInstitute.com, but its only doing like 5 fps on my 2.6 gz brand spankin new macBookPro. How many polys is that thing? and what material are you using?
Unknown said…
Hi CodeJockey, Thanks for this tutorial, really useful.
I seem to be having similar issues to AaronLWilson, my DAE files don't seem to be interactive. Unfortunately AaronLWilson 's solution didn't work for me.
I've made a very simple model in blender to test with, but if I replace your cow model with my model, the interactivity stops working.
Would you mind taking a look at my collada file to see if you can see what I'm doing wrong? Alternatively maybe you could give me some pointers for exporting from blender?
Thanks
Dave
Unknown said…
I found what the problem was in the end, and though I'd better post the solution in case anyone else has similar issues.
My problem actually turned out to be caused by the closest object in the scene intercepting all the InteractiveScene3DEvents, so nothing else ever got hit, even tho it was clearly out of the way of the other objects.
I noticed that some of the geometry in my model looked a bit weird from the unions I had done, so I went into edit mode, selected all vertices, and pressed W -> 'Remove Doubles'. This seemed to clean up some dodgy vertices I had and everything worked perfectly after that :)
zgalieh said…
hi,
i want to know about the collada's file.
is collada's file (.dae) that is exported from blender, an animation?
and when it's imported by papervision3d, can it move on (animated) otomatically while executed by papervision3d?
CodeJockey said…
Unless Bender has changed it's export format, I don't believe that Papervision can parse an animation from Blender.

Popular Posts