Browse Source

More documentation.
Fixed Satellite buffers byte length.

Vicente Penades 6 years ago
parent
commit
d31cbeb6e2

+ 8 - 1
build/SharpGLTF.CodeGen/Program.cs

@@ -35,18 +35,23 @@ namespace SharpGLTF
 
         private static void _ProcessMainSchema()
         {
+            // load and process schema
             var ctx1 = LoadSchemaContext(Constants.MainSchemaFile);
 
-            ctx1.Remove(ctx1.Classes.FirstOrDefault(item => item.PersistentName == "glTF Property"));
+            // we remove "glTF Property" because it is hand coded.
+            ctx1.Remove(ctx1.Classes.FirstOrDefault(item => item.PersistentName == "glTF Property"));            
 
             // mimeType "anyof" is basically the string to use.
             ctx1.Remove(ctx1.Enumerations.FirstOrDefault(item => item.PersistentName == "image/jpeg-image/png"));
+
+            // replace Image.mimeType type from an Enum to String, so we can serialize it with more formats if required
             ctx1.Classes
                 .ToArray()
                 .FirstOrDefault(item => item.PersistentName == "Image")
                 .UseField("mimeType")
                 .FieldType = ctx1.UseString();
 
+            // replace Node.Matrix, Node.Rotation, Node.Scale and Node.Translation with System.Numerics.Vectors types
             var node = ctx1.Classes
                 .ToArray()
                 .FirstOrDefault(item => item.PersistentName == "Node");
@@ -56,6 +61,7 @@ namespace SharpGLTF
             node.UseField("scale").SetDataType(typeof(System.Numerics.Vector3), true).RemoveDefaultValue().SetItemsRange(0);
             node.UseField("translation").SetDataType(typeof(System.Numerics.Vector3), true).RemoveDefaultValue().SetItemsRange(0);
 
+            // replace Material.emissiveFactor with System.Numerics.Vectors types
             ctx1.Classes
                 .ToArray()
                 .FirstOrDefault(item => item.PersistentName == "Material")
@@ -64,6 +70,7 @@ namespace SharpGLTF
                 .SetDefaultValue("Vector3.Zero")
                 .SetItemsRange(0);
 
+            // replace Material.baseColorFactor with System.Numerics.Vectors types
             ctx1.Classes
                 .ToArray()
                 .FirstOrDefault(item => item.PersistentName == "Material PBR Metallic Roughness")

+ 82 - 28
src/SharpGLTF.DOM/Schema2/glb.Images.cs

@@ -37,7 +37,7 @@ namespace SharpGLTF.Schema2
         /// briefly reassigned so the JSON can be serialized correctly.
         /// After serialization <see cref="Image._uri"/> is set back to null.
         /// </remarks>
-        private Byte[] _ExternalImageContent;
+        private Byte[] _SatelliteImageContent;
 
         #endregion
 
@@ -48,7 +48,19 @@ namespace SharpGLTF.Schema2
         /// </summary>
         public int LogicalIndex => this.LogicalParent.LogicalImages.IndexOfReference(this);
 
+        /// <summary>
+        /// Gets a value indicating whether the contained image is stored in a satellite file when loaded or saved.
+        /// </summary>
+        public bool IsSatelliteFile => _SatelliteImageContent != null;
+
+        /// <summary>
+        /// Gets a value indicating whether the contained image is a PNG image.
+        /// </summary>
         public bool IsPng => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("png");
+
+        /// <summary>
+        /// Gets a value indicating whether the contained image is a JPEG image.
+        /// </summary>
         public bool IsJpeg => string.IsNullOrWhiteSpace(_mimeType) ? false : _mimeType.Contains("jpg") | _mimeType.Contains("jpeg");
 
         #endregion
@@ -73,10 +85,16 @@ namespace SharpGLTF.Schema2
             return true;
         }
 
+        /// <summary>
+        /// Retrieves the image file as a block of bytes.
+        /// </summary>
+        /// <returns>A <see cref="ArraySegment{Byte}"/> block containing the image file.</returns>
         public ArraySegment<Byte> GetImageContent()
         {
-            if (_ExternalImageContent != null) return new ArraySegment<byte>(_ExternalImageContent);
+            // the image is stored locally in a temporary buffer
+            if (_SatelliteImageContent != null) return new ArraySegment<byte>(_SatelliteImageContent);
 
+            /// the image is stored in a <see cref="BufferView"/>
             if (this._bufferView.HasValue)
             {
                 var bv = this.LogicalParent.LogicalBufferViews[this._bufferView.Value];
@@ -84,38 +102,62 @@ namespace SharpGLTF.Schema2
                 return bv.Content;
             }
 
+            // TODO: if external images have not been loaded into _ExternalImageContent
+            // and ModelRoot was loaded from file and stored the load path, use the _uri
+            // to load the model.
+
             throw new InvalidOperationException();
         }
 
-        public Image WithExternalFile(string filePath)
+        /// <summary>
+        /// Initializes this <see cref="Image"/> with an image loaded from a file.
+        /// </summary>
+        /// <param name="filePath">A valid path to an image file.</param>
+        /// <returns>This <see cref="Image"/> instance.</returns>
+        public Image WithSatelliteFile(string filePath)
         {
             var content = System.IO.File.ReadAllBytes(filePath);
-            return WithExternalContent(content);
+            return WithSatelliteContent(content);
         }
 
-        public Image WithExternalContent(Byte[] content)
+        /// <summary>
+        /// Initializes this <see cref="Image"/> with an image stored in a <see cref="Byte"/> array.
+        /// </summary>
+        /// <param name="content">A <see cref="Byte"/> array containing a PNG or JPEG image.</param>
+        /// <returns>This <see cref="Image"/> instance.</returns>
+        public Image WithSatelliteContent(Byte[] content)
         {
-            if (_IsPng(content)) _mimeType = MIMEPNG; // these strings might be wrong
-            if (_IsJpeg(content)) _mimeType = MIMEJPEG; // these strings might be wrong
+            Guard.NotNull(content, nameof(content));
+
+            string imageType = null;
+
+            if (_IsPng(content)) imageType = MIMEPNG; // these strings might be wrong
+            if (_IsJpeg(content)) imageType = MIMEJPEG; // these strings might be wrong
+
+            this._mimeType = imageType ?? throw new ArgumentException($"{nameof(content)} must be a PNG or JPG image", nameof(content));
 
             this._uri = null;
             this._bufferView = null;
-            this._ExternalImageContent = content;
+            this._SatelliteImageContent = content;
 
             return this;
         }
 
-        public void UseBufferViewContainer()
+        /// <summary>
+        /// If the image is stored externaly as an image file,
+        /// it creates a new BufferView and stores the image in the BufferView.
+        /// </summary>
+        public void TransferToInternalBuffer()
         {
-            if (this._ExternalImageContent == null) return;
+            if (this._SatelliteImageContent == null) return;
 
             // transfer the external image content to a buffer.
             this._bufferView = this.LogicalParent
-                .UseBufferView(this._ExternalImageContent)
+                .UseBufferView(this._SatelliteImageContent)
                 .LogicalIndex;
 
             this._uri = null;
-            this._ExternalImageContent = null;
+            this._SatelliteImageContent = null;
         }
 
         #endregion
@@ -126,13 +168,12 @@ namespace SharpGLTF.Schema2
         {
             if (!String.IsNullOrWhiteSpace(_uri))
             {
-                _ExternalImageContent = _LoadImageUnchecked(_uri, externalReferenceSolver);
+                _SatelliteImageContent = _LoadImageUnchecked(externalReferenceSolver, _uri);
+                _uri = null;
             }
-
-            _uri = null; // When _Data is not empty, clear URI
         }
 
-        private static Byte[] _LoadImageUnchecked(string uri, AssetReader externalReferenceSolver)
+        private static Byte[] _LoadImageUnchecked(AssetReader externalReferenceSolver, string uri)
         {
             return uri._TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER)
                 ?? uri._TryParseBase64Unchecked(EMBEDDEDOCTETSTREAM)
@@ -145,20 +186,23 @@ namespace SharpGLTF.Schema2
 
         #region binary write
 
-        internal void _EmbedAssets()
+        /// <summary>
+        /// Called internally by the serializer when the buffer content is to be written internally.
+        /// </summary>
+        internal void _WriteToInternal()
         {
-            if (_ExternalImageContent != null)
+            if (_SatelliteImageContent != null)
             {
-                var mimeContent = Convert.ToBase64String(_ExternalImageContent, Base64FormattingOptions.None);
+                var mimeContent = Convert.ToBase64String(_SatelliteImageContent, Base64FormattingOptions.None);
 
-                if (_IsPng(_ExternalImageContent))
+                if (_IsPng(_SatelliteImageContent))
                 {
                     _mimeType = MIMEPNG;
                     _uri = EMBEDDEDPNGBUFFER + mimeContent;
                     return;
                 }
 
-                if (_IsJpeg(_ExternalImageContent))
+                if (_IsJpeg(_SatelliteImageContent))
                 {
                     _mimeType = MIMEJPEG;
                     _uri = EMBEDDEDJPEGBUFFER + mimeContent;
@@ -169,19 +213,29 @@ namespace SharpGLTF.Schema2
             }
         }
 
-        internal void _WriteExternalAssets(string uri, AssetWriter writer)
+        /// <summary>
+        /// Called internally by the serializer when the image content is to be written as an external file
+        /// </summary>
+        /// <param name="writer">The satellite asset writer</param>
+        /// <param name="satelliteUri">A local satellite URI</param>
+        internal void _WriteToSatellite(AssetWriter writer, string satelliteUri)
         {
-            if (_ExternalImageContent != null)
+            if (_SatelliteImageContent != null)
             {
-                if (this._mimeType.Contains("png")) uri += ".png";
-                if (this._mimeType.Contains("jpg")) uri += ".jpg";
-                if (this._mimeType.Contains("jpeg")) uri += ".jpg";
+                if (this._mimeType.Contains("png")) satelliteUri += ".png";
+                if (this._mimeType.Contains("jpg")) satelliteUri += ".jpg";
+                if (this._mimeType.Contains("jpeg")) satelliteUri += ".jpg";
 
-                this._uri = uri;
-                writer(uri, _ExternalImageContent);
+                this._uri = satelliteUri;
+                writer(satelliteUri, _SatelliteImageContent);
             }
         }
 
+        /// <summary>
+        /// Called by the serializer immediatelly after
+        /// calling <see cref="_WriteToSatellite(AssetWriter, string)"/>
+        /// or <see cref="_WriteToInternal"/>
+        /// </summary>
         internal void _ClearAfterWrite() { this._uri = null; }
 
         #endregion

+ 52 - 7
src/SharpGLTF.DOM/Schema2/gltf.Buffer.cs

@@ -46,38 +46,51 @@ namespace SharpGLTF.Schema2
         const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream;base64,";
         const string EMBEDDEDGLTFBUFFER = "data:application/gltf-buffer;base64,";
 
-        internal void _ResolveUri(AssetReader externalReferenceSolver)
+        internal void _ResolveUri(AssetReader satelliteReferenceSolver)
         {
-            _Content = _LoadBinaryBufferUnchecked(_uri, externalReferenceSolver);
+            _Content = _LoadBinaryBufferUnchecked(_uri, satelliteReferenceSolver);
 
             _uri = null; // When _Data is not empty, clear URI
         }
 
-        private static Byte[] _LoadBinaryBufferUnchecked(string uri, AssetReader externalReferenceSolver)
+        private static Byte[] _LoadBinaryBufferUnchecked(string uri, AssetReader satelliteReferenceSolver)
         {
             return uri._TryParseBase64Unchecked(EMBEDDEDGLTFBUFFER)
                 ?? uri._TryParseBase64Unchecked(EMBEDDEDOCTETSTREAM)
-                ?? externalReferenceSolver?.Invoke(uri);
+                ?? satelliteReferenceSolver?.Invoke(uri);
         }
 
         #endregion
 
         #region binary write
 
-        internal void _WriteToExternal(string uri, AssetWriter writer)
+        /// <summary>
+        /// Called internally by the serializer when the buffer content is to be written as an external file
+        /// </summary>
+        /// <param name="writer">The satellite asset writer</param>
+        /// <param name="satelliteUri">A local satellite URI</param>
+        internal void _WriteToSatellite(AssetWriter writer, string satelliteUri)
         {
-            this._uri = uri;
+            this._uri = satelliteUri;
             this._byteLength = _Content.Length;
 
-            writer(uri, _Content);
+            writer(satelliteUri, _Content.GetPaddedContent());
         }
 
+        /// <summary>
+        /// Called internally by the serializer when the buffer content is to be written internally.
+        /// </summary>
         internal void _WriteToInternal()
         {
             this._uri = null;
             this._byteLength = _Content.Length;
         }
 
+        /// <summary>
+        /// Called by the serializer immediatelly after
+        /// calling <see cref="_WriteToSatellite(AssetWriter, string)"/>
+        /// or <see cref="_WriteToInternal"/>
+        /// </summary>
         internal void _ClearAfterWrite()
         {
             this._uri = null;
@@ -85,6 +98,19 @@ namespace SharpGLTF.Schema2
         }
 
         #endregion
+
+        #region API
+
+        internal void _IsolateMemory()
+        {
+            if (_Content == null) return;
+
+            var content = new Byte[_Content.Length];
+            _Content.CopyTo(content, 0);
+            _Content = content;
+        }
+
+        #endregion
     }
 
     public partial class ModelRoot
@@ -152,5 +178,24 @@ namespace SharpGLTF.Schema2
 
             this._buffers.Add(b);
         }
+
+        /// <summary>
+        /// Refreshes all internal memory buffers.
+        /// </summary>
+        /// <remarks>
+        /// <see cref="Buffer"/> instances can be created using external <see cref="Byte"/> arrays, which
+        /// can potentially be shared with other instances. Editing these arrays directly can lead to data
+        /// corruption.
+        /// This method refreshes all internal memory buffers, by copying the data into newly allocated
+        /// buffers. This ensures that at this point, all memory buffers are not shared and of exclusive
+        /// use of this <see cref="ModelRoot"/> instance.
+        /// </remarks>
+        public void IsolateMemory()
+        {
+            foreach (var b in this.LogicalBuffers)
+            {
+                b._IsolateMemory();
+            }
+        }
     }
 }

+ 14 - 0
src/SharpGLTF.DOM/Schema2/gltf.Root.cs

@@ -43,6 +43,20 @@ namespace SharpGLTF.Schema2
             _textures = new ChildrenCollection<Texture, ModelRoot>(this);
         }
 
+        /// <summary>
+        /// Creates a complete clone of this <see cref="ModelRoot"/> instance.
+        /// </summary>
+        /// <returns>A new <see cref="ModelRoot"/> instance.</returns>
+        /// <remarks>
+        /// Deep cloning is performed as a brute force operation; by serializing
+        /// the whole model to GLB into memory, and then deserializing it back.
+        /// </remarks>
+        public ModelRoot DeepClone()
+        {
+            var document = this.GetGLB();
+            return ModelRoot.ParseGLB(document);
+        }
+
         #endregion
 
         #region properties

+ 3 - 3
src/SharpGLTF.DOM/Schema2/gltf.Serialization.cs

@@ -308,7 +308,7 @@ namespace SharpGLTF.Schema2
                 {
                     var buffer = this._buffers[i];
                     var bname = this._buffers.Count != 1 ? $"{baseName}_{i}.bin" : $"{baseName}.bin";
-                    buffer._WriteToExternal(bname, settings.FileWriter);
+                    buffer._WriteToSatellite(settings.FileWriter, bname);
                 }
             }
 
@@ -316,8 +316,8 @@ namespace SharpGLTF.Schema2
             {
                 var image = this._images[i];
                 var iname = this._images.Count != 1 ? $"{baseName}_{i}" : $"{baseName}";
-                if (settings.EmbedImages) image._EmbedAssets();
-                else image._WriteExternalAssets(iname, settings.FileWriter);
+                if (settings.EmbedImages) image._WriteToInternal();
+                else image._WriteToSatellite(settings.FileWriter, iname);
             }
 
             using (var m = new MemoryStream())

+ 22 - 0
src/SharpGLTF.DOM/_Extensions.cs

@@ -19,6 +19,13 @@ namespace SharpGLTF
             return (value % mult) == 0;
         }
 
+        internal static int PaddingSize(this int size, int mult)
+        {
+            var rest = size % mult;
+
+            return rest == 0 ? 0 : mult - rest;
+        }
+
         internal static bool _IsReal(this float value)
         {
             return !(float.IsNaN(value) | float.IsInfinity(value));
@@ -331,5 +338,20 @@ namespace SharpGLTF
         }
 
         #endregion
+
+        #region serialization
+
+        public static Byte[] GetPaddedContent(this Byte[] content)
+        {
+            if (content == null) return null;
+
+            if (content.Length.IsMultipleOf(4)) return content;
+
+            var paddedContent = new Byte[content.Length + content.Length.PaddingSize(4)];
+            content.CopyTo(paddedContent, 0);
+            return paddedContent;
+        }
+
+        #endregion
     }
 }

+ 1 - 1
tests/SharpGLTF.Tests/Schema2/Authoring/CreateModelTests.cs

@@ -133,7 +133,7 @@ namespace SharpGLTF.Schema2.Authoring
             // PBRMetallicRoughness has a "BaseColor" and a "Metallic" and a "Roughness" channels.
             primitive.Material
                 .FindChannel("BaseColor")
-                .SetTexture(0, model.CreateImage().WithExternalFile(imagePath) );
+                .SetTexture(0, model.CreateImage().WithSatelliteFile(imagePath) );
             
             model.AttachToCurrentTest("result.glb");
             model.AttachToCurrentTest("result.gltf");