nomatter
15mai/110

[FLASH] Préchargement des textures d’un Collada avec Away3d

Hé ben je voulais écrire cet article depuis un bon moment... Mais il y a 6 mois maintenant j'ai déménager sur Montréal, ce qui est génial ! Malheureusement à peine 2 mois après je suis tombé très malade et j'ai du revenir en France pour 2 mois et demi (Vraiment besoin d'aide de ma famille). J'ai du prendre soin de ma santé sans pouvoir rien faire d'autre...:( Mais je suis de retour sur Montréal mieux que jamais (ou presque :) ) Et il est temps d'un nouvel article !

Bon si vous vouliez savoir comment précharger les textures d'un Collada, vous avez une solution juste ici ! Par défaut le parser d'Away3d charge automatiquement les textures d'un Collada, ce qui est excessivement pratique et rapide ! Mais maintenant voyons comment il est possible de faire ça "manuellement" et aussi d'accéder à la déclaration des matériaux. (Version d'Away3d : 3.6.0 )
-> Voir l'exemple <-

Code source de l'exemple :

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

J'ai juste écris une classe qui permet de récupérer les urls des textures contenu dans le fichier collada.
En extra, cette classe permet également de récupérer les caméras (En Camera3D pas de TargetCamera3D)

Code source de ColladaContentUtils.as :

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

Télécharger de ColladaContentUtils.as
Télécharger de Projet de préchargement des texture d'un Collada

Bon dans quelques mois "Molehill" serait intégré à la sortie du FP11 et tout ça serait certainement totalement obsolète !

20juin/100

[Website] natureByNoah, Sangue Bom et Vertbaudet

Pas 1, ni 2 mais 3 sites !
J'ai vraiment été totalement dépassé par le temps que m'as pris mes derniers projets... Va falloir que je m'organise un peu mieux !
Sinon une petite news sur les derniers projets sur lesquels j'ai travaillé.


Naturebynoah

L'initiative "Ecoute ta nature" !
La première partie du site présente l'initiative (je vous laisse lire le site) entrepris par Bourgeois en partenariat avec Yannick Noah. L'autre est une partie communautaire dédié à nos gestes quotidiens pour l'environnement. On peut y trouver des astuces intéressantes ! Je vous invite donc (forcément) à regarder tout cela, voir y laisser une petite initiative :)





Sangue Bom

Si vous ne connaissez pas encore Sangue Bom, c'est le moment de le découvrir ! Sound Designer talentueux (ce n'est que mon avis bien sur :) ), on se lasse pas d'apprécier son travail et ses ambiances (d'ailleurs j'ai mis en fond le site pour avoir la musique pendant que j'écris ce post !)







Vertbaudet : Fêtes des mères

Jeunes mamans venez choisir VOTRE jour de fête des mères ! Bon je sais c'est déjà passé... Mais bon vous pouvez voir le résultat final !
(En même temps des jeunes mamans qui se ballade sur ce site et qui, en plus, ne connaitrais pas Vertbaudet... Enfin tout est possible il parait... )








C'est tout pour le moment !

2fév/103

Correcteur de Collada venant de Cinema4D pour Away3D et Papervision3D

---- MODIFICATION : 15 mai 2011
Cinema4D.as Correction du bogue : le script ajoutait une propriété material à des objets qui n'en n'avais pas besoin.
AIR Application : Mis à jour, maintenant met à jour VRAIMENT votre fichier... (merci à Tobias Richter !)
----

Ben le voila enfin ce blog !

Alors c'est quoi le sujet ? Ah moui un "correcteur de Collada" venant de Cinema4D pour Away3 et Papervision3D.
Désolé j'ai pas trouvé de titre plus long...

Sur le dernier projet sur lequel j'ai travaillé (visible ici) j'ai rencontré un problème comme rarement je les aime ! En intégrant le modèle 3d exporté en Collada depuis Cinema4D, il y a eu comme qui dirait un petit souci. Il m'appliquait une seule des textures du fichier sur tous les objets... J'ai essayé avec Away3D et Papervision3D, même sanction.
Malheureusement ma connaissance du format Collada se résumait un peu au bouton export sous Cinema4D...

Est venu le temps de l'exploration (c'est vraiment le mot) de la structure du Collada. J'ai pas été déçu de l'initiative.... Des imbrications dans tous les sens, des propriétés qui font correspondre les textures aux effets, les effets aux matériaux, les matériaux aux instances, etc.
Au final j'ai trouvé ce qui posait problème. Cinema4D met le même nom pour les matériaux dans plusieurs nœuds. Ouais quand on voit écrit "Material 1" partout c'est pas trop dur de deviner que c'est, peut-être, potentiellement, à tout hasard, le problème recherché.
Bref j'ai écris un petite classe pour corriger ça. Maintenant on a Material 1 ET Material 2, Material 3, etc. C'est pas vraiment funky comme nom mais ça permet d'éviter d'avoir sa texture de plancher appliquer sur les murs et les bureaux...

Le code en question :

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

    }
}

Un petit exemple du "bug".

À la base j'avais écris une application AIR pour directement corriger le fichier. Après j'ai extrait le code pour le mettre dans une classe, c'est un petit long de corriger le fichier à chaque fois qu'il y a une modif dessus.
Dans le AIR vous pouvez faire un glisser/déposer dessus pour corriger directement le fichier ou utiliser le bouton "Select file".

Si jamais vous utiliser cinema4d, dites-moi si vous avez rencontré le même problème que me sente moins seul ! J'ai bien essayé de chercher sur différents forums, mais les problèmes liés au Collada sont si nombreux que trouver quelque chose est devenu un enfer...

Le fichier AS : Cinema4D Collada Corrector AS
L'app AIR : C4DColladaCorrector AIR install
Les sources du projet AIR :C4DColladaCorrector AIR project

Remplis sous: AIR, AS3, Cinema 4D, Flex 3 Commentaires