nomatter
15May/110

[Flash] Preloading Collada’s textures with Away3D

Well this post has been delayed for such a long time... First, six months ago I moved to Montreal which it is one the greatest thing I've done! But unfortunately just 2 months after, my health went really bad and I had to come back in France for 2 months and half (absolute need of family support)... Patiently took care of me, can't do something else... :( Well now I'm back to Montreal, better than ever! Well almost :) So it's time to a new blog post :)

So now if you wanted to know how to add a custom preload to your Collada's textures, you have found a solution here! By default the Collada's parser of Away3d autoload textures that your model need. Which is great, it's an easy way to quickly display your model. But now let see how to preload them and more change material declaration. (Away3d version used: 3.6.0)
-> View the example <- Example source code:

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <nomatter.me@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return Nicolas Schiro
* ----------------------------------------------------------------------------
*/


package
{
    import away3d.cameras.Camera3D;
    import away3d.cameras.TargetCamera3D;
    import away3d.cameras.lenses.PerspectiveLens;
    import away3d.containers.ObjectContainer3D;
    import away3d.containers.View3D;
    import away3d.core.base.Mesh;
    import away3d.core.render.Renderer;
    import away3d.lights.DirectionalLight3D;
    import away3d.lights.PointLight3D;
    import away3d.loaders.Collada;
    import away3d.loaders.data.MaterialData;
    import away3d.loaders.utils.MaterialLibrary;
    import away3d.materials.BitmapMaterial;
    import away3d.materials.ColorMaterial;
    import away3d.materials.ShadingColorMaterial;
    import away3d.materials.WhiteShadingBitmapMaterial;
    import away3d.primitives.Sphere;
   
    import br.com.stimuli.loading.BulkLoader;
    import br.com.stimuli.loading.BulkProgressEvent;
   
    import eu.nomatter.away3d.utils.ColladaContentUtils;
    import eu.nomatter.collada.Cinema4D;
   
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Vector3D;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.utils.Dictionary;
   
    [SWF(width='1000', height='624', backgroundColor='#dedede', frameRate='30')]
   
    public class SampleColladaContentUtils extends Sprite
    {
        private var colladaLoader : URLLoader;
        private var collada : XML;
        private var progressTexturesBar : ProgressTexturesBar;
       
        private var colladaContentUtils : ColladaContentUtils;
        private var urlsTextures : Vector.<String>
        private var texturesLoader : BulkLoader;
       
        private var view : View3D;
        private var model : ObjectContainer3D;
       
        public function SampleColladaContentUtils()
        {
            // Load Collada
            this.colladaLoader = new URLLoader();
            this.colladaLoader.addEventListener(Event.COMPLETE, this.completeColladaLoading );
            this.colladaLoader.load( new URLRequest( "assets/nomatter_bake.dae" ) );
        }
       
        private function completeColladaLoading( event : Event ) : void {
           
            // Get Collada
            this.collada = XML( this.colladaLoader.data );
            this.colladaLoader.removeEventListener(Event.COMPLETE, this.completeColladaLoading );
            this.colladaLoader = null;
           
            this.colladaContentUtils = new ColladaContentUtils();
            this.urlsTextures = this.colladaContentUtils.getTextures( collada );
           
            // ProgressBar
            this.progressTexturesBar = new ProgressTexturesBar();
            this.progressTexturesBar.addEventListener(Event.COMPLETE, this.texturesComplete );
            this.addChild( this.progressTexturesBar );
           
            // Load textures
            this.texturesLoader = new BulkLoader( "TEXTURES_LOADER" );
            for each( var url : String in urlsTextures ) {
                this.texturesLoader.add( "assets/"+url, {id:url} );
            }
            this.texturesLoader.addEventListener( BulkProgressEvent.PROGRESS, this.texturesProgress );
            this.texturesLoader.start( 3 );
        }
       
        private function texturesProgress( event : BulkProgressEvent ) : void {
            this.progressTexturesBar.progress( event.percentLoaded );
        }
       
        private function texturesComplete( event : Event ) : void {
            // Remove progressBar
            this.progressTexturesBar.removeEventListener( Event.COMPLETE, this.texturesComplete );
            this.removeChild( this.progressTexturesBar );
            this.progressTexturesBar = null;
           
           
            // get Texture
            var textures : Dictionary = new Dictionary( true );
            for each( var id:String in urlsTextures ) {
                textures[ id ] = this.texturesLoader.getBitmapData( id, false );
            }
           
            // Get the 3D model
            this.model = Collada.parse( this.collada, {autoLoadTextures:false} );

           
            // Here it is! Add materials
            var ml : MaterialLibrary = this.model.materialLibrary;
            var m : MaterialData;
            var bitmap : BitmapData;
            for each( m in ml ) {
                bitmap = textures[ m.textureFileName ];
                if( m.textureFileName == "floor.jpg" ) {
                    m.material = new BitmapMaterial( bitmap );
                } else {
                    m.material = new WhiteShadingBitmapMaterial( bitmap, {smooth:true} );
                }
            }
           
            // Recover cameras
            var cameras : Dictionary = this.colladaContentUtils.getCameras( this.collada );
            var camera : Camera3D = cameras["cam"] as Camera3D;
            camera.lens = new PerspectiveLens();
            camera.moveBackward( 1000 );
           
            // View 3D
            this.view = new View3D( {x:this.stage.stageWidth/2, y:this.stage.stageHeight/2, camera:camera, renderer:Renderer.CORRECT_Z_ORDER } );
            this.view.scene.addChild( this.model );
            this.addChild( this.view );
           
            var light : DirectionalLight3D = new DirectionalLight3D( {direction:new Vector3D(500,-300,200) } );
            this.view.scene.addLight( light );
           
            // Clear loader
            this.texturesLoader.clear();
            this.texturesLoader = null;
           
            // Render
            textures = null;
            this.addEventListener( Event.ENTER_FRAME, this.render );
        }
       
        private function render( event : Event ) : void {
            this.model.rotationY++;
            this.view.render();
           
        }
    }
}

I've simply written a class that parse the collada's data and return all urls textures.
This class can get camera's as well (just Camera3D no TargetCamera3D)

ColladaContentUtils.as source code:

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
/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <nomatter.me@gmail.com> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return Nicolas Schiro
* ----------------------------------------------------------------------------
*/


package eu.nomatter.away3d.utils
{
    import away3d.cameras.Camera3D;
    import away3d.core.base.Object3D;
   
    import flash.display.*;
    import flash.events.Event;
    import flash.events.ProgressEvent;
    import flash.geom.Matrix3D;
    import flash.geom.Vector3D;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.utils.Dictionary;
   
    public class ColladaContentUtils
    {
        private static var numLoader:int = -1;
       
        private var texturesId:Dictionary;
       
       
        public function ColladaContentUtils()
        {
            this.texturesId = new Dictionary();
        }
       
        public function getTextures( collada : XML ) : Vector.<String> {
            var colladaNoNamespace : XML = new XML( collada.toXMLString().replace(/\s+xmlns(:[^=]+)?="[^"]*"/g, "") );
            var ids : Vector.<String> = new Vector.<String>();
            var url:String;
           
            for each( var image:XML in colladaNoNamespace.library_images.image ) {
                url = image.init_from.toString();
                if( Boolean( url ) ) {
                    ids.push( url );
                }
            }
            return ids;
        }
       
       
        // Camera Utils
        public function getCameras( collada : XML ) : Dictionary {
            var colladaNoNamespace : XML = new XML( collada.toXMLString().replace(/\s+xmlns(:[^=]+)?="[^"]*"/g, "") );
           
            var cameras : Dictionary = new Dictionary;
            var camera:Camera3D;
            var id:String;
            var name:String;
            for each( var cameraXML:XML in colladaNoNamespace.library_cameras.camera ) {
                id = cameraXML.@id.toString();
                name = cameraXML.@name.toString();
               
                camera = new Camera3D();
                camera.name = name;
                camera.transform = this.getCameraTransform( id, colladaNoNamespace );
                cameras[ name ] = camera;
            }
           
            return cameras;
        }
       
        private function getCameraTransform( id:String, colladaNoNamespace : XML ) : Matrix3D {
            for each( var node:XML in colladaNoNamespace.library_visual_scenes.visual_scene.node ) {
                var result:XML = this.matchCameraId( node, id );
                if( Boolean( result ) ) {
                   
                    var translate:Array = result.translate.toString().split( " " );
                    var rotationX:Number = Number( result.rotate.( @sid == "rotateX" ).toString().split( " " )[ 3 ] );
                    var rotationY:Number = Number( result.rotate.( @sid == "rotateY" ).toString().split( " " )[ 3 ] );
                    var rotationZ:Number = Number( result.rotate.( @sid == "rotateZ" ).toString().split( " " )[ 3 ] );
                   
                    var object:Object3D = new Object3D();
                    object.rotateTo( rotationX, -rotationY+180, rotationZ );
                    object.position = new Vector3D( -translate[ 0 ], translate[ 1 ], translate[ 2 ] );
                    return object.transform;
                }
            }
            return null;
        }
       
        private function matchCameraId( rootNode:XML, id:String ) : XML {
            var idCamera:String = rootNode.instance_camera.@url.toString().split( "#" )[ 1 ];
           
            if( Boolean( idCamera ) ) {
                if( id == idCamera ) {
                    return rootNode;
                }
            } else if( rootNode.node.length() > 0 ) {
                for each( var node:XML in rootNode.node ) {
                    var returnNode:XML = this.matchCameraId( node, id );
                    if( Boolean( returnNode ) ) {
                        return returnNode;
                    }
                }
            }
            return null;
        }  
    }
}

Download ColladaContentUtils
Download Preloading Collada's Texture project

In a few month "MoleHill" will be released in the FP11 and this will be certainly totally obsolete!

Filed under: AS3, Away3d No Comments
20Jun/101

[Website] naturebynoah, Sangue Bom & Vertbaudet

Not 1, Not 2 but 3 websites !
I'v been very busy at work during last months and get no time to update my blog... I have to improve my planning!


Naturebynoah

Present the "Ecoute ta nature" initiative! (not the dharma...)
The first part of the website present the new eco-products made by Bougeois with the participation of Yannick Noah.
The second part is dedicated to our daily contribution to the nature!
Well that's a french website but feel free to post in English! We'll appreciate it :)





Sangue Bom

I've just made the sound visualization on the website of Sangue Bom ( but it look great! ), a really great sound designer. If you have a look, you'll notice that he worked on "Cisco : c'est vous le boss" and "Ecoute ta nature" too.







Vertbaudet : Mother's Day

Young mom, choose YOUR mother's day! Well mother's day is passed now... But you can still have a look on the final result!









More to come!

Filed under: website 1 Comment
2Feb/1011

Cinema4D Collada Corrector for Away3D and Papervision3D

---- EDIT : 15 may 2011
Cinema4D.as has been updated, bug fix : script added material property on objects that doesn't need it, cause Away3d parser to crash
AIR Application : Bug Fix, now REALLY update the file... (thanks to Tobias Richter! )
----

Welcome on my blog! You're reading my first post congratulations!

Well what's the subject? Oh year a "Cinema4D Collada corrector" for Away3D and Papervision3D...

On my last project I've got an issue when I tried to integrate a 3D model with several objects AND textures in Away3D.
It seems that, when coming from C4D, Away3D and P3D parser's take the same texture for all meshes. Even if I'm really not an expert in Collada, I'd searched throw the collada's file and parser what happened.

After some headaches to understand (a bit) how a Collada file is constructed (love the node's node's node's imbrication, and some id that "bind" textures to effects, effects to materials, materials with instances, etc. I'm sure it's possible to make a map with warzone and traps based on the collada structure...), I've found that Cinema4D export always the same material's name (a wonderful "Material 1"...) in several nodes.
I've written a class to correct this problem. Now you have Material 1 AND Material 2, Material 3, etc. Not sexy nor explicit but that's working.

Here the code :

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
package eu.nomatter.collada
{
    public class Cinema4D
    {
       
        private static var materialCount:int = 0;
       
        public function Cinema4D()
        {
        }
       
        public static function correctMaterial( xml:XML ):XML {
               
            // Fancy namespace remove http://www.stevensacks.net/2008/07/02/parsing-xhtml-with-e4x-in-as3/
            var colladaNamespace:Namespace = xml.namespace();
            xml = new XML( xml.toXMLString().replace(/\s+xmlns(:[^=]+)?="[^"]*"/g, "") );
           
            var idGeom:String;
            Cinema4D.materialCount = 0;
            var newName:String;
            var count:int = 0;
            var nodeGeometry:XMLList;
           
            for each( var geomXML:XML in xml.library_visual_scenes.visual_scene.node ) {
                Cinema4D.correctNode( xml, geomXML );
            }
           
            xml.@xmlns = colladaNamespace.toString();
            return xml;
        }
       
        private static function correctNode( xml:XML, rootNode:XML ):void {
            var materialName:String = "Material";
            if( rootNode.node.length() > 0 ) {
                for each( var node:XML in rootNode.node ) {
                    Cinema4D.correctNode( xml, node );
                }
            } else {
                var idGeom:String = String( rootNode.instance_geometry.@url.toString().split("#")[1] );
                var newName:String = materialName+String( ++Cinema4D.materialCount );
                rootNode.instance_geometry.bind_material.technique_common.instance_material.@symbol = newName;
                var nodeGeometry:XMLList = xml.library_geometries.geometry.(@id == idGeom);
                nodeGeometry.mesh.triangles.@material = newName;
            }
        }

    }
}

You can see a very simple test here

Originally I've made an AIR application to patch the file instead of executing the code at runtime. Now I've extract the code which correct the xml in a separate class. In the Air app you can drag and drop your file on the application to correct it or use the "Select file" button.

Oh and if you're a Cinem4D user, let me know if you had experiment the same problem!

AS Class : Cinema4D Collada Corrector AS
AIR File : C4DColladaCorrector AIR install
AIR Project source : C4DColladaCorrector AIR project

Filed under: AIR, AS3, Away3d, Cinema 4D, Flex 11 Comments