Bläddra i källkod

Add support for Draco compression to glTF loader

Gary Hsu 7 år sedan
förälder
incheckning
733b6ba980

+ 10 - 0
Tools/Gulp/config.json

@@ -37,6 +37,7 @@
             "solidParticles",
             "additionalMeshes",
             "meshBuilder",
+            "meshCompression",
             "audio",
             "additionalTextures",
             "shadows",
@@ -115,6 +116,7 @@
             "solidParticles",
             "additionalMeshes",
             "meshBuilder",
+            "meshCompression",
             "audio",
             "additionalTextures",
             "shadows",
@@ -427,6 +429,12 @@
                 "additionalMeshes"
             ]
         },
+        "meshCompression" :
+        {
+            "files": [
+                "../../src/Mesh/Compression/babylon.dracoCompression.js"
+            ]
+        },
         "shaderMaterial": {
             "files": [
                 "../../src/Materials/babylon.shaderMaterial.js"
@@ -1479,6 +1487,7 @@
                     "../../loaders/src/glTF/2.0/babylon.glTFLoader.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts",
                     "../../loaders/src/glTF/2.0/Extensions/MSFT_lod.ts",
+                    "../../loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_lights.ts"
                 ],
@@ -1499,6 +1508,7 @@
                     "../../loaders/src/glTF/2.0/babylon.glTFLoader.ts",
                     "../../loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts",
                     "../../loaders/src/glTF/2.0/Extensions/MSFT_lod.ts",
+                    "../../loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_materials_pbrSpecularGlossiness.ts",
                     "../../loaders/src/glTF/2.0/Extensions/KHR_lights.ts"
                 ],

+ 66 - 0
loaders/src/glTF/2.0/Extensions/KHR_draco_mesh_compression.ts

@@ -0,0 +1,66 @@
+/// <reference path="../../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GLTF2.Extensions {
+    // https://github.com/KhronosGroup/glTF/pull/874
+
+    const NAME = "KHR_draco_mesh_compression";
+
+    interface IKHRDracoMeshCompression {
+        bufferView: number;
+        attributes: { [name: string]: number };
+    }
+
+    export class KHR_draco_mesh_compression extends GLTFLoaderExtension {
+        public readonly name = NAME;
+
+        protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> {
+            return this._loadExtensionAsync<IKHRDracoMeshCompression, VertexData>(context, primitive, (context, extension) => {
+                if (primitive.mode != undefined) {
+                    if (primitive.mode !== MeshPrimitiveMode.POINTS &&
+                        primitive.mode !== MeshPrimitiveMode.TRIANGLE_STRIP &&
+                        primitive.mode !== MeshPrimitiveMode.TRIANGLES) {
+                        throw new Error(context + ": Unsupported mode " + primitive.mode);
+                    }
+                }
+
+                const attributes: { [kind: string]: number } = {};
+                const loadAttribute = (name: string, kind: string) => {
+                    const uniqueId = extension.attributes[name];
+                    if (uniqueId == undefined) {
+                        return;
+                    }
+
+                    babylonMesh._delayInfo = babylonMesh._delayInfo || [];
+                    if (babylonMesh._delayInfo.indexOf(kind) === -1) {
+                        babylonMesh._delayInfo.push(kind);
+                    }
+
+                    attributes[kind] = uniqueId;
+                };
+
+                loadAttribute("POSITION", VertexBuffer.PositionKind);
+                loadAttribute("NORMAL", VertexBuffer.NormalKind);
+                loadAttribute("TANGENT", VertexBuffer.TangentKind);
+                loadAttribute("TEXCOORD_0", VertexBuffer.UVKind);
+                loadAttribute("TEXCOORD_1", VertexBuffer.UV2Kind);
+                loadAttribute("JOINTS_0", VertexBuffer.MatricesIndicesKind);
+                loadAttribute("WEIGHTS_0", VertexBuffer.MatricesWeightsKind);
+                loadAttribute("COLOR_0", VertexBuffer.ColorKind);
+
+                var bufferView = GLTFLoader._GetProperty(context, this._loader._gltf.bufferViews, extension.bufferView);
+                return this._loader._loadBufferViewAsync("#/bufferViews/" + bufferView._index, bufferView).then(data => {
+                    try {
+                        return DracoCompression.Decode(data, attributes);
+                    }
+                    catch (e) {
+                        throw new Error(context + ": " + e.message);
+                    }
+                });
+            });
+        }
+    }
+
+    if (DracoCompression.IsSupported) {
+        GLTFLoader._Register(NAME, loader => new KHR_draco_mesh_compression(loader));
+    }
+}

+ 6 - 1
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -452,6 +452,11 @@ module BABYLON.GLTF2 {
         }
 
         private _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Promise<VertexData> {
+            const promise = GLTFLoaderExtension._LoadVertexDataAsync(this, context, primitive, babylonMesh);
+            if (promise) {
+                return promise;
+            }
+
             const attributes = primitive.attributes;
             if (!attributes) {
                 throw new Error(context + ": Attributes are missing");
@@ -1004,7 +1009,7 @@ module BABYLON.GLTF2 {
             return buffer._data;
         }
 
-        private _loadBufferViewAsync(context: string, bufferView: ILoaderBufferView): Promise<ArrayBufferView> {
+        public _loadBufferViewAsync(context: string, bufferView: ILoaderBufferView): Promise<ArrayBufferView> {
             if (bufferView._data) {
                 return bufferView._data;
             }

+ 13 - 3
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -19,6 +19,9 @@ module BABYLON.GLTF2 {
         /** Override this method to modify the default behavior for loading nodes. */
         protected _loadNodeAsync(context: string, node: ILoaderNode): Nullable<Promise<void>> { return null; }
 
+        /** Override this method to modify the default behavior for loading mesh primitive vertex data. */
+        protected _loadVertexDataAsync(context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> { return null; }
+
         /** Override this method to modify the default behavior for loading materials. */
         protected _loadMaterialAsync(context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> { return null; }
 
@@ -28,14 +31,14 @@ module BABYLON.GLTF2 {
         // #endregion
 
         /** Helper method called by a loader extension to load an glTF extension. */
-        protected _loadExtensionAsync<T>(context: string, property: IProperty, actionAsync: (context: string, extension: T) => Promise<void>): Nullable<Promise<void>> {
+        protected _loadExtensionAsync<TProperty, TResult = void>(context: string, property: IProperty, actionAsync: (context: string, extension: TProperty) => Promise<TResult>): Nullable<Promise<TResult>> {
             if (!property.extensions) {
                 return null;
             }
 
             const extensions = property.extensions;
 
-            const extension = extensions[this.name] as T;
+            const extension = extensions[this.name] as TProperty;
             if (!extension) {
                 return null;
             }
@@ -43,9 +46,11 @@ module BABYLON.GLTF2 {
             // Clear out the extension before executing the action to avoid recursing into the same property.
             delete extensions[this.name];
 
-            return actionAsync(context + "/extensions/" + this.name, extension).then(() => {
+            return actionAsync(context + "/extensions/" + this.name, extension).then(result => {
                 // Restore the extension after completing the action.
                 extensions[this.name] = extension;
+
+                return result;
             });
         }
 
@@ -59,6 +64,11 @@ module BABYLON.GLTF2 {
             return loader._applyExtensions(extension => extension._loadNodeAsync(context, node));
         }
 
+        /** Helper method called by the loader to allow extensions to override loading mesh primitive vertex data. */
+        public static _LoadVertexDataAsync(loader: GLTFLoader, context: string, primitive: ILoaderMeshPrimitive, babylonMesh: Mesh): Nullable<Promise<VertexData>> {
+            return loader._applyExtensions(extension => extension._loadVertexDataAsync(context, primitive, babylonMesh));
+        }
+
         /** Helper method called by the loader to allow extensions to override loading materials. */
         public static _LoadMaterialAsync(loader: GLTFLoader, context: string, material: ILoaderMaterial, babylonMesh: Mesh): Nullable<Promise<void>> {
             return loader._applyExtensions(extension => extension._loadMaterialAsync(context, material, babylonMesh));

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 31 - 0
sandbox/draco_decoder.js


+ 3 - 2
sandbox/index-local.html

@@ -5,7 +5,8 @@
     <link href="index.css" rel="stylesheet" />
     <script src="https://babylonjs.azurewebsites.net/cannon.js"></script>
     <script src="https://babylonjs.azurewebsites.net/Oimo.js"></script>
-	<script src="../Tools/DevLoader/BabylonLoader.js"></script>
+    <script src="../Tools/DevLoader/BabylonLoader.js"></script>
+    <script src="draco_decoder.js"></script>
 </head>
 <body>
     <canvas id="renderCanvas"></canvas>
@@ -52,6 +53,6 @@
     <script>
         BABYLONDEVTOOLS.Loader.require('index.js')
             .load();
-    </script>	
+    </script>
 </body>
 </html>

+ 1 - 0
sandbox/index.html

@@ -35,6 +35,7 @@
     <script src="https://preview.babylonjs.com/loaders/babylon.objFileLoader.js"></script>
     <script src="https://preview.babylonjs.com/loaders/babylon.stlFileLoader.js"></script>
 
+    <script src="draco_decoder.js"></script>
 </head>
 
 <body>

+ 102 - 0
src/Mesh/Compression/babylon.dracoCompression.ts

@@ -0,0 +1,102 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts" />
+
+declare var DracoDecoderModule: any;
+
+module BABYLON {
+    /**
+     * Draco compression (https://google.github.io/draco/)
+     */
+    export class DracoCompression {
+        /**
+         * Returns whether Draco compression is supported.
+         */
+        public static get IsSupported(): boolean {
+            return !!window.DracoDecoderModule;
+        }
+
+        /**
+         * Decodes Draco compressed data to vertex data.
+         * @param data The array buffer view for the Draco compression data
+         * @param attributes A map of attributes from vertex buffer kinds to Draco unique ids
+         * @returns The decoded vertex data
+         */
+        public static Decode(data: ArrayBufferView, attributes: { [kind: string]: number }): VertexData {
+            const dracoModule = new DracoDecoderModule();
+            const buffer = new dracoModule.DecoderBuffer();
+            buffer.Init(data, data.byteLength);
+
+            const decoder = new dracoModule.Decoder();
+            let geometry: any;
+            let status: any;
+
+            const vertexData = new VertexData();
+
+            try {
+                const type = decoder.GetEncodedGeometryType(buffer);
+                switch (type) {
+                    case dracoModule.TRIANGULAR_MESH:
+                        geometry = new dracoModule.Mesh();
+                        status = decoder.DecodeBufferToMesh(buffer, geometry);
+                        break;
+                    case dracoModule.POINT_CLOUD:
+                        geometry = new dracoModule.PointCloud();
+                        status = decoder.DecodeBufferToPointCloud(buffer, geometry);
+                        break;
+                    default:
+                        throw new Error("Invalid geometry type");
+                }
+
+                if (!status.ok() || !geometry.ptr) {
+                    throw new Error(status.error_msg());
+                }
+
+                const numPoints = geometry.num_points();
+
+                if (type === dracoModule.TRIANGULAR_MESH) {
+                    const numFaces = geometry.num_faces();
+                    const faceIndices = new dracoModule.DracoInt32Array();
+                    try {
+                        vertexData.indices = new Uint32Array(numFaces * 3);
+                        for (let i = 0; i < numFaces; i++) {
+                            decoder.GetFaceFromMesh(geometry, i, faceIndices);
+                            const offset = i * 3;
+                            vertexData.indices[offset + 0] = faceIndices.GetValue(0);
+                            vertexData.indices[offset + 1] = faceIndices.GetValue(1);
+                            vertexData.indices[offset + 2] = faceIndices.GetValue(2);
+                        }
+                    }
+                    finally {
+                        dracoModule.destroy(faceIndices);
+                    }
+                }
+
+                for (const kind in attributes) {
+                    const uniqueId = attributes[kind];
+                    const attribute = decoder.GetAttributeByUniqueId(geometry, uniqueId);
+                    const dracoData = new dracoModule.DracoFloat32Array();
+                    try {
+                        decoder.GetAttributeFloatForAllPoints(geometry, attribute, dracoData);
+                        const data = new Float32Array(numPoints * VertexBuffer.DeduceStride(kind));
+                        for (let i = 0; i < data.length; i++) {
+                            data[i] = dracoData.GetValue(i);
+                        }
+                        vertexData.set(data, kind);
+                    }
+                    finally {
+                        dracoModule.destroy(dracoData);
+                    }
+                }
+            }
+            finally {
+                if (geometry) {
+                    dracoModule.destroy(geometry);
+                }
+
+                dracoModule.destroy(decoder);
+                dracoModule.destroy(buffer);
+            }
+
+            return vertexData;
+        }
+    }
+}

+ 4 - 7
src/Mesh/babylon.vertexBuffer.ts

@@ -237,10 +237,6 @@
          */
         public static DeduceStride(kind: string): number {
             switch (kind) {
-                case VertexBuffer.PositionKind:
-                    return 3;
-                case VertexBuffer.NormalKind:
-                    return 3;
                 case VertexBuffer.UVKind:
                 case VertexBuffer.UV2Kind:
                 case VertexBuffer.UV3Kind:
@@ -248,14 +244,15 @@
                 case VertexBuffer.UV5Kind:
                 case VertexBuffer.UV6Kind:
                     return 2;
-                case VertexBuffer.TangentKind:
+                case VertexBuffer.NormalKind:
+                case VertexBuffer.PositionKind:
+                    return 3;
                 case VertexBuffer.ColorKind:
-                    return 4;
                 case VertexBuffer.MatricesIndicesKind:
                 case VertexBuffer.MatricesIndicesExtraKind:
-                    return 4;
                 case VertexBuffer.MatricesWeightsKind:
                 case VertexBuffer.MatricesWeightsExtraKind:
+                case VertexBuffer.TangentKind:
                     return 4;
                 default:
                     throw new Error("Invalid kind '" + kind + "'");

+ 1 - 0
src/babylon.mixins.ts

@@ -18,6 +18,7 @@ interface Window {
     mozURL: typeof URL;
     msURL: typeof URL;
     VRFrameData: any; // WebVR, from specs 1.1
+    DracoDecoderModule: any;
 }
 
 interface WebGLRenderingContext {