diff --git a/glTF-BinExporter/GlTFExporterCommand.cs b/glTF-BinExporter/GlTFExporterCommand.cs index f8e4e13..552afb9 100644 --- a/glTF-BinExporter/GlTFExporterCommand.cs +++ b/glTF-BinExporter/GlTFExporterCommand.cs @@ -35,23 +35,20 @@ protected override Result RunCommand(Rhino.RhinoDoc doc, RunMode mode) { GetObject go = Selection.GetValidExportObjects("Select objects to export."); - var opts = new ExportOptions() { UseDracoCompression = true, DracoCompressionLevel = 10, DracoQuantizationBits = 16, UseBinary = true }; - - // NOTE: The following options can be useful in dev/debug: - //bool useDracoCompression = true; - //Rhino.Input.RhinoGet.GetBool("Compression", true, "None", "Draco", ref useDracoCompression); - //bool useBinary = true; - //Rhino.Input.RhinoGet.GetBool("Mode", true, "Text", "Binary", ref useBinary); - //bool includeMaterials = true; - //Rhino.Input.RhinoGet.GetBool("Materials", true, "Exclude", "Include", ref opts.IncludeMaterial); - //int dracoCompressionLevel = 10; - Rhino.Input.RhinoGet.GetInteger("Draco Compression Level (max=10)", true, ref opts.DracoCompressionLevel, 1, 10); - //bool quantizaionBits = true; - Rhino.Input.RhinoGet.GetInteger("Quantization", true, ref opts.DracoQuantizationBits, 8, 32); - //Rhino.Input.RhinoGet.GetBool("Draco Quantization", true, "B", "Bits 16 Bits", ref quantizaionBits); - //opts.DracoQuantizationBits = quantizaionBits ? 24 : 12; - - var dialog = new SaveFileDialog() { DefaultExt = ".glb", Title = "Select glTF Binary file to export to.", Filter = "glTF Binary (*.glb) | *.glb" }; + var opts = new ExportOptions() { UseDracoCompression = false, DracoCompressionLevel = 10, DracoQuantizationBits = 16, UseBinary = true }; + + Rhino.Input.RhinoGet.GetBool("Binary or Text", true, "Text", "Binary", ref opts.UseBinary); + + Rhino.Input.RhinoGet.GetBool("Compression", true, "None", "Draco", ref opts.UseDracoCompression); + + if (opts.UseDracoCompression) + { + Rhino.Input.RhinoGet.GetInteger("Draco Compression Level (max=10)", true, ref opts.DracoCompressionLevel, 1, 10); + Rhino.Input.RhinoGet.GetInteger("Quantization", true, ref opts.DracoQuantizationBits, 8, 32); + } + + var dialog = GetSaveFileDialog(opts.UseBinary); + var fileSelected = dialog.ShowSaveDialog(); if (!fileSelected) { @@ -89,8 +86,32 @@ protected override Result RunCommand(Rhino.RhinoDoc doc, RunMode mode) return Result.Success; } catch (Exception e) { RhinoApp.WriteLine("ERROR: Failed exporting selected geometry to file."); + System.Diagnostics.Debug.WriteLine(e.Message); return Result.Failure; } } + + private SaveFileDialog GetSaveFileDialog(bool binaryExport) + { + if(binaryExport) + { + return new SaveFileDialog() + { + DefaultExt = ".glb", + Title = "Select glTF Binary file to export to.", + Filter = "glTF Binary (*.glb) | *.glb" + }; + } + else //Text glTF + { + return new SaveFileDialog() + { + DefaultExt = ".gltf", + Title = "Select glTF file to export to.", + Filter = "glTF Text (*.gltf) | *.gltf" + }; + } + } + } } diff --git a/glTF-BinExporter/glTF/GlBuffer.cs b/glTF-BinExporter/glTF/GlBuffer.cs index 0ce1ac9..5f8726c 100644 --- a/glTF-BinExporter/glTF/GlBuffer.cs +++ b/glTF-BinExporter/glTF/GlBuffer.cs @@ -29,6 +29,7 @@ public class Buffer public bool IsGLBinaryMode; // dump to byte[] with ".flatten" + .ToArray(); + [JsonIgnore] public List> RawBytes; public bool ShouldSerializeRawBytes() @@ -75,6 +76,22 @@ public void Add(Point3d point) PrimitiveCount += 1; } + public void Add(Vector3d point) + { + // Switch GL coords for Y<=>Z + float[] coords = new float[] { (float)point.X, (float)point.Z, -(float)point.Y }; + Add(coords); + PrimitiveCount += 1; + } + + public void Add(Point2f point) + { + // Switch GL coords for Y<=>Z + float[] coords = new float[] { (float)point.X, -(float)point.Y }; + Add(coords); + PrimitiveCount += 1; + } + public void Add(MeshFace face) { if (face.IsTriangle) diff --git a/glTF-BinExporter/glTF/GlTFRootModel.cs b/glTF-BinExporter/glTF/GlTFRootModel.cs index b4592c9..6d5cda6 100644 --- a/glTF-BinExporter/glTF/GlTFRootModel.cs +++ b/glTF-BinExporter/glTF/GlTFRootModel.cs @@ -20,7 +20,8 @@ namespace glTF_BinExporter.glTF /// public class RootModel { - public int scene; + public int scene = 0; + public List scenes; public List nodes; @@ -145,6 +146,9 @@ public void SerializeToGLB(in MemoryStream memoryStream) bv.byteOffset = bv.bufferRef.binaryOffset; } + //Have to get rid of the other buffers so only the new buffer + //all the data was dumped into remains + buffers.Clear(); buffers.Add(new Buffer(false) { byteLength = binaryChunkUnpaddedLength, uri = null }); // JSON Chunk @@ -518,90 +522,174 @@ public void AddMaterial(Rhino.DocObjects.Material rhinoMaterial, Guid renderMatI public void AddRhinoObject(Rhino.Geometry.Mesh[] rhinoMeshes, Rhino.DocObjects.Material material, Guid renderMatId) { - // TODO: Disabled for now. Don't have bandwidth to fix none-DracoCompression for now. Beware of lot's of "old" code below. - //int currentMaterialIdx = materials.FindIndex(m => m.Id == material.Id); - //if (currentMaterialIdx == -1) - //{ - // AddMaterial(material); - // currentMaterialIdx = materials.Count - 1; - //} + int currentMaterialIdx = materials.FindIndex(m => m.Id == material.Id); + if (currentMaterialIdx == -1) + { + AddMaterial(material, renderMatId); + currentMaterialIdx = materials.Count - 1; + } - //var primitives = new List(); - //// For each rhino mesh, create gl-buffers, gl-meshes, etc. - //foreach (var rhinoMesh in rhinoMeshes) - //{ - // // Create buffers for data (position, normals, indices etc.) - // var vtxBuffer = new Buffer(IsGLBinaryMode); + var primitives = new List(); - // var min = new Point3d() { X = Double.PositiveInfinity, Y = Double.PositiveInfinity, Z = Double.PositiveInfinity }; - // var max = new Point3d() { X = Double.NegativeInfinity, Y = Double.NegativeInfinity, Z = Double.NegativeInfinity }; - // foreach (var p in rhinoMesh.Vertices) - // { - // vtxBuffer.Add(p); - - // min.X = Math.Min(min.X, p.X); - // // Switch Y<=>Z for GL coords - // min.Y = Math.Min(min.Y, p.Z); - // min.Z = Math.Min(min.Z, -p.Y); - - // max.X = Math.Max(max.X, p.X); - // // Switch Y<=>Z for GL coords - // max.Y = Math.Max(max.Y, p.Z); - // max.Z = Math.Max(max.Z, -p.Y); - // } - // buffers.Add(vtxBuffer); - // int vtxBufferIdx = buffers.Count - 1; - - // var idsBuffer = new Buffer(IsGLBinaryMode); - // foreach (var f in rhinoMesh.Faces) - // { - // idsBuffer.Add(f); - // } - // buffers.Add(idsBuffer); - // int idsBufferIdx = buffers.Count - 1; - - // // Create bufferviews - // var vtxBufferView = new BufferView() { bufferRef = vtxBuffer, buffer = vtxBufferIdx, byteOffset = 0, byteLength = vtxBuffer.byteLength, target = GlConstants.ARRAY_BUFFER }; - // bufferViews.Add(vtxBufferView); - // int vtxBufferViewIdx = bufferViews.Count - 1; - - // var idsBufferView = new BufferView() { bufferRef = idsBuffer, buffer = idsBufferIdx, byteOffset = 0, byteLength = idsBuffer.byteLength, target = GlConstants.ELEMENT_ARRAY_BUFFER }; - // bufferViews.Add(idsBufferView); - // int idsBufferViewIdx = bufferViews.Count - 1; - - // // Create accessors - // var vtxAccessor = new AccessorVec3 - // { - // bufferView = vtxBufferViewIdx, - // count = vtxBuffer.PrimitiveCount, - // min = new float[] { (float)min.X, (float)min.Y, (float)min.Z }, - // max = new float[] { (float)max.X, (float)max.Y, (float)max.Z } - // }; - // accessors.Add(vtxAccessor); - // int vtxAccessorIdx = accessors.Count - 1; - - // var idsAccessor = new AccessorScalar - // { - // bufferView = idsBufferViewIdx, - // count = idsBuffer.PrimitiveCount - // }; - // accessors.Add(idsAccessor); - // int idsAccessorIdx = accessors.Count - 1; - - // // Create primitives - // var attribute = new Attribute() { POSITION = vtxAccessorIdx }; - // var primitive = new Primitive() { attributes = attribute, indices = idsAccessorIdx, material = currentMaterialIdx }; - - // // Create mesh - // primitives.Add(primitive); - //} - //var mesh = new Mesh() { primitives = primitives }; - //meshes.Add(mesh); + foreach (var rhinoMesh in rhinoMeshes) + { + // Create buffers for data (position, normals, indices etc.) + var vtxBuffer = new Buffer(ExportOptions.UseBinary); + var vtxMin = new Point3d() { X = Double.PositiveInfinity, Y = Double.PositiveInfinity, Z = Double.PositiveInfinity }; + var vtxMax = new Point3d() { X = Double.NegativeInfinity, Y = Double.NegativeInfinity, Z = Double.NegativeInfinity }; + foreach (var p in rhinoMesh.Vertices) + { + vtxBuffer.Add(p); - //var node = new Node() { mesh = meshes.Count - 1 }; - //nodes.Add(node); + vtxMin.X = Math.Min(vtxMin.X, p.X); + // Switch Y<=>Z for GL coords + vtxMin.Y = Math.Min(vtxMin.Y, p.Z); + vtxMin.Z = Math.Min(vtxMin.Z, -p.Y); - //scenes[scene].nodes.Add(nodes.Count - 1); + vtxMax.X = Math.Max(vtxMax.X, p.X); + // Switch Y<=>Z for GL coords + vtxMax.Y = Math.Max(vtxMax.Y, p.Z); + vtxMax.Z = Math.Max(vtxMax.Z, -p.Y); + } + buffers.Add(vtxBuffer); + int vtxBufferIdx = buffers.Count - 1; + + var idsBuffer = new Buffer(ExportOptions.UseBinary); + foreach (var f in rhinoMesh.Faces) + { + idsBuffer.Add(f); + } + buffers.Add(idsBuffer); + int idsBufferIdx = buffers.Count - 1; + + Buffer normalsBuffer = new Buffer(ExportOptions.UseBinary); + var normalsMin = new Point3d() { X = Double.PositiveInfinity, Y = Double.PositiveInfinity, Z = Double.PositiveInfinity }; + var normalsMax = new Point3d() { X = Double.NegativeInfinity, Y = Double.NegativeInfinity, Z = Double.NegativeInfinity }; + //normalsBuffer.Add(rhinoMesh.Normals.ToFloatArray()); + foreach (var n in rhinoMesh.Normals) + { + normalsBuffer.Add(n); + + normalsMin.X = Math.Min(normalsMin.X, n.X); + // Switch Y<=>Z for GL coords + normalsMin.Y = Math.Min(normalsMin.Y, n.Z); + normalsMin.Z = Math.Min(normalsMin.Z, -n.Y); + + normalsMax.X = Math.Max(normalsMax.X, n.X); + // Switch Y<=>Z for GL coords + normalsMax.Y = Math.Max(normalsMax.Y, n.Z); + normalsMax.Z = Math.Max(normalsMax.Z, -n.Y); + } + int normalsIdx = buffers.AddAndReturnIndex(normalsBuffer); + + Buffer texCoordsBuffer = new Buffer(ExportOptions.UseBinary); + var texCoordsMin = new Point2d() { X = Double.PositiveInfinity, Y = Double.PositiveInfinity }; + var texCoordsMax = new Point2d() { X = Double.NegativeInfinity, Y = Double.NegativeInfinity }; + foreach (var tx in rhinoMesh.TextureCoordinates) + { + texCoordsBuffer.Add(tx); + + texCoordsMin.X = Math.Min(texCoordsMin.X, tx.X); + // Switch Y<=>Z for GL coords + texCoordsMin.Y = Math.Min(texCoordsMin.Y, -tx.Y); + + texCoordsMax.X = Math.Max(texCoordsMax.X, tx.X); + // Switch Y<=>Z for GL coords + texCoordsMax.Y = Math.Max(texCoordsMax.Y, -tx.Y); + } + + int texCoordsIdx = buffers.AddAndReturnIndex(texCoordsBuffer); + + // Create bufferviews + var vtxBufferView = new BufferView() { bufferRef = vtxBuffer, buffer = vtxBufferIdx, byteOffset = 0, byteLength = vtxBuffer.byteLength, target = GLConstants.ARRAY_BUFFER }; + bufferViews.Add(vtxBufferView); + int vtxBufferViewIdx = bufferViews.Count - 1; + + var idsBufferView = new BufferView() { bufferRef = idsBuffer, buffer = idsBufferIdx, byteOffset = 0, byteLength = idsBuffer.byteLength, target = GLConstants.ELEMENT_ARRAY_BUFFER }; + bufferViews.Add(idsBufferView); + int idsBufferViewIdx = bufferViews.Count - 1; + + BufferView normalsBufferView = new BufferView() + { + bufferRef = normalsBuffer, + buffer = normalsIdx, + byteOffset = 0, + byteLength = normalsBuffer.byteLength, + target = GLConstants.ARRAY_BUFFER + }; + int normalsBufferViewIdx = bufferViews.AddAndReturnIndex(normalsBufferView); + + BufferView texCoordsBufferView = new BufferView() + { + bufferRef = texCoordsBuffer, + buffer = texCoordsIdx, + byteOffset = 0, + byteLength = texCoordsBuffer.byteLength, + target = GLConstants.ARRAY_BUFFER + }; + int texCoordsBufferViewIdx = bufferViews.AddAndReturnIndex(texCoordsBufferView); + + // Create accessors + var vtxAccessor = new AccessorVec3() + { + bufferView = vtxBufferViewIdx, + count = vtxBuffer.PrimitiveCount, + min = new float[] { (float)vtxMin.X, (float)vtxMin.Y, (float)vtxMin.Z }, + max = new float[] { (float)vtxMax.X, (float)vtxMax.Y, (float)vtxMax.Z } + }; + + accessors.Add(vtxAccessor); + int vtxAccessorIdx = accessors.Count - 1; + + var idsAccessor = new AccessorScalar() + { + min = new int[] { 0 }, + max = new int[] { rhinoMesh.Vertices.Count - 1 }, + bufferView = idsBufferViewIdx, + count = idsBuffer.PrimitiveCount + }; + accessors.Add(idsAccessor); + int idsAccessorIdx = accessors.Count - 1; + + AccessorVec3 normalsAccessor = new AccessorVec3() + { + bufferView = normalsBufferViewIdx, + count = rhinoMesh.Normals.Count, + min = new float[] { (float)normalsMin.X, (float)normalsMin.Y, (float)normalsMin.Z }, + max = new float[] { (float)normalsMax.X, (float)normalsMax.Y, (float)normalsMax.Z } + }; + int normalsAccessorIdx = accessors.AddAndReturnIndex(normalsAccessor); + + AccessorVec2 texCoordsAccessor = new AccessorVec2() + { + bufferView = texCoordsBufferViewIdx, + count = rhinoMesh.TextureCoordinates.Count, + min = new float[] { (float)texCoordsMin.X, (float)texCoordsMin.Y }, + max = new float[] { (float)texCoordsMax.X, (float)texCoordsMax.Y } + }; + int texCoordsAccessorIdx = accessors.AddAndReturnIndex(texCoordsAccessor); + + // Create primitives + var attribute = new Attribute() + { + POSITION = vtxAccessorIdx, + NORMAL = normalsAccessorIdx, + TEXCOORD_0 = texCoordsAccessorIdx + }; + + var primitive = new Primitive() { attributes = attribute, indices = idsAccessorIdx, material = currentMaterialIdx }; + + // Create mesh + primitives.Add(primitive); + } + + var mesh = new Mesh() { primitives = primitives }; + meshes.Add(mesh); + + var node = new Node() { mesh = meshes.Count - 1 }; + nodes.Add(node); + + scenes[scene].nodes.Add(nodes.Count - 1); } } } diff --git a/glTF-BinExporter/glTF/GlTFUtils.cs b/glTF-BinExporter/glTF/GlTFUtils.cs index 34d5821..8ac9fbb 100644 --- a/glTF-BinExporter/glTF/GlTFUtils.cs +++ b/glTF-BinExporter/glTF/GlTFUtils.cs @@ -157,5 +157,12 @@ public static void ExportBinary(MemoryStream outStream, IEnumerable outStream.Flush(); } + + public static int AddAndReturnIndex(this List list, T item) + { + list.Add(item); + return list.Count - 1; + } + } }