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):
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");
- viewport = new Viewport3D(800, 600, false, true, true, true);
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.
- scene.addChild(cow);
- // before we can add event listeners we need to make sure the
- // object is instantiated completely
- cow.addEventListener(FileLoadEvent.LOAD_COMPLETE, handleColladaComplete);
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!
- // first we need to get a handle to the child object
- var childObj:DisplayObject3D = getColladaDisplayObj(cow);
- // using the child object we get a handle to its material
- var childMat:MaterialObject3D = getColladaNodeMaterial(childObj);
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.
- var childname:String = String(tempCO.childrenList()).substring(0,String(tempCO.childrenList()).indexOf(": "));
- returnObj = tempCO.getChildByName(childname);
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:
- var materialName:String = StringUtil.trim(String(colladaNode.materials));
- trace("materialName: '" + materialName + "'");
- returnVal = colladaNode.getMaterialByName(materialName);
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.
- if (childMat != null) {
- // now we have to tell the material to dispatch InteractiveScene3D events
- childMat.interactive = true;
- // Now we can add our listeners to the child object
- childObj.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, handleCowOver);
- childObj.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, handleCowOut);
- childObj.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, handleCowClick);
- }
The full source for the demo can be downloaded here.
- private function handleCowOver(event:InteractiveScene3DEvent):void {
- var cowLayer:ViewportLayer = viewport.getChildLayer(cow, true, true);
- cowLayer.filters = [new GlowFilter(0xffee11, 1, 10, 10, 6, 3, false, false)];
- }
- private function handleCowOut(event:InteractiveScene3DEvent):void {
- var cowLayer:ViewportLayer = viewport.getChildLayer(cow, true, true);
- cowLayer.filters = null;
- }
Comments
Very usefull, real-life demo + source code :)
Nice example.
How would i go about acsessing multiple objects (children), a more complex collada file ?
cheers, dani
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.
myDisplayObject3DInstance.material = myCellShadedMaterial;
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) ?
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;
}
Also, just to say that I've picked quite a few good tips from your blog.
Thanks.
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
//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;
}
}
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;
}
}
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?
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
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 :)
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?