Esempio n. 1
0
 /// <summary>
 /// Added function to set the root
 /// </summary>
 /// <param name="glb">GLB to add</param>
 /// <param name="newRoot">The new root to update it with</param>
 public static void SetRoot(GLBObject glb, GLTFRoot newRoot)
 {
     if (newRoot != null)
     {
         glb.Root = newRoot;
     }
 }
Esempio n. 2
0
        /// <summary>
        /// Merges two glb files together
        /// </summary>
        /// <param name="mergeTo">The glb to update</param>
        /// <param name="mergeFrom">The glb to merge from</param>
        public static void MergeGLBs(GLBObject mergeTo, GLBObject mergeFrom)
        {
            if (mergeTo == null)
            {
                throw new ArgumentNullException(nameof(mergeTo));
            }
            if (mergeFrom == null)
            {
                throw new ArgumentNullException(nameof(mergeFrom));
            }

            // 1) merge json
            // 2) copy mergefrom binary data to mergeto binary data
            // 3) Fix up bufferviews to be the new offset
            int  previousBufferViewsCount = mergeTo.Root.BufferViews?.Count ?? 0;
            uint previousBufferSize       = mergeTo.BinaryChunkInfo.Length;

            GLTFHelpers.MergeGLTF(mergeTo.Root, mergeFrom.Root);
            _AddBinaryData(mergeTo, mergeFrom.Stream, false, mergeFrom.BinaryChunkInfo.StartPosition + GLTFParser.CHUNK_HEADER_SIZE);
            uint bufferSizeDiff =
                mergeTo.BinaryChunkInfo.Length -
                previousBufferSize;                 // calculate buffer size change to update the byte offsets of the appended buffer views

            if (mergeTo.Root.BufferViews != null)
            {
                for (int i = previousBufferViewsCount; i < mergeTo.Root.BufferViews.Count; ++i)
                {
                    mergeTo.Root.BufferViews[i].ByteOffset += bufferSizeDiff;
                }
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Adds binary data to the GLB
        /// </summary>
        /// <param name="glb">The glb to update</param>
        /// <param name="binaryData">The binary data to append</param>
        /// <param name="createBufferView">Whether a buffer view should be created, added to the GLTFRoot, and id returned</param>
        /// <param name="streamStartPosition">Start position of stream</param>
        /// <param name="bufferViewName">Root to replace the current one with</param>
        /// <returns>The location of the added buffer view</returns>
        public static BufferViewId AddBinaryData(GLBObject glb, Stream binaryData, bool createBufferView = true, long streamStartPosition = 0, string bufferViewName = null)
        {
            if (glb == null)
            {
                throw new ArgumentNullException(nameof(glb));
            }
            if (glb.Root == null && bufferViewName == null)
            {
                throw new ArgumentException("glb Root and new root cannot be null", nameof(glb));
            }
            if (glb.Stream == null)
            {
                throw new ArgumentException("glb Stream cannot be null", nameof(glb));
            }
            if (binaryData == null)
            {
                throw new ArgumentNullException(nameof(binaryData));
            }
            if (binaryData.Length > uint.MaxValue)
            {
                throw new ArgumentException("Stream cannot be larger than uint.MaxValue", nameof(binaryData));
            }

            return(_AddBinaryData(glb, binaryData, createBufferView, streamStartPosition, bufferViewName));
        }
Esempio n. 4
0
        private static GLBObject _ConstructFromEmptyStream(Stream inStream, long streamStartPosition)
        {
            GLBObject glbObject = new GLBObject
            {
                Stream        = inStream,
                JsonChunkInfo = new ChunkInfo
                {
                    Length        = 0,
                    StartPosition = GLTFParser.HEADER_SIZE,
                    Type          = ChunkFormat.JSON
                },
                BinaryChunkInfo = new ChunkInfo
                {
                    Length        = 0,
                    StartPosition = GLTFParser.HEADER_SIZE + GLTFParser.CHUNK_HEADER_SIZE,
                    Type          = ChunkFormat.BIN
                },
                Header = new GLBHeader
                {
                    FileLength = GLTFParser.HEADER_SIZE,
                    Version    = 2
                },
                StreamStartPosition = streamStartPosition
            };

            return(glbObject);
        }
Esempio n. 5
0
 public GLBObject(GLBObject other)
 {
     Root   = other.Root;
     Header = other.Header;
     StreamStartPosition = other.StreamStartPosition;
     JsonChunkInfo       = other.JsonChunkInfo;
     BinaryChunkInfo     = other.BinaryChunkInfo;
 }
Esempio n. 6
0
        /// <summary>
        /// Turns a glTF file w/ structure into a GLB. Does not currently copy binary data
        /// </summary>
        /// <param name="root">The glTF root to turn into a GLBObject</param>
        /// <param name="glbOutStream">Output stream to write the GLB to</param>
        /// <param name="loader">Loader for loading external components from GLTFRoot. The loader will receive uris and return the stream to the resource</param>
        /// <returns>A constructed GLBObject</returns>
        private static GLBObject ConstructFromGLTF(GLTFRoot root, Stream glbOutStream, Func <string, Stream> loader)
        {
            if (root == null)
            {
                throw new ArgumentNullException(nameof(root));
            }
            if (glbOutStream == null)
            {
                throw new ArgumentNullException(nameof(glbOutStream));
            }

            MemoryStream gltfJsonStream = new MemoryStream();

            using (StreamWriter sw = new StreamWriter(gltfJsonStream))
            {
                root.Serialize(sw, true);
                sw.Flush();

                long proposedLength = gltfJsonStream.Length + GLTFParser.HEADER_SIZE + GLTFParser.CHUNK_HEADER_SIZE;
                if (gltfJsonStream.Length > uint.MaxValue)
                {
                    throw new ArgumentException("Serialized root cannot exceed uint.maxvalue", nameof(root));
                }
                uint proposedLengthAsUint = (uint)proposedLength;
                glbOutStream.SetLength(proposedLengthAsUint);
                GLBObject glbObject = new GLBObject
                {
                    Header = new GLBHeader
                    {
                        FileLength = proposedLengthAsUint,
                        Version    = 2
                    },
                    Root          = root,
                    Stream        = glbOutStream,
                    JsonChunkInfo = new ChunkInfo
                    {
                        Length        = (uint)gltfJsonStream.Length,
                        StartPosition = GLTFParser.HEADER_SIZE,
                        Type          = ChunkFormat.JSON
                    }
                };

                // write header
                WriteHeader(glbOutStream, glbObject.Header, glbObject.StreamStartPosition);

                // write chunk header
                WriteChunkHeader(glbOutStream, glbObject.JsonChunkInfo);

                gltfJsonStream.Position = 0;
                gltfJsonStream.CopyTo(glbOutStream);

                // todo: implement getting binary data for loader

                return(glbObject);
            }
        }
Esempio n. 7
0
        /// <summary>
        /// Removes a blob from the GLB at the given BufferView
        /// Updates accessors and images to have correct new bufferview index
        /// This function can invalidate BufferViewId's returned by previous function
        /// </summary>
        /// <param name="glb">The glb to remove from</param>
        /// <param name="bufferViewId">The buffer to remove</param>
        public static void RemoveBinaryData(GLBObject glb, BufferViewId bufferViewId)
        {
            if (glb == null)
            {
                throw new ArgumentNullException(nameof(glb));
            }
            if (bufferViewId == null)
            {
                throw new ArgumentNullException(nameof(bufferViewId));
            }

            BufferView bufferViewToRemove = bufferViewId.Value;
            int        id = bufferViewId.Id;

            if (bufferViewToRemove.ByteOffset + bufferViewToRemove.ByteLength == glb.BinaryChunkInfo.Length)
            {
                uint bufferViewLengthAsUint = bufferViewToRemove.ByteLength;
                glb.SetFileLength(glb.Header.FileLength - bufferViewLengthAsUint);
                glb.SetBinaryChunkLength(glb.BinaryChunkInfo.Length - bufferViewLengthAsUint);
                if (glb.BinaryChunkInfo.Length == 0)
                {
                    glb.Root.Buffers.RemoveAt(0);
                    foreach (BufferView bufferView in glb.Root.BufferViews)                     // other buffers may still exist, and their index has now changed
                    {
                        --bufferView.Buffer.Id;
                    }

                    glb.SetFileLength(glb.Header.FileLength - GLTFParser.CHUNK_HEADER_SIZE);
                }
                else
                {
                    glb.Root.Buffers[0].ByteLength = glb.BinaryChunkInfo.Length;

                    // write binary chunk header
                    WriteChunkHeader(glb.Stream, glb.BinaryChunkInfo);
                }

                // trim the end
                glb.Stream.SetLength(glb.Header.FileLength);

                // write glb header
                WriteHeader(glb.Stream, glb.Header, glb.StreamStartPosition);
            }

            glb.Root.BufferViews.RemoveAt(id);
            if (glb.Root.Accessors != null)
            {
                foreach (Accessor accessor in glb.Root.Accessors)                 // shift over all accessors
                {
                    if (accessor.BufferView != null && accessor.BufferView.Id >= id)
                    {
                        --accessor.BufferView.Id;
                    }

                    if (accessor.Sparse != null)
                    {
                        if (accessor.Sparse.Indices?.BufferView.Id >= id)
                        {
                            --accessor.Sparse.Indices.BufferView.Id;
                        }

                        if (accessor.Sparse.Values?.BufferView.Id >= id)
                        {
                            --accessor.Sparse.Values.BufferView.Id;
                        }
                    }
                }
            }

            if (glb.Root.Images != null)
            {
                foreach (GLTFImage image in glb.Root.Images)
                {
                    if (image.BufferView != null && image.BufferView.Id >= id)
                    {
                        --image.BufferView.Id;
                    }
                }
            }
        }
Esempio n. 8
0
        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);
        }
Esempio n. 9
0
        /// <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();
            }
        }