Jelajahi Sumber

Merge pull request #2952 from bghgary/multiPrimitiveMorphTargets-fix

Fix glTF loader issue with morph targets in multiple primitives
David Catuhe 7 tahun lalu
induk
melakukan
b7bc8f70e3

+ 143 - 99
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -378,63 +378,47 @@ module BABYLON.GLTF2 {
         }
 
         private _loadMesh(context: string, node: IGLTFNode, mesh: IGLTFMesh): void {
-            node.babylonMesh.name = mesh.name || node.babylonMesh.name;
+            node.babylonMesh.name = node.babylonMesh.name || mesh.name;
 
-            var babylonMultiMaterial = new MultiMaterial(node.babylonMesh.name, this._babylonScene);
-            node.babylonMesh.material = babylonMultiMaterial;
-
-            var geometry = new Geometry(node.babylonMesh.name, this._babylonScene, null, false, node.babylonMesh);
-            var vertexData = new VertexData();
-            vertexData.positions = [];
-            vertexData.indices = [];
-
-            var subMeshInfos: { verticesStart: number; verticesCount: number; indicesStart: number; indicesCount: number; loadMaterial: (index: number) => void }[] = [];
-            var numRemainingPrimitives = mesh.primitives.length;
-
-            for (var index = 0; index < mesh.primitives.length; index++) {
-                var primitive = mesh.primitives[index];
-                this._loadPrimitive(context + "/primitives/" + index, node, mesh, primitive, (subVertexData, loadMaterial) => {
-                    subMeshInfos.push({
-                        verticesStart: vertexData.positions.length,
-                        verticesCount: subVertexData.positions.length,
-                        indicesStart: vertexData.indices.length,
-                        indicesCount: subVertexData.indices.length,
-                        loadMaterial: loadMaterial
-                    });
-
-                    vertexData.merge(subVertexData);
-
-                    if (--numRemainingPrimitives === 0) {
-                        geometry.setAllVerticesData(vertexData, false);
+            if (!mesh.primitives || mesh.primitives.length === 0) {
+                throw new Error(context + ": Primitives are missing");
+            }
 
-                        // TODO: optimize this so that sub meshes can be created without being overwritten after setting vertex data.
-                        // Sub meshes must be cleared and created after setting vertex data because of mesh._createGlobalSubMesh.
-                        node.babylonMesh.subMeshes = [];
+            this._createMorphTargets(context, node, mesh);
 
-                        for (var index = 0; index < subMeshInfos.length; index++) {
-                            var info = subMeshInfos[index];
-                            SubMesh.AddToMesh(index, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, node.babylonMesh);
-                            info.loadMaterial(index);
-                        }
-                    }
-                });
-            }
-        }
+            this._loadAllVertexDataAsync(context, mesh, () => {
+                this._loadMorphTargets(context, node, mesh);
 
-        private _loadPrimitive(context: string, node: IGLTFNode, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive, onSuccess: (vertexData: VertexData, loadMaterial: (index: number) => void) => void): void {
-            var subMaterials = (node.babylonMesh.material as MultiMaterial).subMaterials;
+                var primitives = mesh.primitives;
 
-            if (primitive.mode && primitive.mode !== EMeshPrimitiveMode.TRIANGLES) {
-                // TODO: handle other primitive modes
-                throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
-            }
+                var vertexData = new VertexData();
+                for (var primitive of primitives) {
+                    vertexData.merge(primitive.vertexData);
+                }
 
-            this._createMorphTargets(node, mesh, primitive);
+                new Geometry(node.babylonMesh.name, this._babylonScene, vertexData, false, node.babylonMesh);
+
+                // TODO: optimize this so that sub meshes can be created without being overwritten after setting vertex data.
+                // Sub meshes must be cleared and created after setting vertex data because of mesh._createGlobalSubMesh.
+                node.babylonMesh.subMeshes = [];
+
+                var verticesStart = 0;
+                var indicesStart = 0;
+                for (var index = 0; index < primitives.length; index++) {
+                    var vertexData = primitives[index].vertexData;
+                    var verticesCount = vertexData.positions.length;
+                    var indicesCount = vertexData.indices.length;
+                    SubMesh.AddToMesh(index, verticesStart, verticesCount, indicesStart, indicesCount, node.babylonMesh);
+                    verticesStart += verticesCount;
+                    indicesStart += indicesCount;
+                };
 
-            this._loadVertexDataAsync(context, mesh, primitive, vertexData => {
-                this._loadMorphTargetsData(context, mesh, primitive, vertexData, node.babylonMesh);
+                var multiMaterial = new MultiMaterial(node.babylonMesh.name, this._babylonScene);
+                node.babylonMesh.material = multiMaterial;
+                var subMaterials = multiMaterial.subMaterials;
+                for (var index = 0; index < primitives.length; index++) {
+                    var primitive = primitives[index];
 
-                var loadMaterial = (index: number) => {
                     if (primitive.material == null) {
                         subMaterials[index] = this._getDefaultMaterial();
                     }
@@ -461,17 +445,34 @@ module BABYLON.GLTF2 {
                         });
                     }
                 };
-
-                onSuccess(vertexData, loadMaterial);
             });
         }
 
+        private _loadAllVertexDataAsync(context: string, mesh: IGLTFMesh, onSuccess: () => void): void {
+            var primitives = mesh.primitives;
+            var numRemainingPrimitives = primitives.length;
+            for (var index = 0; index < primitives.length; index++) {
+                let primitive = primitives[index];
+                this._loadVertexDataAsync(context + "/primitive/" + index, mesh, primitive, vertexData => {
+                    primitive.vertexData = vertexData;
+                    if (--numRemainingPrimitives === 0) {
+                        onSuccess();
+                    }
+                });
+            }
+        }
+
         private _loadVertexDataAsync(context: string, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive, onSuccess: (vertexData: VertexData) => void): void {
             var attributes = primitive.attributes;
             if (!attributes) {
                 throw new Error(context + ": Attributes are missing");
             }
 
+            if (primitive.mode && primitive.mode !== EMeshPrimitiveMode.TRIANGLES) {
+                // TODO: handle other primitive modes
+                throw new Error(context + ": Mode " + primitive.mode + " is not currently supported");
+            }
+
             var vertexData = new VertexData();
 
             var numRemainingAttributes = Object.keys(attributes).length;
@@ -534,75 +535,118 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _createMorphTargets(node: IGLTFNode, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive): void {
-            var targets = primitive.targets;
+        private _createMorphTargets(context: string, node: IGLTFNode, mesh: IGLTFMesh): void {
+            var primitives = mesh.primitives;
+
+            var targets = primitives[0].targets;
             if (!targets) {
                 return;
             }
 
-            if (!node.babylonMesh.morphTargetManager) {
-                node.babylonMesh.morphTargetManager = new MorphTargetManager();
+            for (var primitive of primitives) {
+                if (!primitive.targets || primitive.targets.length != targets.length) {
+                    throw new Error(context + ": All primitives are required to list the same number of targets");
+                }
             }
 
+            var morphTargetManager = new MorphTargetManager();
+            node.babylonMesh.morphTargetManager = morphTargetManager;
             for (var index = 0; index < targets.length; index++) {
                 var weight = node.weights ? node.weights[index] : mesh.weights ? mesh.weights[index] : 0;
-                node.babylonMesh.morphTargetManager.addTarget(new MorphTarget("morphTarget" + index, weight));
+                morphTargetManager.addTarget(new MorphTarget("morphTarget" + index, weight));
             }
         }
 
-        private _loadMorphTargetsData(context: string, mesh: IGLTFMesh, primitive: IGLTFMeshPrimitive, vertexData: VertexData, babylonMesh: Mesh): void {
-            var targets = primitive.targets;
-            if (!targets) {
+        private _loadMorphTargets(context: string, node: IGLTFNode, mesh: IGLTFMesh): void {
+            var morphTargetManager = node.babylonMesh.morphTargetManager;
+            if (!morphTargetManager) {
                 return;
             }
 
-            for (var index = 0; index < targets.length; index++) {
-                let babylonMorphTarget = babylonMesh.morphTargetManager.getTarget(index);
-                var attributes = targets[index];
-                for (let attribute in attributes) {
-                    var accessor = GLTFUtils.GetArrayItem(this._gltf.accessors, attributes[attribute]);
-                    if (!accessor) {
-                        throw new Error(context + "/targets/" + index + ": Failed to find attribute '" + attribute + "' accessor " + attributes[attribute]);
+            this._loadAllMorphTargetVertexDataAsync(context, node, mesh, () => {
+                var numTargets = morphTargetManager.numTargets;
+                for (var index = 0; index < numTargets; index++) {
+                    var vertexData = new VertexData();
+                    for (var primitive of mesh.primitives) {
+                        vertexData.merge(primitive.targetsVertexData[index]);
                     }
 
-                    this._loadAccessorAsync("#/accessors/" + accessor.index, accessor, data => {
-                        if (accessor.name) {
-                            babylonMorphTarget.name = accessor.name;
-                        }
+                    var target = morphTargetManager.getTarget(index);
+                    target.setNormals(vertexData.normals);
+                    target.setPositions(vertexData.positions);
+                    target.setTangents(vertexData.tangents);
+                }
+            });
+        }
 
-                        // glTF stores morph target information as deltas while babylon.js expects the final data.
-                        // As a result we have to add the original data to the delta to calculate the final data.
-                        var values = <Float32Array>data;
-                        switch (attribute) {
-                            case "NORMAL":
-                                GLTFUtils.ForEach(values, (v, i) => values[i] += vertexData.normals[i]);
-                                babylonMorphTarget.setNormals(values);
-                                break;
-                            case "POSITION":
-                                GLTFUtils.ForEach(values, (v, i) => values[i] += vertexData.positions[i]);
-                                babylonMorphTarget.setPositions(values);
-                                break;
-                            case "TANGENT":
-                                // Tangent data for morph targets is stored as xyz delta.
-                                // The vertexData.tangent is stored as xyzw.
-                                // So we need to skip every fourth vertexData.tangent.
-                                for (var i = 0, j = 0; i < values.length; i++, j++) {
-                                    values[i] += vertexData.tangents[j];
-                                    if ((i + 1) % 3 == 0) {
-                                        j++;
-                                    }
-                                }
-                                babylonMorphTarget.setTangents(values);
-                                break;
-                            default:
-                                Tools.Warn("Ignoring unrecognized attribute '" + attribute + "'");
-                                break;
+        private _loadAllMorphTargetVertexDataAsync(context: string, node: IGLTFNode, mesh: IGLTFMesh, onSuccess: () => void): void {
+            var numRemainingTargets = mesh.primitives.length * node.babylonMesh.morphTargetManager.numTargets;
+
+            for (var primitive of mesh.primitives) {
+                var targets = primitive.targets;
+                primitive.targetsVertexData = new Array<VertexData>(targets.length);
+                for (let index = 0; index < targets.length; index++) {
+                    this._loadMorphTargetVertexDataAsync(context + "/targets/" + index, primitive.vertexData, targets[index], vertexData => {
+                        primitive.targetsVertexData[index] = vertexData;
+                        if (--numRemainingTargets === 0) {
+                            onSuccess();
                         }
                     });
                 }
             }
         }
 
+        private _loadMorphTargetVertexDataAsync(context: string, vertexData: VertexData, attributes: { [name: string]: number }, onSuccess: (vertexData: VertexData) => void): void {
+            var targetVertexData = new VertexData();
+
+            var numRemainingAttributes = Object.keys(attributes).length;
+            for (let attribute in attributes) {
+                var accessor = GLTFUtils.GetArrayItem(this._gltf.accessors, attributes[attribute]);
+                if (!accessor) {
+                    throw new Error(context + ": Failed to find attribute '" + attribute + "' accessor " + attributes[attribute]);
+                }
+
+                this._loadAccessorAsync("#/accessors/" + accessor.index, accessor, data => {
+                    // glTF stores morph target information as deltas while babylon.js expects the final data.
+                    // As a result we have to add the original data to the delta to calculate the final data.
+                    var values = <Float32Array>data;
+                    switch (attribute) {
+                        case "NORMAL":
+                            for (var i = 0; i < values.length; i++) {
+                                values[i] += vertexData.normals[i];
+                            }
+                            targetVertexData.normals = values;
+                            break;
+                        case "POSITION":
+                            for (var i = 0; i < values.length; i++) {
+                                values[i] += vertexData.positions[i];
+                            }
+                            targetVertexData.positions = values;
+                            break;
+                        case "TANGENT":
+                            // Tangent data for morph targets is stored as xyz delta.
+                            // The vertexData.tangent is stored as xyzw.
+                            // So we need to skip every fourth vertexData.tangent.
+                            for (var i = 0, j = 0; i < values.length; i++, j++) {
+                                values[i] += vertexData.tangents[j];
+                                if ((i + 1) % 3 == 0) {
+                                    j++;
+                                }
+                            }
+                            targetVertexData.tangents = values;
+                            break;
+                        default:
+                            Tools.Warn("Ignoring unrecognized attribute '" + attribute + "'");
+                            break;
+                    }
+
+                    if (--numRemainingAttributes === 0) {
+                        onSuccess(targetVertexData);
+                    }
+                });
+            }
+        }
+
         private _loadTransform(node: IGLTFNode): void {
             var position: Vector3 = Vector3.Zero();
             var rotation: Quaternion = Quaternion.Identity();
@@ -819,7 +863,7 @@ module BABYLON.GLTF2 {
                     case "influence":
                         getNextOutputValue = () => {
                             var numTargets = targetNode.babylonMesh.morphTargetManager.numTargets;
-                            var value = new Array(numTargets);
+                            var value = new Array<number>(numTargets);
                             for (var i = 0; i < numTargets; i++) {
                                 value[i] = outputData[outputBufferOffset++];
                             }
@@ -1258,7 +1302,7 @@ module BABYLON.GLTF2 {
                     break;
                 case "MASK":
                     babylonMaterial.alphaCutOff = (material.alphaCutoff == null ? 0.5 : material.alphaCutoff);
-                    
+
                     if (colorFactor) {
                         if (colorFactor[3] == 0) {
                             babylonMaterial.alphaCutOff = 1;

+ 4 - 0
loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts

@@ -205,6 +205,10 @@ module BABYLON.GLTF2 {
         material?: number;
         mode?: EMeshPrimitiveMode;
         targets?: { [name: string]: number }[];
+
+        // Runtime values
+        vertexData: VertexData;
+        targetsVertexData: VertexData[];
     }
 
     export interface IGLTFMesh extends IGLTFChildRootProperty {

+ 0 - 6
loaders/src/glTF/2.0/babylon.glTFLoaderUtils.ts

@@ -29,12 +29,6 @@ module BABYLON.GLTF2 {
             return bufferView.buffer;
         }
 
-        public static ForEach(view: Uint16Array | Uint32Array | Float32Array, func: (nvalue: number, index: number) => void): void {
-            for (var index = 0; index < view.length; index++) {
-                func(view[index], index);
-            }
-        }
-
         public static ValidateUri(uri: string): boolean {
             return (uri.indexOf("..") === -1);
         }

+ 1 - 1
src/Mesh/babylon.mesh.vertexData.ts

@@ -337,7 +337,7 @@
             }
 
             if (!source) {
-                if (length === other.length) {
+                if (length === 0 || length === other.length) {
                     return other;
                 }