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