Explorar o código

Merge pull request #5697 from brianzinn/master

Add support for color vertices in OBJ file (not part of standard)
David Catuhe %!s(int64=6) %!d(string=hai) anos
pai
achega
939ef36c68

+ 3 - 0
dist/preview release/what's new.md

@@ -88,6 +88,9 @@
 - Enable dragging in boundingBoxGizmo without needing a parent ([TrevorDev](https://github.com/TrevorDev))
 - Added per mesh culling strategy ([jerome](https://github.com/jbousquie))
 
+### OBJ Loader
+- Add color vertex support (not part of standard) ([brianzinn](https://github.com/brianzinn))
+
 ### glTF Loader
 
 - Added support for mesh instancing for improved performance when multiple nodes point to the same mesh ([bghgary](https://github.com/bghgary))

+ 116 - 50
loaders/src/OBJ/babylon.objFileLoader.ts

@@ -201,10 +201,31 @@ module BABYLON {
         }
     }
 
+    type MeshObject = {
+        name: string;
+        indices?: Array<number>;
+        positions?: Array<number>;
+        normals?: Array<number>;
+        colors?: Array<number>;
+        uvs?: Array<number>;
+        materialName: string;
+    };
+
     export class OBJFileLoader implements ISceneLoaderPluginAsync {
 
         public static OPTIMIZE_WITH_UV = false;
+        /**
+         * Invert model on y-axis (does a model scaling inversion)
+         */
         public static INVERT_Y = false;
+        /**
+         * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
+         */
+        public static IMPORT_VERTEX_COLORS = false;
+        /**
+         * Compute the normals for the model, even if normals are present in the file
+         */
+        public static COMPUTE_NORMALS = false;
         public name = "obj";
         public extensions = ".obj";
         public obj = /^o/;
@@ -212,7 +233,7 @@ module BABYLON {
         public mtllib = /^mtllib /;
         public usemtl = /^usemtl /;
         public smooth = /^s /;
-        public vertexPattern = /v( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
+        public vertexPattern = /v( +[\d|\.|\+|\-|e|E]+){3,7}/;
         // vn float float float
         public normalPattern = /vn( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)( +[\d|\.|\+|\-|e|E]+)/;
         // vt float float
@@ -253,10 +274,10 @@ module BABYLON {
         }
 
         /**
-         * Imports one or more meshes from the loaded glTF data and adds them to the scene
+         * Imports one or more meshes from the loaded OBJ data and adds them to the scene
          * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
          * @param scene the scene the meshes should be added to
-         * @param data the glTF data to load
+         * @param data the OBJ data to load
          * @param rootUrl root url to load from
          * @param onProgress event that fires when loading progress has occured
          * @param fileName Defines the name of the file to load
@@ -275,9 +296,10 @@ module BABYLON {
         }
 
         /**
-         * Imports all objects from the loaded glTF data and adds them to the scene
+         * Imports all objects from the loaded OBJ data and adds them to the scene
+         *
          * @param scene the scene the objects should be added to
-         * @param data the glTF data to load
+         * @param data the OBJ data to load
          * @param rootUrl root url to load from
          * @param onProgress event that fires when loading progress has occured
          * @param fileName Defines the name of the file to load
@@ -321,20 +343,22 @@ module BABYLON {
          * @private
          */
         private _parseSolid(meshesNames: any, scene: BABYLON.Scene, data: string, rootUrl: string): Promise<Array<AbstractMesh>> {
-
             var positions: Array<BABYLON.Vector3> = [];      //values for the positions of vertices
             var normals: Array<BABYLON.Vector3> = [];      //Values for the normals
             var uvs: Array<BABYLON.Vector2> = [];      //Values for the textures
-            var meshesFromObj: Array<any> = [];      //[mesh] Contains all the obj meshes
-            var handledMesh: any;      //The current mesh of meshes array
+            var colors: Array<BABYLON.Color4> = [];
+            var meshesFromObj: Array<MeshObject> = [];      //[mesh] Contains all the obj meshes
+            var handledMesh: MeshObject;      //The current mesh of meshes array
             var indicesForBabylon: Array<number> = [];      //The list of indices for VertexData
             var wrappedPositionForBabylon: Array<BABYLON.Vector3> = [];      //The list of position in vectors
+            var wrappedColorsForBabylon: Array<BABYLON.Color4> = []; // Array with all color values to match with the indices
             var wrappedUvsForBabylon: Array<BABYLON.Vector2> = [];      //Array with all value of uvs to match with the indices
             var wrappedNormalsForBabylon: Array<BABYLON.Vector3> = [];      //Array with all value of normals to match with the indices
             var tuplePosNorm: Array<{ normals: Array<number>; idx: Array<number>; uv: Array<number> }> = [];      //Create a tuple with indice of Position, Normal, UV  [pos, norm, uvs]
             var curPositionInIndices = 0;
             var hasMeshes: Boolean = false;   //Meshes are defined in the file
-            var unwrappedPositionsForBabylon: Array<number> = [];      //Value of positionForBabylon w/o Vector3() [x,y,z]
+            var unwrappedPositionsForBabylon: Array<number> = [];    //Value of positionForBabylon w/o Vector3() [x,y,z]
+            var unwrappedColorsForBabylon: Array<number> = [];       // Value of colorForBabylon w/o Color4() [r,g,b,a]
             var unwrappedNormalsForBabylon: Array<number> = [];      //Value of normalsForBabylon w/o Vector3()  [x,y,z]
             var unwrappedUVForBabylon: Array<number> = [];      //Value of uvsForBabylon w/o Vector3()      [x,y,z]
             var triangles: Array<string> = [];      //Indices from new triangles coming from polygons
@@ -345,11 +369,13 @@ module BABYLON {
             var increment: number = 1;      //Id for meshes created by the multimaterial
             var isFirstMaterial: boolean = true;
 
+            var grayColor = new BABYLON.Color4(0.5, 0.5, 0.5, 1);
+
             /**
              * Search for obj in the given array.
              * This function is called to check if a couple of data already exists in an array.
              *
-             * If found, returns the index of the founded tuple index. Returns -1 if not found
+             * If found, returns the index of the found tuple index. Returns -1 if not found
              * @param arr Array<{ normals: Array<number>, idx: Array<number> }>
              * @param obj Array<number>
              * @returns {boolean}
@@ -383,7 +409,7 @@ module BABYLON {
              * @param textureVectorFromOBJ Vector3 The value of uvs
              * @param normalsVectorFromOBJ Vector3 The value of normals at index objNormale
              */
-            var setData = (indicePositionFromObj: number, indiceUvsFromObj: number, indiceNormalFromObj: number, positionVectorFromOBJ: BABYLON.Vector3, textureVectorFromOBJ: BABYLON.Vector2, normalsVectorFromOBJ: BABYLON.Vector3) => {
+            var setData = (indicePositionFromObj: number, indiceUvsFromObj: number, indiceNormalFromObj: number, positionVectorFromOBJ: BABYLON.Vector3, textureVectorFromOBJ: BABYLON.Vector2, normalsVectorFromOBJ: BABYLON.Vector3, positionColorsFromOBJ?: BABYLON.Color4) => {
                 //Check if this tuple already exists in the list of tuples
                 var _index: number;
                 if (OBJFileLoader.OPTIMIZE_WITH_UV) {
@@ -421,6 +447,13 @@ module BABYLON {
                     //Push the normals for Babylon
                     //Each element is a BABYLON.Vector3(x,y,z)
                     wrappedNormalsForBabylon.push(normalsVectorFromOBJ);
+
+                    if (positionColorsFromOBJ !== undefined) {
+                        //Push the colors for Babylon
+                        //Each element is a BABYLON.Color4(r,g,b,a)
+                        wrappedColorsForBabylon.push(positionColorsFromOBJ);
+                    }
+
                     //Add the tuple in the comparison list
                     tuplePosNorm[indicePositionFromObj].normals.push(indiceNormalFromObj);
                     tuplePosNorm[indicePositionFromObj].idx.push(curPositionInIndices++);
@@ -428,13 +461,13 @@ module BABYLON {
                 } else {
                     //The tuple already exists
                     //Add the index of the already existing tuple
-                    //At this index we can get the value of position, normal and uvs of vertex
+                    //At this index we can get the value of position, normal, color and uvs of vertex
                     indicesForBabylon.push(_index);
                 }
             };
 
             /**
-             * Transform BABYLON.Vector() object onto 3 digits in an array
+             * Transform BABYLON.Vector() and BABYLON.Color() objects into numbers in an array
              */
             var unwrapData = () => {
                 //Every array has the same length
@@ -443,11 +476,16 @@ module BABYLON {
                     unwrappedPositionsForBabylon.push(wrappedPositionForBabylon[l].x, wrappedPositionForBabylon[l].y, wrappedPositionForBabylon[l].z);
                     unwrappedNormalsForBabylon.push(wrappedNormalsForBabylon[l].x, wrappedNormalsForBabylon[l].y, wrappedNormalsForBabylon[l].z);
                     unwrappedUVForBabylon.push(wrappedUvsForBabylon[l].x, wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
+                    if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                        //Push the r, g, b, a values of each element in the unwrapped array
+                        unwrappedColorsForBabylon.push(wrappedColorsForBabylon[l].r, wrappedColorsForBabylon[l].g, wrappedColorsForBabylon[l].b, wrappedColorsForBabylon[l].a);
+                    }
                 }
                 // Reset arrays for the next new meshes
                 wrappedPositionForBabylon = [];
                 wrappedNormalsForBabylon = [];
                 wrappedUvsForBabylon = [];
+                wrappedColorsForBabylon = [];
                 tuplePosNorm = [];
                 curPositionInIndices = 0;
             };
@@ -504,7 +542,8 @@ module BABYLON {
                         indicePositionFromObj,
                         0, 0,                                           //In the pattern 1, normals and uvs are not defined
                         positions[indicePositionFromObj],               //Get the vectors data
-                        BABYLON.Vector2.Zero(), BABYLON.Vector3.Up()    //Create default vectors
+                        BABYLON.Vector2.Zero(), BABYLON.Vector3.Up(),    //Create default vectors
+                        OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
                     );
                 }
                 //Reset variable for the next line
@@ -535,7 +574,8 @@ module BABYLON {
                         0,                                  //Default value for normals
                         positions[indicePositionFromObj],   //Get the values for each element
                         uvs[indiceUvsFromObj],
-                        BABYLON.Vector3.Up()                //Default value for normals
+                        BABYLON.Vector3.Up(),                //Default value for normals
+                        OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
                     );
                 }
 
@@ -566,7 +606,8 @@ module BABYLON {
 
                     setData(
                         indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj,
-                        positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj] //Set the vector for each component
+                        positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj], //Set the vector for each component
+                        OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
                     );
 
                 }
@@ -597,7 +638,8 @@ module BABYLON {
                         indiceNormalFromObj,
                         positions[indicePositionFromObj], //Get each vector of data
                         BABYLON.Vector2.Zero(),
-                        normals[indiceNormalFromObj]
+                        normals[indiceNormalFromObj],
+                        OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
                     );
                 }
                 //Reset variable for the next line
@@ -627,7 +669,8 @@ module BABYLON {
 
                     setData(
                         indicePositionFromObj, indiceUvsFromObj, indiceNormalFromObj,
-                        positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj] //Set the vector for each component
+                        positions[indicePositionFromObj], uvs[indiceUvsFromObj], normals[indiceNormalFromObj], //Set the vector for each component
+                        OBJFileLoader.IMPORT_VERTEX_COLORS === true ? colors[indicePositionFromObj] : undefined
                     );
 
                 }
@@ -655,9 +698,14 @@ module BABYLON {
                     handledMesh.normals = unwrappedNormalsForBabylon.slice();
                     handledMesh.uvs = unwrappedUVForBabylon.slice();
 
+                    if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                        handledMesh.colors = unwrappedColorsForBabylon.slice();
+                    }
+
                     //Reset the array for the next mesh
                     indicesForBabylon = [];
                     unwrappedPositionsForBabylon = [];
+                    unwrappedColorsForBabylon = [];
                     unwrappedNormalsForBabylon = [];
                     unwrappedUVForBabylon = [];
                 }
@@ -676,10 +724,12 @@ module BABYLON {
                     continue;
 
                     //Get information about one position possible for the vertices
-                } else if ((result = this.vertexPattern.exec(line)) !== null) {
+                } else if (this.vertexPattern.test(line)) {
+                    result = line.split(' ');
+                    //Value of result with line: "v 1.0 2.0 3.0"
+                    // ["v", "1.0", "2.0", "3.0"]
+
                     //Create a Vector3 with the position x, y, z
-                    //Value of result:
-                    // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
                     //Add the Vector in the list of positions
                     positions.push(new BABYLON.Vector3(
                         parseFloat(result[1]),
@@ -687,6 +737,21 @@ module BABYLON {
                         parseFloat(result[3])
                     ));
 
+                    if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                        if (result.length >= 7) {
+                            // TODO: if these numbers are > 1 we can use Color4.FromInts(r,g,b,a)
+                            colors.push(new BABYLON.Color4(
+                                parseFloat(result[4]),
+                                parseFloat(result[5]),
+                                parseFloat(result[6]),
+                                (result.length === 7 || result[7] === undefined) ? 1 : parseFloat(result[7])
+                            ));
+                        } else {
+                            // TODO: maybe push NULL and if all are NULL to skip (and remove grayColor var).
+                            colors.push(grayColor);
+                        }
+                    }
+
                 } else if ((result = this.normalPattern.exec(line)) !== null) {
                     //Create a Vector3 with the normals x, y, z
                     //Value of result
@@ -765,20 +830,12 @@ module BABYLON {
                 } else if (this.group.test(line) || this.obj.test(line)) {
                     //Create a new mesh corresponding to the name of the group.
                     //Definition of the mesh
-                    var objMesh: {
-                        name: string;
-                        indices?: Array<number>;
-                        positions?: Array<number>;
-                        normals?: Array<number>;
-                        uvs?: Array<number>;
-                        materialName: string;
-                    } =
-                    //Set the name of the current obj mesh
-                    {
-                        name: line.substring(2).trim(),
+                    var objMesh: MeshObject = {
+                        name: line.substring(2).trim(), //Set the name of the current obj mesh
                         indices: undefined,
                         positions: undefined,
                         normals: undefined,
+                        colors: undefined,
                         uvs: undefined,
                         materialName: ""
                     };
@@ -802,20 +859,12 @@ module BABYLON {
                         //Set the data for the previous mesh
                         addPreviousObjMesh();
                         //Create a new mesh
-                        var objMesh: {
-                            name: string;
-                            indices?: Array<number>;
-                            positions?: Array<number>;
-                            normals?: Array<number>;
-                            uvs?: Array<number>;
-                            materialName: string;
-                        } =
-                        //Set the name of the current obj mesh
-                        {
-                            name: objMeshName + "_mm" + increment.toString(),
+                        var objMesh: MeshObject = {
+                            name: objMeshName + "_mm" + increment.toString(), //Set the name of the current obj mesh
                             indices: undefined,
                             positions: undefined,
                             normals: undefined,
+                            colors: undefined,
                             uvs: undefined,
                             materialName: materialNameFromObj
                         };
@@ -851,7 +900,7 @@ module BABYLON {
                 //Set the data for the last mesh
                 handledMesh = meshesFromObj[meshesFromObj.length - 1];
 
-                //Reverse indices for displaying faces in the good sens
+                //Reverse indices for displaying faces in the good sense
                 indicesForBabylon.reverse();
                 //Get the good array
                 unwrapData();
@@ -860,9 +909,13 @@ module BABYLON {
                 handledMesh.positions = unwrappedPositionsForBabylon;
                 handledMesh.normals = unwrappedNormalsForBabylon;
                 handledMesh.uvs = unwrappedUVForBabylon;
+
+                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                    handledMesh.colors = unwrappedColorsForBabylon;
+                }
             }
 
-            //If any o or g keyword found, create a mesj with a random id
+            //If any o or g keyword found, create a mesh with a random id
             if (!hasMeshes) {
                 // reverse tab of indices
                 indicesForBabylon.reverse();
@@ -873,6 +926,7 @@ module BABYLON {
                     name: BABYLON.Geometry.RandomId(),
                     indices: indicesForBabylon,
                     positions: unwrappedPositionsForBabylon,
+                    colors: unwrappedColorsForBabylon,
                     normals: unwrappedNormalsForBabylon,
                     uvs: unwrappedUVForBabylon,
                     materialName: materialNameFromObj
@@ -910,11 +964,23 @@ module BABYLON {
                 materialToUse.push(meshesFromObj[j].materialName);
 
                 var vertexData: VertexData = new BABYLON.VertexData(); //The container for the values
-                //Set the data for the babylonMesh
-                vertexData.positions = handledMesh.positions;
-                vertexData.normals = handledMesh.normals;
-                vertexData.uvs = handledMesh.uvs;
-                vertexData.indices = handledMesh.indices;
+                //Set the vertex data for the babylonMesh
+                vertexData.uvs = handledMesh.uvs as FloatArray;
+                vertexData.indices = handledMesh.indices as FloatArray;
+                vertexData.positions = handledMesh.positions as FloatArray;
+
+                if (OBJFileLoader.COMPUTE_NORMALS === true) {
+                    let normals: Array<number> = new Array<number>();
+                    BABYLON.VertexData.ComputeNormals(handledMesh.positions, handledMesh.indices, normals);
+                    vertexData.normals = normals;
+                } else {
+                    vertexData.normals = handledMesh.normals as FloatArray;
+                }
+
+                if (OBJFileLoader.IMPORT_VERTEX_COLORS === true) {
+                    vertexData.colors = handledMesh.colors as FloatArray;
+                }
+
                 //Set the data from the VertexBuffer to the current BABYLON.Mesh
                 vertexData.applyToMesh(babylonMesh);
                 if (OBJFileLoader.INVERT_Y) {

+ 33 - 0
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -519,6 +519,39 @@ describe('Babylon Scene Loader', function() {
         // TODO: test KHR_lights
     });
 
+    /**
+     * Integration tests for loading OBJ assets.
+     */
+    describe('#OBJ', () => {
+        it('should load a tetrahedron (without colors)', () => {
+            var fileContents = `               
+                g tetrahedron
+                
+                v 1.00 1.00 1.00 0.666 0 0
+                v 2.00 1.00 1.00 0.666 0 0
+                v 1.00 2.00 1.00 0.666 0 0
+                v 1.00 1.00 2.00 0.666 0 0
+                
+                f 1 3 2
+                f 1 4 3
+                f 1 2 4
+                f 2 3 4
+            `;
+
+            var scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.LoadAssetContainerAsync('', 'data:' + fileContents, scene, ()=> {}, ".obj").then(container => {
+                expect(container.meshes.length).to.eq(1);
+                let tetrahedron = container.meshes[0];
+
+                var positions : BABYLON.FloatArray = tetrahedron.getVerticesData(BABYLON.VertexBuffer.PositionKind);
+                var colors : BABYLON.FloatArray = tetrahedron.getVerticesData(BABYLON.VertexBuffer.ColorKind);
+
+                expect(positions).to.deep.equal([1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 2]);
+                assert.isNull(colors, 'expecting colors vertex buffer to be null')
+            })
+        })
+    })
+
     describe('#AssetContainer', () => {
         it('should be loaded from BoomBox GLTF', () => {
             var scene = new BABYLON.Scene(subject);