Exemple #1
0
    protected override async Task ConstructScene(GLTFScene scene, bool showSceneObj, CancellationToken cancellationToken)
    {
        // calc total vert/poly count

        /* int vertCount = 0, polyCount = 0;
         * foreach (var mesh in _gltfRoot.Meshes)
         * {
         *      foreach (var prim in mesh.Primitives)
         *      {
         *              var localVertCount = (int) prim.Attributes[SemanticProperties.POSITION].Value.Count;
         *              vertCount += localVertCount;
         *              polyCount += ((int?)prim.Indices?.Value.Count ?? localVertCount) / 3;
         *      }
         * }
         *
         * if (vertCount > VERTEX_LIMIT)
         * {
         *      throw new Exception($"Requested glTF {_gltfFileName} has too many vertices ({vertCount:N} > {VERTEX_LIMIT:N})");
         * }
         *
         * if (polyCount > POLYGON_LIMIT)
         * {
         *      throw new Exception($"Requested glTF {_gltfFileName} has too many polygons ({polyCount:N} > {POLYGON_LIMIT:N})");
         * } */

        await base.ConstructScene(scene, showSceneObj, cancellationToken);

        foreach (var skin in CreatedObject.GetComponentsInChildren <SkinnedMeshRenderer>(true))
        {
            skin.quality = SkinQuality.Bone2;
        }
    }
Exemple #2
0
        private SceneId ExportScene(string name, Transform[] rootObjTransforms)
        {
            var scene = new GLTFScene();

            if (ExportNames)
            {
                scene.Name = name;
            }

            scene.Nodes = new List <NodeId>(rootObjTransforms.Length);
            foreach (var transform in rootObjTransforms)
            {
                scene.Nodes.Add(ExportNode(transform));
            }

            _root.Scenes.Add(scene);

            return(new SceneId {
                Id = _root.Scenes.Count - 1,
                Root = _root
            });
        }
Exemple #3
0
        public void ExportGltf(BabylonScene babylonScene, string outputDirectory, string outputFileName, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            // Force output file extension to be gltf
            outputFileName = Path.ChangeExtension(outputFileName, "gltf");

            // Update path of output .gltf file to include subdirectory
            var outputFile = Path.Combine(outputDirectory, outputFileName);

            float progressionStep;
            var   progression = 0.0f;

            ReportProgressChanged((int)progression);

            // Initialization
            initBabylonNodes(babylonScene);
            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                generator = $"babylon.js glTF exporter for maya 2018 v{exporterVersion}",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Meshes
            RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            }

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);

            progressionStep = 40.0f / babylonRootNodes.Count;
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            logRankTexture = 2;
            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            }
            ;
            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);

            // Animations
            RaiseMessage("GLTFExporter | Exporting Animations");
            ExportAnimationGroups(gltf, babylonScene);

            // Prepare buffers
            gltf.BuffersList.ForEach(buffer =>
            {
                buffer.BufferViews.ForEach(bufferView =>
                {
                    bufferView.Accessors.ForEach(accessor =>
                    {
                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
                        accessor.bytesList = new List <byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));

                        // Update byte properties
                        accessor.byteOffset    = bufferView.byteLength;
                        bufferView.byteLength += accessor.bytesList.Count;
                        // Merge bytes
                        bufferView.bytesList.AddRange(accessor.bytesList);
                    });
                    // Update byte properties
                    bufferView.byteOffset = buffer.byteLength;
                    buffer.byteLength    += bufferView.bytesList.Count;
                    // Merge bytes
                    buffer.bytesList.AddRange(bufferView.bytesList);
                });
            });

            // Cast lists to arrays
            gltf.Prepare();

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            if (!generateBinary)
            {
                // Write .gltf file
                string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                File.WriteAllText(outputGltfFile, gltfToJson(gltf));

                // Write .bin file
                string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
                {
                    gltf.BuffersList.ForEach(buffer =>
                    {
                        buffer.bytesList.ForEach(b => writer.Write(b));
                    });
                }
            }
            else
            {
                // Export glTF data to binary format .glb

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary
                var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
                imageBufferViews.ForEach(imageBufferView =>
                {
                    imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
                });
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                chunkDataJson = padChunk(chunkDataJson, 4, 0x20);
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }


                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            ReportProgressChanged(100);
        }
        public void ExportGltf(BabylonScene babylonScene, string outputDirectory, string outputFileName, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            // Force output file extension to be gltf
            outputFileName = Path.ChangeExtension(outputFileName, "gltf");

            // Update path of output .gltf file to include subdirectory
            var outputFile = Path.Combine(outputDirectory, outputFileName);

            float progressionStep;
            var   progression = 0.0f;

            ReportProgressChanged((int)progression);

            // Initialization
            initBabylonNodes(babylonScene);
            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version = "2.0"
                          // no minVersion
            };

            string maxVersion = null;

#if MAX2015
            maxVersion = "2015";
#elif MAX2017
            maxVersion = "2017";
#elif MAX2018
            maxVersion = "2018";
#elif MAX2019
            maxVersion = "2019";
#endif
            gltf.asset.generator = $"babylon.js glTF exporter for 3ds max {maxVersion} v{exporterVersion}";

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene   scene  = new GLTFScene();
            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Meshes
            RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            }


            // Root nodes
            RaiseMessage("GLTFExporter | Exporting nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            progressionStep          = 40.0f / babylonRootNodes.Count;
            alreadyExportedSkeletons = new Dictionary <BabylonSkeleton, BabylonSkeletonExportData>();
            nodeToGltfNodeMap        = new Dictionary <BabylonNode, GLTFNode>();
            boneToGltfNodeMap        = new Dictionary <BabylonBone, GLTFNode>();
            NbNodesByName            = new Dictionary <string, int>();
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            }
            ;
            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);

            // Animations
            RaiseMessage("GLTFExporter | Exporting Animations");
            ExportAnimationGroups(gltf, babylonScene);

            // Prepare buffers
            gltf.BuffersList.ForEach(buffer =>
            {
                buffer.BufferViews.ForEach(bufferView =>
                {
                    bufferView.Accessors.ForEach(accessor =>
                    {
                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
                        accessor.bytesList = new List <byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));

                        // Update byte properties
                        accessor.byteOffset    = bufferView.byteLength;
                        bufferView.byteLength += accessor.bytesList.Count;
                        // Merge bytes
                        bufferView.bytesList.AddRange(accessor.bytesList);
                    });
                    // Update byte properties
                    bufferView.byteOffset = buffer.byteLength;
                    buffer.byteLength    += bufferView.bytesList.Count;
                    // Merge bytes
                    buffer.bytesList.AddRange(bufferView.bytesList);
                });
            });

            // Cast lists to arrays
            gltf.Prepare();

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            if (!generateBinary)
            {
                // Write .gltf file
                string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                File.WriteAllText(outputGltfFile, gltfToJson(gltf));

                // Write .bin file
                string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
                {
                    gltf.BuffersList.ForEach(buffer =>
                    {
                        buffer.bytesList.ForEach(b => writer.Write(b));
                    });
                }
            }
            else
            {
                // Export glTF data to binary format .glb

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary
                var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
                imageBufferViews.ForEach(imageBufferView =>
                {
                    imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
                });
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                chunkDataJson = padChunk(chunkDataJson, 4, 0x20);
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }

                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            // Draco compression
            if (exportParameters.dracoCompression)
            {
                RaiseMessage("GLTFExporter | Draco compression");

                try
                {
                    Process gltfPipeline = new Process();

                    // Hide the cmd window that show the gltf-pipeline result
                    //gltfPipeline.StartInfo.UseShellExecute = false;
                    //gltfPipeline.StartInfo.CreateNoWindow = true;
                    gltfPipeline.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

                    string arg;
                    if (generateBinary)
                    {
                        string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                        arg = $" -i {outputGlbFile} -o {outputGlbFile} -d";
                    }
                    else
                    {
                        string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                        arg = $" -i {outputGltfFile} -o {outputGltfFile} -d -s";
                    }
                    gltfPipeline.StartInfo.FileName  = "gltf-pipeline.cmd";
                    gltfPipeline.StartInfo.Arguments = arg;

                    gltfPipeline.Start();
                    gltfPipeline.WaitForExit();
                }
                catch
                {
                    RaiseError("gltf-pipeline module not found.", 1);
                    RaiseError("The exported file wasn't compressed.");
                }
            }

            ReportProgressChanged(100);
        }
        public void ExportGltf(ExportParameters exportParameters, BabylonScene babylonScene, string outputDirectory, string outputFileName, bool generateBinary, ILoggingProvider logger)
        {
            this.exportParameters = exportParameters;
            this.logger           = logger;

            logger.RaiseMessage("GLTFExporter | Exportation started", Color.Blue);
#if DEBUG
            var watch = new Stopwatch();
            watch.Start();
#endif
            this.babylonScene = babylonScene;

            // Force output file extension to be gltf
            outputFileName = Path.ChangeExtension(outputFileName, "gltf");

            // Update path of output .gltf file to include subdirectory
            var outputFile = Path.Combine(outputDirectory, outputFileName);

            float progressionStep;
            var   progression = 0.0f;
            logger.ReportProgressChanged((int)progression);

            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version = "2.0"
                          // no minVersion
            };

            var softwarePackageName = babylonScene.producer != null ? babylonScene.producer.name : "";
            var softwareVersion     = babylonScene.producer != null ? babylonScene.producer.version : "";
            var exporterVersion     = babylonScene.producer != null ? babylonScene.producer.exporter_version : "";

            gltf.asset.generator = $"babylon.js glTF exporter for {softwarePackageName} {softwareVersion} v{exporterVersion}";

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();
            ExportGLTFExtension(babylonScene, ref scene, gltf);
            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Initialization
            initBabylonNodes(babylonScene, gltf);


            // Root nodes
            logger.RaiseMessage("GLTFExporter | Exporting nodes");
            progression = 30.0f;
            logger.ReportProgressChanged((int)progression);
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            progressionStep          = 30.0f / babylonRootNodes.Count;
            alreadyExportedSkeletons = new Dictionary <BabylonSkeleton, BabylonSkeletonExportData>();
            nodeToGltfNodeMap        = new Dictionary <BabylonNode, GLTFNode>();
            NbNodesByName            = new Dictionary <string, int>();
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                logger.ReportProgressChanged((int)progression);
                logger.CheckCancelled();
            });
#if DEBUG
            var nodesExportTime = watch.ElapsedMilliseconds / 1000.0;
            logger.RaiseMessage(string.Format("GLTFNodes exported in {0:0.00}s", nodesExportTime), Color.Blue);
#endif

            // Meshes
            logger.RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            logger.ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                logger.ReportProgressChanged((int)progression);
                logger.CheckCancelled();
            }
#if DEBUG
            var meshesExportTime = watch.ElapsedMilliseconds / 1000.0 - nodesExportTime;
            logger.RaiseMessage(string.Format("GLTFMeshes exported in {0:0.00}s", meshesExportTime), Color.Blue);
#endif

            //Mesh Skins, light and Cameras
            logger.RaiseMessage("GLTFExporter | Exporting skins, lights and cameras");
            progression = 50.0f;
            logger.ReportProgressChanged((int)progression);
            progressionStep = 50.0f / babylonRootNodes.Count;
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeTypeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                logger.ReportProgressChanged((int)progression);
                logger.CheckCancelled();
            });
#if DEBUG
            var skinLightCameraExportTime = watch.ElapsedMilliseconds / 1000.0 - meshesExportTime;
            logger.RaiseMessage(string.Format("GLTFSkin GLTFLights GLTFCameras exported in {0:0.00}s", skinLightCameraExportTime), Color.Blue);
#endif
            // Materials
            progression = 70.0f;
            logger.ReportProgressChanged((int)progression);
            logger.RaiseMessage("GLTFExporter | Exporting materials");

            if (exportParameters.tryToReuseOpaqueAndBlendTexture)
            {
                // we MUST sort the material in order to let BabylonPBRMetallicRoughnessMaterial with Alpha first.
                // the reason is the sequential write pattern
                babylonMaterialsToExport = SortMaterialPriorToOptimizeTextureUsage(gltf, babylonMaterialsToExport).ToList();
            }

            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                logger.CheckCancelled();
            }
            ;
            logger.RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
#if DEBUG
            var materialsExportTime = watch.ElapsedMilliseconds / 1000.0 - nodesExportTime;
            logger.RaiseMessage(string.Format("GLTFMaterials exported in {0:0.00}s", materialsExportTime), Color.Blue);
#endif
            // Animations
            progression = 90.0f;
            logger.ReportProgressChanged((int)progression);
            logger.RaiseMessage("GLTFExporter | Exporting Animations");
            ExportAnimationGroups(gltf, babylonScene);
#if DEBUG
            var animationGroupsExportTime = watch.ElapsedMilliseconds / 1000.0 - materialsExportTime;
            logger.RaiseMessage(string.Format("GLTFAnimations exported in {0:0.00}s", animationGroupsExportTime), Color.Blue);
#endif
            // Prepare buffers
            gltf.BuffersList.ForEach(buffer =>
            {
                buffer.BufferViews.ForEach(bufferView =>
                {
                    bufferView.Accessors.ForEach(accessor =>
                    {
                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
                        accessor.bytesList = new List <byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));

                        // Update byte properties
                        accessor.byteOffset    = bufferView.byteLength;
                        bufferView.byteLength += accessor.bytesList.Count;
                        // Merge bytes
                        bufferView.bytesList.AddRange(accessor.bytesList);
                    });
                    // Update byte properties
                    bufferView.byteOffset = buffer.byteLength;
                    buffer.byteLength    += bufferView.bytesList.Count;
                    // Merge bytes
                    buffer.bytesList.AddRange(bufferView.bytesList);
                });
            });

            // Cast lists to arrays
            gltf.Prepare();

            // Output
            logger.RaiseMessage("GLTFExporter | Saving to output file");
            if (!generateBinary)
            {
                // Write .gltf file
                string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                File.WriteAllText(outputGltfFile, gltfToJson(gltf));

                // Write .bin file
                string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
                {
                    gltf.BuffersList.ForEach(buffer =>
                    {
                        buffer.bytesList.ForEach(b => writer.Write(b));
                    });
                }
            }
            else
            {
                // Export glTF data to binary format .glb

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.UTF8.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                chunkDataJson = padChunk(chunkDataJson, 4, 0x20);
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }

                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            // Draco compression
            if (exportParameters.dracoCompression)
            {
                logger.RaiseMessage("GLTFExporter | Draco compression");
                GLTFPipelineUtilities.DoDracoCompression(logger, generateBinary, outputFile, exportParameters.dracoParams);
            }

            logger.ReportProgressChanged(100);
        }
Exemple #6
0
        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary, bool exportGltfImagesAsBinary)
        {
            RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            float progressionStep;
            var   progression = 0.0f;

            ReportProgressChanged((int)progression);

            // Initialization
            initBabylonNodes(babylonScene);
            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                generator = "Babylon2Gltf2017",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Meshes
            RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            }

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);

            progressionStep = 40.0f / babylonRootNodes.Count;
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // TODO - Choose between this method and the reverse of X axis
            // Switch from left to right handed coordinate system
            RaiseMessage("GLTFExporter | Exporting root node");
            var tmpNodesList = new List <int>(scene.NodesList);
            var rootNode     = new BabylonMesh
            {
                name            = "root",
                rotation        = new float[] { 0, (float)Math.PI, 0 },
                scaling         = new float[] { 1, 1, -1 },
                idGroupInstance = -1
            };

            scene.NodesList.Clear();
            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);

            gltfRootNode.ChildrenList.AddRange(tmpNodesList);

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            }
            ;
            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);

            if (exportGltfImagesAsBinary)
            {
                SwitchImagesFromUriToBinary(gltf);
            }

            // Cast lists to arrays
            gltf.Prepare();

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");

            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");

            File.WriteAllText(outputGltfFile, gltfToJson(gltf));

            // Write data to binary file
            string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");

            using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
            {
                gltf.BuffersList.ForEach(buffer =>
                {
                    buffer.bytesList = new List <byte>();
                    gltf.BufferViewsList.FindAll(bufferView => bufferView.buffer == buffer.index).ForEach(bufferView =>
                    {
                        bufferView.bytesList.ForEach(b => writer.Write(b));
                        buffer.bytesList.AddRange(bufferView.bytesList);
                    });
                });
            }

            // Binary
            if (generateBinary)
            {
                // Export glTF data to binary format .glb
                RaiseMessage("GLTFExporter | Generating .glb file");

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary if not already done
                // TODO - make it optional
                if (!exportGltfImagesAsBinary)
                {
                    var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
                    imageBufferViews.ForEach(imageBufferView =>
                    {
                        imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
                    });
                }
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                var nbSpaceToAdd      = chunkDataJson.Length % 4 == 0 ? 0 : (4 - chunkDataJson.Length % 4);
                var chunkDataJsonList = new List <byte>(chunkDataJson);
                for (int i = 0; i < nbSpaceToAdd; i++)
                {
                    chunkDataJsonList.Add(0x20);
                }
                chunkDataJson = chunkDataJsonList.ToArray();
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }


                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            ReportProgressChanged(100);
        }
Exemple #7
0
        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            float progressionStep;
            var   progression = 0.0f;

            ReportProgressChanged((int)progression);

            // Initialization
            initBabylonNodes(babylonScene);
            babylonMaterialsToExport = new List <BabylonMaterial>();

            var gltf = new GLTF(Path.GetDirectoryName(outputFile));

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                generator = "Babylon2Gltf2017",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Meshes
            RaiseMessage("GLTFExporter | Exporting meshes");
            progression = 10.0f;
            ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            }

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);

            progressionStep = 40.0f / babylonRootNodes.Count;
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // TODO - Choose between this method and the reverse of X axis
            // Switch from left to right handed coordinate system
            RaiseMessage("GLTFExporter | Exporting root node");
            var tmpNodesList = new List <int>(scene.NodesList);
            var rootNode     = new BabylonMesh
            {
                name            = "root",
                rotation        = new float[] { 0, (float)Math.PI, 0 },
                scaling         = new float[] { 1, 1, -1 },
                idGroupInstance = -1
            };

            scene.NodesList.Clear();
            GLTFNode gltfRootNode = ExportAbstractMesh(rootNode, gltf, null);

            gltfRootNode.ChildrenList.AddRange(tmpNodesList);

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            foreach (var babylonMaterial in babylonMaterialsToExport)
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            }
            ;
            RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            // Cast lists to arrays
            gltf.Prepare();
            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings());
            var sb             = new StringBuilder();
            var sw             = new StringWriter(sb, CultureInfo.InvariantCulture);

            // Do not use the optimized writer because it's not necessary to truncate values
            // Use the bounded writer in case some values are infinity ()
            using (var jsonWriter = new JsonTextWriterBounded(sw))
            {
                jsonWriter.Formatting = Formatting.None;
                jsonSerializer.Serialize(jsonWriter, gltf);
            }
            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");

            File.WriteAllText(outputGltfFile, sb.ToString());

            // Binary
            if (generateBinary)
            {
                // TODO - Export glTF data to binary format .glb
                RaiseError("GLTFExporter | TODO - Generating binary files");
            }

            ReportProgressChanged(100);
        }
Exemple #8
0
        public static GLTFRoot ExportModel(string path)
        {
            using var stream = File.OpenRead(path);
            var pokemonModel   = PokemonModel.GetRootAsPokemonModel(stream.ToByteBuffer());
            var bufferRoot     = Path.GetDirectoryName(path);
            var bufferRootFile = Path.Combine(bufferRoot, $"{Path.GetFileNameWithoutExtension(path)}.mdlbin");

            using var buffer = File.OpenWrite(bufferRootFile);
            buffer.SetLength(0);

            var gltfRoot = new GLTFRoot
            {
                Asset = new GLTFAsset
                {
                    Generator = "GFLX",
                    Copyright = "2019 GameFreak - Pokemon Company",
                    Version   = "2.0"
                }
            };
            var gltfBuffer = gltfRoot.Buffers.Count;
            var materials  = DeconstructMaterials(pokemonModel, gltfRoot);

            var scene = new GLTFScene();

            for (var i = 0; i < pokemonModel.VisualGroupLength; ++i)
            {
                var group      = pokemonModel.VisualGroup(i).GetValueOrDefault();
                var mesh       = pokemonModel.Meshes((int)group.MeshId).GetValueOrDefault();
                var bone       = pokemonModel.Bones((int)group.BoneId).GetValueOrDefault();
                var attributes = DeconstructVbo(mesh, gltfRoot, buffer, gltfBuffer);
                var node       = new GLTFMesh
                {
                    Name = bone.Name
                };
                for (var j = 0; j < mesh.IndiceBufferLength; ++j)
                {
                    var primitive = mesh.IndiceBuffer(j).GetValueOrDefault();
                    node.Primitives.Add(new GLTFMeshPrimitive
                    {
                        Attributes = attributes,
                        Indices    = gltfRoot.Accessors.Count,
                        Mode       = GLTFDrawMode.Triangles,
                        Material   = materials[primitive.MaterialId]
                    });
                    gltfRoot.Accessors.Add(new GLTFAccessor
                    {
                        BufferView    = gltfRoot.BufferViews.Count,
                        ByteOffset    = 0,
                        ComponentType = GLTFComponentType.UnsignedShort,
                        Count         = (uint)primitive.IndicesLength,
                        Type          = GLTFAccessorAttributeType.SCALAR
                    });
                    var targetBuffer = MemoryMarshal.Cast <ushort, byte>(primitive.GetIndicesBytes());
                    gltfRoot.BufferViews.Add(new GLTFBufferView
                    {
                        Buffer     = gltfBuffer,
                        ByteOffset = (uint)buffer.Position,
                        ByteLength = (uint)targetBuffer.Length
                    });
                    buffer.Write(targetBuffer);
                }

                scene.Nodes.Add(gltfRoot.Nodes.Count);
                gltfRoot.Nodes.Add(new GLTFNode
                {
                    Mesh = gltfRoot.Meshes.Count,
                    Name = node.Name
                });
                gltfRoot.Meshes.Add(node);
            }

            gltfRoot.Scenes.Add(scene);
            gltfRoot.Buffers.Add(new GLTFBuffer
            {
                Uri        = Path.GetFileName(bufferRootFile),
                ByteLength = (uint)buffer.Length
            });

            return(gltfRoot);
        }
Exemple #9
0
        //public Dictionary<BabylonCamera, GLTFCamera> cameraToGltfCameraMap; //todo: fill this

        public void ExportGltf(ExportParameters exportParameters, BabylonScene babylonScene, string outputDirectory, string outputFileName, bool generateBinary, ILoggingProvider logger)
        {
            this.exportParameters = exportParameters;
            this.logger           = logger;

            logger?.Print("GLTFExporter | Exportation started", Color.Blue);
#if DEBUG
            var watch = new Stopwatch();
            watch.Start();
#endif
            this.babylonScene = babylonScene;

            // Force output file extension to be gltf
            outputFileName = Path.ChangeExtension(outputFileName, "gltf");

            // Update path of output .gltf file to include subdirectory
            var outputFile = Path.Combine(outputDirectory, outputFileName);

            float progressionStep;
            var   progression = 0.0f;
            logger?.ReportProgressChanged((int)progression);

            babylonMaterials = new List <BabylonMaterial>();

            var gltf = new GLTF(outputFile);

            // Asset
            gltf.asset = new GLTFAsset
            {
                version = "2.0"
                          // no minVersion
            };

            FlightSimExtension.FlightSimNormalMapConvention.AddNormalMapConvention(ref gltf, exportParameters);

            var softwarePackageName = babylonScene.producer != null ? babylonScene.producer.name : "";
            var softwareVersion     = babylonScene.producer != null ? babylonScene.producer.version : "";
            var exporterVersion     = babylonScene.producer != null ? babylonScene.producer.exporter_version : "";

            gltf.asset.generator = $"babylon.js glTF exporter for {softwarePackageName} {softwareVersion} v{exporterVersion}";

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene        scene     = new GLTFScene();
            List <GLTFScene> subScenes = new List <GLTFScene>();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Initialization
            initBabylonNodes(babylonScene, gltf);

            // Root nodes
            logger?.Print("GLTFExporter | Exporting nodes", Color.Black);
            progression = 30.0f;
            logger?.ReportProgressChanged((int)progression);
            List <BabylonNode> babylonRootNodes;
            if (exportParameters.exportAsSubmodel)
            {
                babylonRootNodes = babylonNodes.FindAll(node => node.subModelRoot == true);
            }
            else
            {
                babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            }

            progressionStep           = 30.0f / babylonRootNodes.Count;
            alreadyExportedSkeletons  = new Dictionary <BabylonSkeleton, BabylonSkeletonExportData>();
            nodeToGltfNodeMap         = new Dictionary <BabylonNode, GLTFNode>();
            materialToGltfMaterialMap = new Dictionary <BabylonMaterial, GLTFMaterial>();
            //cameraToGltfCameraMap = new Dictionary<BabylonCamera, GLTFCamera>();
            NbNodesByName = new Dictionary <string, int>();
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                logger?.ReportProgressChanged((int)progression);
                //logger?.CheckCancelled(this);
            });

#if DEBUG
            var nodesExportTime = watch.ElapsedMilliseconds / 1000.0;
            logger?.Print(string.Format("GLTFNodes exported in {0:0.00}s", nodesExportTime), Color.Blue);
#endif

            // Meshes
            logger?.Print("GLTFExporter | Exporting meshes", Color.Black);
            progression = 10.0f;
            logger?.ReportProgressChanged((int)progression);
            progressionStep = 40.0f / babylonScene.meshes.Length;
            foreach (var babylonMesh in babylonScene.meshes)
            {
                ExportMesh(babylonMesh, gltf, babylonScene);
                progression += progressionStep;
                logger?.ReportProgressChanged((int)progression);
                //logger?.CheckCancelled(this);
            }
#if DEBUG
            var meshesExportTime = watch.ElapsedMilliseconds / 1000.0 - nodesExportTime;
            logger?.Print(string.Format("GLTFMeshes exported in {0:0.00}s", meshesExportTime), Color.Blue);
#endif

            //Mesh Skins, light and Cameras
            logger?.Print("GLTFExporter | Exporting skins, lights and cameras", Color.Black);
            progression = 50.0f;
            logger?.ReportProgressChanged((int)progression);
            progressionStep = 50.0f / babylonRootNodes.Count;
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeTypeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                logger?.ReportProgressChanged((int)progression);
                //logger?.CheckCancelled(this);
            });
#if DEBUG
            var skinLightCameraExportTime = watch.ElapsedMilliseconds / 1000.0 - meshesExportTime;
            logger?.Print(string.Format("GLTFSkin GLTFLights GLTFCameras exported in {0:0.00}s", skinLightCameraExportTime), Color.Blue);
#endif
            // Materials
            progression = 70.0f;
            logger?.ReportProgressChanged((int)progression);
            logger?.Print("GLTFExporter | Exporting materials", Color.Black);
            foreach (var babylonMaterial in babylonMaterials)
            {
                ExportMaterial(babylonMaterial, gltf);
                //logger?.CheckCancelled(this);
            }
            ;
            logger?.RaiseMessage(string.Format("GLTFExporter | Nb materials exported: {0}", gltf.MaterialsList.Count), Color.Gray, 1);
#if DEBUG
            var materialsExportTime = watch.ElapsedMilliseconds / 1000.0 - nodesExportTime;
            logger?.Print(string.Format("GLTFMaterials exported in {0:0.00}s", materialsExportTime), Color.Blue);
#endif

            if (exportParameters.animationExportType != AnimationExportType.NotExport)
            {
                // Animations
                progression = 90.0f;
                logger?.ReportProgressChanged((int)progression);
                logger?.Print("GLTFExporter | Exporting animations", Color.Black);
                ExportAnimationGroups(gltf, babylonScene);
#if DEBUG
                var animationGroupsExportTime = watch.ElapsedMilliseconds / 1000.0 - materialsExportTime;
                logger?.Print(string.Format("GLTFAnimations exported in {0:0.00}s", animationGroupsExportTime),
                              Color.Blue);
#endif
            }

            // Prepare buffers
            gltf.BuffersList.ForEach(buffer =>
            {
                buffer.BufferViews.ForEach(bufferView =>
                {
                    bufferView.Accessors.ForEach(accessor =>
                    {
                        // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements
                        accessor.bytesList = new List <byte>(padChunk(accessor.bytesList.ToArray(), 4, 0x00));

                        // Update byte properties
                        accessor.byteOffset    = bufferView.byteLength;
                        bufferView.byteLength += accessor.bytesList.Count;
                        // Merge bytes
                        bufferView.bytesList.AddRange(accessor.bytesList);
                    });
                    // Update byte properties
                    bufferView.byteOffset = buffer.byteLength;
                    buffer.byteLength    += bufferView.bytesList.Count;
                    // Merge bytes
                    buffer.bytesList.AddRange(bufferView.bytesList);
                });
            });

            //remove all namespaces of node:
            // char0:x0_name_left -> x0_name_left
            if (exportParameters.removeNamespaces)
            {
                foreach (GLTFNode gltfNode in gltf.NodesList)
                {
                    int indexNamespaceChar = gltfNode.name.LastIndexOf(":");
                    if (indexNamespaceChar > 0)
                    {
                        gltfNode.name = gltfNode.name.Substring(indexNamespaceChar + 1);
                    }
                }
            }

            //remove LOD prefix of node of node:
            //x0_name_left -> name_left
            if (exportParameters.removeLodPrefix)
            {
                foreach (GLTFNode gltfNode in gltf.NodesList)
                {
                    string pattern = "(?i)x[0-9]_";
                    string result  = Regex.Replace(gltfNode.name, pattern, "");
                    gltfNode.name = result;
                }
            }

            //remove empty space at end
            foreach (GLTFNode gltfNode in gltf.NodesList)
            {
                gltfNode.name = gltfNode.name.TrimEnd();
            }


            for (int i = 0; i < gltf.scenes.Count(); i++)
            {
                ExportGLTFExtension(babylonScene, ref gltf.scenes[i], gltf);
            }

            // Output
            logger?.Print("GLTFExporter | Saving to output file", Color.Black);
            if (!generateBinary)
            {
                // Cast lists to arrays
                gltf.Prepare();

                // Write .gltf file
                string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                File.WriteAllText(outputGltfFile, gltfToJson(gltf));

                // Write .bin file
                string outputBinaryFile = Path.ChangeExtension(outputFile, "bin");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputBinaryFile, FileMode.Create)))
                {
                    gltf.BuffersList.ForEach(buffer =>
                    {
                        buffer.bytesList.ForEach(b => writer.Write(b));
                    });
                }
            }
            else
            {
                // Export glTF data to binary format .glb

                // Header
                UInt32 magic   = 0x46546C67; // ASCII code for glTF
                UInt32 version = 2;
                UInt32 length  = 12;         // Header length

                // --- JSON chunk ---
                UInt32 chunkTypeJson = 0x4E4F534A; // ASCII code for JSON
                // Remove buffers uri
                foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                {
                    gltfBuffer.uri = null;
                }
                // Switch images to binary
                var imageBufferViews = SwitchImagesFromUriToBinary(gltf);
                imageBufferViews.ForEach(imageBufferView =>
                {
                    imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList);
                });
                gltf.Prepare();
                // Serialize gltf data to JSON string then convert it to bytes
                byte[] chunkDataJson = Encoding.ASCII.GetBytes(gltfToJson(gltf));
                // JSON chunk must be padded with trailing Space chars (0x20) to satisfy alignment requirements
                chunkDataJson = padChunk(chunkDataJson, 4, 0x20);
                UInt32 chunkLengthJson = (UInt32)chunkDataJson.Length;
                length += chunkLengthJson + 8; // 8 = JSON chunk header length

                // bin chunk
                UInt32 chunkTypeBin   = 0x004E4942; // ASCII code for BIN
                UInt32 chunkLengthBin = 0;
                if (gltf.BuffersList.Count > 0)
                {
                    foreach (GLTFBuffer gltfBuffer in gltf.BuffersList)
                    {
                        chunkLengthBin += (uint)gltfBuffer.byteLength;
                    }
                    length += chunkLengthBin + 8; // 8 = bin chunk header length
                }

                // Write binary file
                string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                using (BinaryWriter writer = new BinaryWriter(File.Open(outputGlbFile, FileMode.Create)))
                {
                    // Header
                    writer.Write(magic);
                    writer.Write(version);
                    writer.Write(length);

                    // JSON chunk
                    writer.Write(chunkLengthJson);
                    writer.Write(chunkTypeJson);
                    writer.Write(chunkDataJson);

                    // bin chunk
                    if (gltf.BuffersList.Count > 0)
                    {
                        writer.Write(chunkLengthBin);
                        writer.Write(chunkTypeBin);
                        gltf.BuffersList[0].bytesList.ForEach(b => writer.Write(b));
                    }
                };
            }

            // Draco compression
            if (exportParameters.dracoCompression)
            {
                logger?.RaiseMessage("GLTFExporter | Draco compression");

                try
                {
                    Process gltfPipeline = new Process();

                    // Hide the cmd window that show the gltf-pipeline result
                    //gltfPipeline.StartInfo.UseShellExecute = false;
                    //gltfPipeline.StartInfo.CreateNoWindow = true;
                    gltfPipeline.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

                    string arg;
                    if (generateBinary)
                    {
                        string outputGlbFile = Path.ChangeExtension(outputFile, "glb");
                        arg = $" -i {outputGlbFile} -o {outputGlbFile} -d";
                    }
                    else
                    {
                        string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");
                        arg = $" -i {outputGltfFile} -o {outputGltfFile} -d -s";
                    }
                    gltfPipeline.StartInfo.FileName  = "gltf-pipeline.cmd";
                    gltfPipeline.StartInfo.Arguments = arg;

                    gltfPipeline.Start();
                    gltfPipeline.WaitForExit();
                }
                catch
                {
                    logger?.RaiseError("gltf-pipeline module not found.", 1);
                    logger?.RaiseError("The exported file wasn't compressed.");
                }
            }

            logger?.ReportProgressChanged(100);
        }
        public void ExportGltf(BabylonScene babylonScene, string outputFile, bool generateBinary)
        {
            RaiseMessage("GLTFExporter | Export outputFile=" + outputFile + " generateBinary=" + generateBinary);
            RaiseMessage("GLTFExporter | Exportation started", Color.Blue);

            ReportProgressChanged(0);
            var gltf = new GLTF(Path.GetDirectoryName(outputFile));

            // Asset
            gltf.asset = new GLTFAsset
            {
                version   = "2.0",
                generator = "Babylon2Gltf2017",
                copyright = "2017 (c) BabylonJS"
                            // no minVersion
            };

            // Scene
            gltf.scene = 0;

            // Scenes
            GLTFScene scene = new GLTFScene();

            GLTFScene[] scenes = { scene };
            gltf.scenes = scenes;

            // Materials
            RaiseMessage("GLTFExporter | Exporting materials");
            ReportProgressChanged(10);
            var babylonMaterials = babylonScene.MaterialsList;

            babylonMaterials.ForEach((babylonMaterial) =>
            {
                ExportMaterial(babylonMaterial, gltf);
                CheckCancelled();
            });
            // TODO - Handle multimaterials
            RaiseMessage(string.Format("GLTFExporter | Total: {0}", gltf.MaterialsList.Count /*+ glTF.MultiMaterialsList.Count*/), Color.Gray, 1);

            // Nodes
            List <BabylonNode> babylonNodes = new List <BabylonNode>();

            babylonNodes.AddRange(babylonScene.meshes);
            babylonNodes.AddRange(babylonScene.lights);
            babylonNodes.AddRange(babylonScene.cameras);

            // Root nodes
            RaiseMessage("GLTFExporter | Exporting root nodes");
            List <BabylonNode> babylonRootNodes = babylonNodes.FindAll(node => node.parentId == null);
            var progressionStep = 80.0f / babylonRootNodes.Count;
            var progression     = 20.0f;

            ReportProgressChanged((int)progression);
            babylonRootNodes.ForEach(babylonNode =>
            {
                exportNodeRec(babylonNode, gltf, babylonScene);
                progression += progressionStep;
                ReportProgressChanged((int)progression);
                CheckCancelled();
            });

            // Output
            RaiseMessage("GLTFExporter | Saving to output file");
            // Cast lists to arrays
            gltf.Prepare();
            var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()); // Standard serializer, not the optimized one
            var sb             = new StringBuilder();
            var sw             = new StringWriter(sb, CultureInfo.InvariantCulture);

            using (var jsonWriter = new JsonTextWriter(sw))
            {
                jsonWriter.Formatting = Formatting.None;
                jsonSerializer.Serialize(jsonWriter, gltf);
            }
            string outputGltfFile = Path.ChangeExtension(outputFile, "gltf");

            File.WriteAllText(outputGltfFile, sb.ToString());

            // Binary
            if (generateBinary)
            {
                // TODO - Export glTF data to binary format .glb
                RaiseError("GLTFExporter | TODO - Generating binary files");
            }

            ReportProgressChanged(100);
        }