private static BufferViewId _AddBinaryData(GLBObject glb, Stream binaryData, bool createBufferView, long streamStartPosition, string bufferViewName = null) { binaryData.Position = streamStartPosition; // Append new binary chunk to end uint blobLengthAsUInt = CalculateAlignment((uint)(binaryData.Length - streamStartPosition), 4); uint newBinaryBufferSize = glb.BinaryChunkInfo.Length + blobLengthAsUInt; uint newGLBSize = glb.Header.FileLength + blobLengthAsUInt; uint blobWritePosition = glb.Header.FileLength; // there was an existing file that had no binary chunk info previously if (glb.BinaryChunkInfo.Length == 0) { newGLBSize += GLTFParser.CHUNK_HEADER_SIZE; blobWritePosition += GLTFParser.CHUNK_HEADER_SIZE; glb.SetBinaryChunkStartPosition(glb.Header.FileLength); // if 0, then appends chunk info at the end } glb.Stream.SetLength(glb.Header.FileLength + blobLengthAsUInt); glb.Stream.Position = blobWritePosition; // assuming the end of the file is the end of the binary chunk binaryData.CopyTo(glb.Stream); // make sure this doesn't supersize it glb.SetFileLength(newGLBSize); glb.SetBinaryChunkLength(newBinaryBufferSize); // write glb header past magic number WriteHeader(glb.Stream, glb.Header, glb.StreamStartPosition); WriteChunkHeader(glb.Stream, glb.BinaryChunkInfo); if (createBufferView) { // Add a new BufferView to the GLTFRoot BufferView bufferView = new BufferView { Buffer = new BufferId { Id = 0, Root = glb.Root }, ByteLength = blobLengthAsUInt, // figure out whether glb size is wrong or if documentation is unclear ByteOffset = glb.BinaryChunkInfo.Length - blobLengthAsUInt, Name = bufferViewName }; if (glb.Root.BufferViews == null) { glb.Root.BufferViews = new List <BufferView>(); } glb.Root.BufferViews.Add(bufferView); return(new BufferViewId { Id = glb.Root.BufferViews.Count - 1, Root = glb.Root }); } return(null); }
/// <summary> /// Saves out the GLBObject to its own stream /// The GLBObject stream will be updated to be the output stream. Callers are reponsible for handling Stream lifetime /// </summary> /// <param name="glb">The GLB to flush to the output stream and update</param> /// <param name="newRoot">Optional root to replace the one in the glb</param> /// <returns>A GLBObject that is based upon outStream</returns> public static void UpdateStream(GLBObject glb) { if (glb.Root == null) { throw new ArgumentException("glb Root and newRoot cannot be null", nameof(glb.Root)); } if (glb.Stream == null) { throw new ArgumentException("glb GLBStream property cannot be null", nameof(glb.Stream)); } MemoryStream gltfJsonStream = new MemoryStream(); using (StreamWriter sw = new StreamWriter(gltfJsonStream)) { glb.Root.Serialize(sw, true); // todo: this could out of memory exception sw.Flush(); if (gltfJsonStream.Length > int.MaxValue) { // todo: make this a non generic exception throw new Exception("JSON chunk of GLB has exceeded maximum allowed size (4 GB)"); } // realloc of out of space if (glb.JsonChunkInfo.Length < gltfJsonStream.Length) { uint proposedJsonChunkLength = (uint)System.Math.Min((long)gltfJsonStream.Length * 2, uint.MaxValue); // allocate double what is required proposedJsonChunkLength = CalculateAlignment(proposedJsonChunkLength, 4); // chunks must be 4 byte aligned uint amountToAddToFile = proposedJsonChunkLength - glb.JsonChunkInfo.Length; // we have not yet initialized a json chunk before if (glb.JsonChunkInfo.Length == 0) { amountToAddToFile += GLTFParser.CHUNK_HEADER_SIZE; glb.SetJsonChunkStartPosition(GLTFParser.HEADER_SIZE); } // new proposed length = propsoedJsonBufferSize - currentJsonBufferSize + totalFileLength long proposedLength = amountToAddToFile + glb.Header.FileLength; if (proposedLength > uint.MaxValue) { throw new Exception("GLB has exceeded max allowed size (4 GB)"); } uint proposedLengthAsUint = (uint)proposedLength; try { glb.Stream.SetLength(proposedLength); } catch (IOException e) { #if WINDOWS_UWP Debug.WriteLine(e); #else Console.WriteLine(e); #endif throw; } long newBinaryChunkStartPosition = GLTFParser.HEADER_SIZE + GLTFParser.CHUNK_HEADER_SIZE + proposedJsonChunkLength; glb.Stream.Position = glb.BinaryChunkInfo.StartPosition; glb.SetBinaryChunkStartPosition(newBinaryChunkStartPosition); if (glb.BinaryChunkInfo.Length > 0) { uint lengthToCopy = glb.BinaryChunkInfo.Length + GLTFParser.CHUNK_HEADER_SIZE; // todo: we need to be able to copy while doing it with smaller buffers. Also int is smaller than uint, so this is not standards compliant. glb.Stream.CopyToSelf((int)newBinaryChunkStartPosition, lengthToCopy); } // write out new GLB length glb.SetFileLength(proposedLengthAsUint); WriteHeader(glb.Stream, glb.Header, glb.StreamStartPosition); // write out new JSON header glb.SetJsonChunkLength(proposedJsonChunkLength); WriteChunkHeader(glb.Stream, glb.JsonChunkInfo); } // clear the buffer glb.Stream.Position = glb.JsonChunkInfo.StartPosition + GLTFParser.CHUNK_HEADER_SIZE; uint amountToCopy = glb.JsonChunkInfo.Length; while (amountToCopy != 0) { int currAmountToCopy = (int)System.Math.Min(amountToCopy, int.MaxValue); byte[] filler = Encoding.ASCII.GetBytes(new string(' ', currAmountToCopy)); glb.Stream.Write(filler, 0, filler.Length); amountToCopy -= (uint)currAmountToCopy; } // write new JSON data gltfJsonStream.Position = 0; glb.Stream.Position = glb.JsonChunkInfo.StartPosition + GLTFParser.CHUNK_HEADER_SIZE; gltfJsonStream.CopyTo(glb.Stream); glb.Stream.Flush(); } }