From 71563fb9d620807c2464a8474a7a2369638a8873 Mon Sep 17 00:00:00 2001 From: Blake Gross Date: Thu, 4 Oct 2018 11:20:07 -0700 Subject: [PATCH] Checking back in various bug fixes from internal branch and glb writing logic (#253) * Merged PR 947085: Merging changes for loading from glTF branch into master - Reset master to be the same as the KhronosGroup github master branch - Sending out PR which are changes that are not committed yet to KhronosGroup master branch. Main new changes is ILoader implementation - Some of this code has been previously reviewed, such as the changes to GLTFSerialization * Merged PR 950991: Added StorageFolder support for file loading * Added coroutine helper in order to run Unity main thread tasks as async * Refactored ILoader to only load streams * Made ILoader async for UWP Related work items: #14071709 * Merged PR 999460: Fixed issue where extras and extensions would break parsing Fixed issue where extras and extensions would break parsing also comitting Json 9.0 changes * Merged PR 1014087: Adding GLTF merge feature and copy constructor for GLTFRoot [+] Added test that merges lantern and boombox together [+] Added test that verifies lengths and copying behaviour in serialization unit test [~] Added copy constructors to each GLTFProperty class [~] Added GLTFHelpers merge code which adds together two GLTF objects * Merged PR 1023531: fixes for copy and merge bugs fixes for copy and merge bugs * Min and max are optional parameters * Extensions used and extensions required were not initialized * Merged PR 1086143: Merging changes from Pisa back into internal glTF repo Merging changes from Pisa back into internal glTF repo * Merged PR 1096169: Adding unsigned int as a supported type Adding unsigned int as a supported type to GetUnsignedDiscreteElement * Merged PR 1097336: Fixed matrix on node to serialize as column major Fixed matrix on node to serialize as column major * Merged PR 1105269: refactor to remove unity-specific coordinate space conversion refactor to remove unity-specific coordinate space conversion * Merged PR 1112053: Refactored UnityGLTF to be able to have a Unity component and a file load component with the Unity component being based around coroutines Refactored UnityGLTF to be able to have a Unity component and a file load component with the Unity component being based around coroutines * Got project working in UWP * Separated out functions into multiple components for loading buffers vs loading images * Added coroutines to Unity loading to split loading up across frames Related work items: #14485947 * Merged PR 1134418: Updated standalone texture creation Updated standalone texture creation. * Merged PR 1162126: More fixes to transforms More fixes to transforms * Merged PR 1112053: Refactored UnityGLTF to be able to have a Unity component and a file load component with the Unity component being based around coroutines Refactored UnityGLTF to be able to have a Unity component and a file load component with the Unity component being based around coroutines * Got project working in UWP * Separated out functions into multiple components for loading buffers vs loading images * Added coroutines to Unity loading to split loading up across frames Related work items: #14485947 * Merged PR 1134418: Updated standalone texture creation Updated standalone texture creation. * Merged PR 1162126: More fixes to transforms More fixes to transforms * Updated GLTF * Reverted WebRequestLoader to use UnityWebRequest and ILoader interface for non UWP now uses ILoader * Merged PR 1241880: Made all API's use coroutines instead of async in UWP - Switched async functions to coroutine based - Added TaskExtensions to run async as coroutines * Merged PR 1242827: Merging glTF master into Pisa - Merged together glTF master and Pisa - Fixed bugs that popped up - Main merge problem was merging together the changes that happened to spec gloss * Merged PR 1243600: GLTFRoot now goes through extension clone There was a bug where any clone that depended on GLTFRoot could not be completed successfully. All clones now have GLTFRoot passed in. * Adding GLTF merge feature and copy constructor for GLTFRoot [+] Added test that merges lantern and boombox together [+] Added test that verifies lengths and copying behaviour in serialization unit test [~] Added copy constructors to each GLTFProperty class [~] Added GLTFHelpers merge code which adds together two GLTF objects * Min and max are now optional parameters * Extensions used and extensions required were not initialized * Deleting files that we do not want to keep Deleting Examples, UnityTestFramework, and Test files so that they are not included in Pisa * Merged PR 1326279: Modifying UnityGLTF Components to function with current version of Unity Modified some files to function properly with our current Pisa codebase In addition removed AsyncCoroutineHelpers from here and ported to Pisa * Merged PR 1328468: Fixing the TaskExtension check for proper unity support The .Net check only works for il2cpp. Swapping to use check for Unity Version so this works everywhere. * Merged PR 1344253: GLB Updates GLB Updates to GLTFSceneImporter [+] Allow delay-loading the GLB stream [~] Properly initialize the buffer if asking for a thumbnail before loading all textures [~] Sending all image loads through LoadUnityTexture so our Pisa version can check for dds regardless of source type * Merged PR 1353216: GLB serialization fix Fix serialization of images with buffer view references. * Merged PR 1373687: Merging github/master into pisa_dev * Merged PR 1385832: Merging initial glb builder work into pisa_dev. Can create a GLBObject from a GLB and resave it out with varying size JSON See https://microsoft.sharepoint.com/teams/wdg_wex/hxt/Shared%20Documents/Pisa/Dev/Dev%20Specs/Edit%20and%20Save%20as%20GLB%20One%20Pager.docx?web=1 for implementation details. See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#glb-file-format-specification for GLB specification - Upgraded .NET version to 4.6 - Implemented ConstructFromStream and UpdateStream API's - Adds ability to create a GLB from a stream, and update the JSON of the GLB. Supports having two streams to same resource. -- Reallocation for JSON chunks is currently only a "growing" allocation and will always double. Max size is uint.maxvalue -- Known issue: having a file to copy that is larger than RAM will cause an OutOfMemoryException. This will be fixed in a future version of the API * Merged PR 1390761: Added functions to Add or Remove generic stream blobs to the GLB - Implemented AddBlob which appends a stream onto the end of GLB and updates the GLTFRoot with the updated BufferView and returns the new BufferView - Implemented BufferView RemoveBlob which removes a BufferView from GLTF and will shrink the buffer it is at the end * Merged PR 1395885: Buffers are now treated as uint32 Buffers are now treated as uint32 * Merged PR 1407945: Fixed issue where binary segment was not aligned properly Fixed issue where binary segment was not aligned properly * Merged PR 1412368: UInt script updates Updating a couple scripts for the Int > UInt changes. Also adding a null check to the GLBBuilder for the case where a GLB references a non-buffer-view image. * Merged PR 1413081: Merge pisa_dev to master - Made all methods synchronous. Callers can call them async - Reverted project to be .NET 3,5 - Added MergeGLB method to merge two GLB's together * Merged PR 1428200: Fix for issue where writing to incorrect buffer location Fix for issue where writing to incorrect buffer location * Merged PR 1441409: added ability to load color and emissive as sRGB added ability to load color and emissive as sRGB * Merged PR 1470088: Changed GLBBuilder namespace from UnityGLTF to GLTF Changed GLBBuilder namespace from UnityGLTF to GLTF * Merged PR 1471747: Created generic interface for glTF objects Created generic interface for glTF objects IGLTFObject represents an abstract GLTFObject that only has a root GLTFObject is to be used by glTF (up to calling library to wrap) GLBObject is produced by the GLBBuilder API's. * Merged PR 1472641: adding constructor for GLTFObject adding constructor for GLTFObject * Merged PR 1475126: reset position to properly parse JSON to GLB reset position to properly parse JSON to GLB * Merged PR 1531076: Fixed for GLBBuilder and to GLTFHelper that were found when building out GLB save functionality - Added more tests - Fixed bug where Root Merge would merge buffers - Fixed bug where extensions were not copied properly - Created the ability to construct from an empty stream - Added ability to add to a buffer without creating a buffer view * Merged PR 1527102: Modifications to GLTFSerialization projects to get them to compile in external solutions Modifications to GLTFSerialization projects to get them to compile in external solutions * Merged PR 1569575: parsing now only looks for two chunks parsing now only looks for two chunks * Merged PR 1569970: fix to ensure that we do not try to parse past stream end fix to ensure that we do not try to parse past stream end * Merged PR 1589667: Thumbnail index fix Correcting the CreateTexture function to pass the requested texture index/id to LoadImageBuffer instead of the image index/id. This happened to work before because we normally have a 1-to-1 relationship between textures and images, but when we don't this breaks the cache loading math. Bug 16500028 - [PC App] Most assets imported from Reinwood don't have thumbnails inside Woodinville's asset library * Merged PR 1609994: fixed issue with invariant culture in float parsing and tangents are now calculated fixed issue with invariant culture in float parsing and made it so that tangents and normals are calculated if not specified * Merged PR 1616105: making it so that tangents are always recalculated making it so that tangents are always recalculated. This is the only way that anything shows correct in Unity regardless of whether or not the initial asset has tangents. * Merged PR 1629903: gltf root can be explicity swapped gltf root can be explicity swapped * Merged PR 1659403: Changes to GLBBuilder to ensure that files without binary contents export properly and get updated correctly -If chunk info sections are not initialized, we now initialize them - Added test to verify files are written out properly Related work items: #16672277 * Merged PR 1692792: added copy constructor to GLBObject added copy constructor to GLBObject * Merged PR 1707450: Fixed null check that should be in ref counted cache data that was causing exception on asset clean up Fixed null check that should be in ref counted cache data that was causing exception on asset clean up * Merged PR 1746215: Recalculate tangents We need to recalculate tangents we're getting back from Simplygon * Merged PR 1775517: Changed Node Creation to not yield between Nodes and made it iterative - Added profiling - Made node creation iterative and stopped yielding * Merged PR 1780585: Fix for bug where if node parent was 0 it would not be set properly Fix for bug where if node parent was 0 it would not be set properly which caused scaling issue in the PC app Related work items: #17378069 * Merged PR 2026929: Invalid GLTF handling Adding a helper function for dropping invalid data from a gltf node (references to indexes that don't exist) and turning it on by default in the GLBBuilder. Related work items: #17520364 * Merged PR 2095998: Removed invariant culture as ToString() adds the culture info Removed invariant culture as ToString() adds the culture info This is giving the issue in locales like fr-FR when we try to render the asset in Perspective view in the Pisa PC App and this is happening due to the parsing in the InvariantCulture and ToString() localizes the value. Related work items: #18189854 * Merged PR 2104257: Fixed exceptions when serializing assets with the khr spec-gloss extension Fixed exceptions when serializing assets with the khr spec-gloss extension * Merged PR 2115866: Fixing some deserialization exceptions Fixed a few deserialization exceptions while testing with the sample assets from the GLTF sample assets repo * Merged PR 2302513: parent nodes no longer are set active automatically to prevent preappearing parent nodes no longer are set active automatically to prevent preappearing before compleition * fixed gltf root merge issue --- .gitignore | 4 +- .../External/glTF-Binary/.gitignore | 1 + .../External/glTF-Binary/Box.glb | Bin 0 -> 1664 bytes .../GLTFSerialization.NetStd.csproj | 4 + GLTFSerialization/GLTFSerialization.sln | 8 +- .../GLTFSerialization/AttributeAccessor.cs | 2 +- .../Extensions/GLTFJsonExtensions.cs | 6 +- ...aterials_pbrSpecularGlossinessExtension.cs | 4 +- .../GLTFSerialization/GLBBuilder.cs | 538 + .../GLTFSerialization/GLBObject.cs | 102 + .../GLTFSerialization/GLTFHelpers.cs | 1432 +-- .../GLTFSerialization/GLTFObject.cs | 20 + .../GLTFSerialization/GLTFParser.cs | 114 +- .../GLTFSerialization.csproj | 13 +- .../GLTFSerialization/IGLTFObject.cs | 12 + .../GLTFSerialization/Schema/Accessor.cs | 449 +- .../GLTFSerialization/Schema/BufferView.cs | 13 +- .../GLTFSerialization/Schema/GLTFBuffer.cs | 5 +- .../GLTFSerialization/Schema/GLTFImage.cs | 2 +- .../GLTFSerialization/Schema/GLTFProperty.cs | 2 +- .../GLTFSerialization/Schema/GLTFRoot.cs | 17 +- .../GLTFSerialization/Schema/MeshPrimitive.cs | 3 +- .../Utilities/JsonReaderExtensions.cs | 12 + .../Utilities/StreamExtensions.cs | 65 + .../GLTFSerialization/Utilities/SubStream.cs | 131 + .../GLTFSerializationUWP.csproj | 49 +- .../GLTFSerializationTests/GLBBuilderTest.cs | 343 + .../GLTFLoadTestHelper.cs | 8 +- .../GLTFSerializationTests/GLTFLoaderTest.cs | 14 +- .../GLTFSerializationTests.csproj | 5 +- .../GLTFSerializationTests/TestAssetPaths.cs | 26 + .../GLTFUWPLoaderTest.cs | 2 + UnityGLTF/.gitignore | 92 +- UnityGLTF/Assets/UnityGLTF/Examples.meta | 4 +- .../UnityGLTF/Examples/RootMergeComponent.cs | 2 +- .../UnityGLTF/Plugins/Newtonsoft.Json.xml | 8922 +++++++++++++++++ .../UnityGLTF/Scripts/Async/AsyncAction.cs | 57 - .../Scripts/Async/AsyncAction.cs.meta | 12 - .../UnityGLTF/Scripts/Async/TaskExtensions.cs | 4 +- .../UnityGLTF/Scripts/Cache/AssetCache.cs | 14 +- .../Scripts/Cache/BufferCacheData.cs | 2 +- .../Scripts/Cache/RefCountedCacheData.cs | 9 +- .../Assets/UnityGLTF/Scripts/Editor.meta | 4 +- .../Scripts/Editor/GLTFExportMenu.cs.meta | 4 +- .../Scripts/Extensions/SchemaExtensions.cs | 1004 +- .../UnityGLTF/Scripts/GLTFSceneExporter.cs | 46 +- .../UnityGLTF/Scripts/GLTFSceneImporter.cs | 3155 +++--- .../Scripts/InstantiatedGLTFObject.cs | 1 + .../Shaders/UnityStandardInput.cginc | 4 +- .../Shaders/UnityStandardShadow.cginc | 4 +- UnityGLTF/Assets/UnityTestTools.meta | 4 +- .../ProjectSettings/ProjectSettings.asset | 122 +- 52 files changed, 13694 insertions(+), 3178 deletions(-) create mode 100644 GLTFSerialization/External/glTF-Binary/.gitignore create mode 100644 GLTFSerialization/External/glTF-Binary/Box.glb create mode 100644 GLTFSerialization/GLTFSerialization/GLBBuilder.cs create mode 100644 GLTFSerialization/GLTFSerialization/GLBObject.cs create mode 100644 GLTFSerialization/GLTFSerialization/GLTFObject.cs create mode 100644 GLTFSerialization/GLTFSerialization/IGLTFObject.cs create mode 100644 GLTFSerialization/GLTFSerialization/Utilities/JsonReaderExtensions.cs create mode 100644 GLTFSerialization/GLTFSerialization/Utilities/StreamExtensions.cs create mode 100644 GLTFSerialization/GLTFSerialization/Utilities/SubStream.cs create mode 100644 GLTFSerialization/Tests/GLTFSerializationTests/GLBBuilderTest.cs create mode 100644 GLTFSerialization/Tests/GLTFSerializationTests/TestAssetPaths.cs delete mode 100644 UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs delete mode 100644 UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs.meta diff --git a/.gitignore b/.gitignore index 2658e650e..9456736d1 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,6 @@ _pkginfo.txt # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ \ No newline at end of file +!*.[Cc]ache/ + +*.orig \ No newline at end of file diff --git a/GLTFSerialization/External/glTF-Binary/.gitignore b/GLTFSerialization/External/glTF-Binary/.gitignore new file mode 100644 index 000000000..1e38c7908 --- /dev/null +++ b/GLTFSerialization/External/glTF-Binary/.gitignore @@ -0,0 +1 @@ +*Out* \ No newline at end of file diff --git a/GLTFSerialization/External/glTF-Binary/Box.glb b/GLTFSerialization/External/glTF-Binary/Box.glb new file mode 100644 index 0000000000000000000000000000000000000000..95ec886b6b92b134291fd41d34ac9d5349306e0a GIT binary patch literal 1664 zcmb7EOK;jh5S}DW+O$p6v`u^8GeNd_1bm4oEftl43Q#VHgE0$OGB&bJ+Q_oRvHz-n zq(7!Jix*Y|3Dweg=e6_A%bt4u#xVe_&H(fXY|f(@CPIkBiUbn22;I3GyAPRY#|SxE#v~@J z-RZV!7Blr6`_bt&`^`?9nFdzn`eWB2AFOMR#W1rd(&eFRdl`st&r#1>1WTZ{gEyie zT(@AfoJ@Fl@A97_$mlWVoykNr7-KrYd=dEEkNb}c3{ujK0x6e1_PSp7z%Cj)?0>TUa1Jlw h71BAph6{KDmq-`z7OvnOyhpl%4{!}1;S1.0.0-alpha + + true + + diff --git a/GLTFSerialization/GLTFSerialization.sln b/GLTFSerialization/GLTFSerialization.sln index 98f555f6a..ce775c332 100644 --- a/GLTFSerialization/GLTFSerialization.sln +++ b/GLTFSerialization/GLTFSerialization.sln @@ -37,12 +37,12 @@ Global {72AC331F-9810-4DE2-8EA3-84559A787218}.Release|x86.Build.0 = Release|x86 {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|x86.ActiveCfg = Debug|x86 - {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|x86.Build.0 = Debug|x86 + {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Debug|x86.Build.0 = Debug|Any CPU {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|Any CPU.Build.0 = Release|Any CPU - {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|x86.ActiveCfg = Release|x86 - {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|x86.Build.0 = Release|x86 + {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|x86.ActiveCfg = Release|Any CPU + {D228AE89-BE53-474F-AB7C-04A3293F59C1}.Release|x86.Build.0 = Release|Any CPU {821B87A3-D8FA-407A-BC58-928A859C71C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {821B87A3-D8FA-407A-BC58-928A859C71C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {821B87A3-D8FA-407A-BC58-928A859C71C4}.Debug|x86.ActiveCfg = Debug|x86 diff --git a/GLTFSerialization/GLTFSerialization/AttributeAccessor.cs b/GLTFSerialization/GLTFSerialization/AttributeAccessor.cs index 61e1b9770..3735608f4 100644 --- a/GLTFSerialization/GLTFSerialization/AttributeAccessor.cs +++ b/GLTFSerialization/GLTFSerialization/AttributeAccessor.cs @@ -7,7 +7,7 @@ public class AttributeAccessor public AccessorId AccessorId { get; set; } public NumericArray AccessorContent { get; set; } public System.IO.Stream Stream { get; set; } - public long Offset { get; set; } + public uint Offset { get; set; } public AttributeAccessor() { diff --git a/GLTFSerialization/GLTFSerialization/Extensions/GLTFJsonExtensions.cs b/GLTFSerialization/GLTFSerialization/Extensions/GLTFJsonExtensions.cs index c7ad0f171..0c17bbe8f 100644 --- a/GLTFSerialization/GLTFSerialization/Extensions/GLTFJsonExtensions.cs +++ b/GLTFSerialization/GLTFSerialization/Extensions/GLTFJsonExtensions.cs @@ -314,11 +314,11 @@ public static Quaternion ReadAsQuaternion(this JsonReader reader) return quat; } - public static Dictionary ReadAsDictionary(this JsonReader reader, Func deserializerFunc) + public static Dictionary ReadAsDictionary(this JsonReader reader, Func deserializerFunc, bool skipStartObjectRead = false) { - if (reader.Read() && reader.TokenType != JsonToken.StartObject) + if (!skipStartObjectRead && reader.Read() && reader.TokenType != JsonToken.StartObject) { - throw new Exception(string.Format("Dictionary must be an object at: {0}", reader.Path)); + throw new Exception(string.Format("Dictionary must be an object at: {0}.", reader.Path)); } var dict = new Dictionary(); diff --git a/GLTFSerialization/GLTFSerialization/Extensions/KHR_materials_pbrSpecularGlossinessExtension.cs b/GLTFSerialization/GLTFSerialization/Extensions/KHR_materials_pbrSpecularGlossinessExtension.cs index f742dd240..39401f7e0 100644 --- a/GLTFSerialization/GLTFSerialization/Extensions/KHR_materials_pbrSpecularGlossinessExtension.cs +++ b/GLTFSerialization/GLTFSerialization/Extensions/KHR_materials_pbrSpecularGlossinessExtension.cs @@ -86,13 +86,13 @@ public JProperty Serialize() JProperty jProperty = new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME, new JObject( - new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.DIFFUSE_FACTOR, DiffuseFactor), + new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.DIFFUSE_FACTOR, new JArray(DiffuseFactor.R, DiffuseFactor.G, DiffuseFactor.B, DiffuseFactor.A)), new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.DIFFUSE_TEXTURE, new JObject( new JProperty(TextureInfo.INDEX, DiffuseTexture.Index.Id) ) ), - new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.SPECULAR_FACTOR, SpecularFactor), + new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.SPECULAR_FACTOR, new JArray(SpecularFactor.X, SpecularFactor.Y, SpecularFactor.Z)), new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.GLOSSINESS_FACTOR, GlossinessFactor), new JProperty(KHR_materials_pbrSpecularGlossinessExtensionFactory.SPECULAR_GLOSSINESS_TEXTURE, new JObject( diff --git a/GLTFSerialization/GLTFSerialization/GLBBuilder.cs b/GLTFSerialization/GLTFSerialization/GLBBuilder.cs new file mode 100644 index 000000000..cff3f2983 --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/GLBBuilder.cs @@ -0,0 +1,538 @@ +using GLTF; +using GLTF.Schema; +using GLTF.Utilities; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace GLTF +{ + // Static class for construction GLB objects. These API's only work with the .NET 4.6 runtime and above. + public static class GLBBuilder + { + /// + /// Turns a glTF file w/ structure into a GLB. Does not currently copy binary data + /// + /// The glTF root to turn into a GLBObject + /// Output stream to write the GLB to + /// Loader for loading external components from GLTFRoot. The loader will receive uris and return the stream to the resource + /// A constructed GLBObject + private static GLBObject ConstructFromGLTF(GLTFRoot root, Stream glbOutStream, Func 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; + } + } + + /// + /// Turns the GLB data contained in a stream into a GLBObject. Will copy to the outStream if specified + /// + /// The glTF root to turn into a GLBObject + /// The stream the glb came in + /// If outstream is specified, the glb gets copied to it + /// Offset into the buffer that the GLB starts + /// A constructed GLBObject + public static GLBObject ConstructFromGLB(GLTFRoot root, Stream inputGLBStream, Stream outStream = null, + long inputGLBStreamStartPosition = 0) + { + if (outStream != null) + { + inputGLBStream.Position = inputGLBStreamStartPosition; + inputGLBStream.CopyTo(outStream); + } + else + { + outStream = inputGLBStream; + } + + // header information is 4 bytes in, past the magic number + inputGLBStream.Position = 4 + inputGLBStreamStartPosition; + GLBHeader header = GLTFParser.ParseGLBHeader(inputGLBStream); + + inputGLBStream.Position = GLTFParser.HEADER_SIZE + inputGLBStreamStartPosition; + List allChunks = GLTFParser.FindChunks(inputGLBStream); + ChunkInfo jsonChunkInfo = new ChunkInfo + { + Type = ChunkFormat.JSON + }; + ChunkInfo binaryChunkInfo = new ChunkInfo + { + Type = ChunkFormat.BIN + }; + + foreach (ChunkInfo chunkInfo in allChunks) + { + switch (chunkInfo.Type) + { + case ChunkFormat.JSON: + jsonChunkInfo = chunkInfo; + break; + case ChunkFormat.BIN: + binaryChunkInfo = chunkInfo; + break; + } + } + + if (jsonChunkInfo.Length == 0) + { + throw new ArgumentException("JSON chunk must exists for valid GLB", nameof(inputGLBStream)); + } + + // todo: compute the initial bufferview list + return new GLBObject + { + Root = root, + Stream = outStream, + StreamStartPosition = inputGLBStreamStartPosition, + Header = header, + JsonChunkInfo = jsonChunkInfo, + BinaryChunkInfo = binaryChunkInfo + }; + } + + /// + /// Turns a stream that contains glTF or a GLB data into a GLBObject. glTF is not yet supported + /// + /// The stream to turn into a GLB + /// If specified, output stream to write the GLB to + /// Loader for loading external components from GLTFRoot. Loader is required if loading from glTF + /// A constructed GLBObject + public static GLBObject ConstructFromStream(Stream inStream, Stream glbOutStream = null, Func loader = null, + long streamStartPosition = 0, bool removeUndefinedReferences = true) + { + if (inStream == null) throw new ArgumentNullException(nameof(inStream)); + + if (inStream.Length > 0) + { + inStream.Position = streamStartPosition; + + GLTFRoot root; + GLTFParser.ParseJson(inStream, out root, streamStartPosition); + if (removeUndefinedReferences) + { + GLTFHelpers.RemoveUndefinedReferences(root); + } + if (!root.IsGLB) + { + return ConstructFromGLTF(root, glbOutStream, loader); + } + + return ConstructFromGLB(root, inStream, glbOutStream, streamStartPosition); + } + + return _ConstructFromEmptyStream(inStream, streamStartPosition); + } + + 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; + } + + /// + /// 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 + /// + /// The GLB to flush to the output stream and update + /// Optional root to replace the one in the glb + /// A GLBObject that is based upon outStream + 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(); + } + } + + /// + /// Adds binary data to the GLB + /// + /// The glb to update + /// The binary data to append + /// Whether a buffer view should be created, added to the GLTFRoot, and id returned + /// Start position of stream + /// Root to replace the current one with + /// The location of the added buffer view + 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); + } + + 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(); + } + + glb.Root.BufferViews.Add(bufferView); + + return new BufferViewId + { + Id = glb.Root.BufferViews.Count - 1, + Root = glb.Root + }; + } + + return null; + } + + /// + /// Merges two glb files together + /// + /// The glb to update + /// The glb to merge from + 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; + } + } + } + + /// + /// 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 + /// + /// The glb to remove from + /// The buffer to remove + 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; + } + } + } + } + + /// + /// Added function to set the root + /// + /// GLB to add + /// The new root to update it with + public static void SetRoot(GLBObject glb, GLTFRoot newRoot) + { + if (newRoot != null) + { + glb.Root = newRoot; + } + } + + private static void WriteHeader(Stream stream, GLBHeader header, long streamStartPosition) + { + stream.Position = streamStartPosition; + byte[] magicNumber = BitConverter.GetBytes(GLTFParser.MAGIC_NUMBER); + byte[] version = BitConverter.GetBytes(header.Version); + byte[] length = BitConverter.GetBytes(header.FileLength); + + stream.Write(magicNumber, 0, magicNumber.Length); + stream.Write(version, 0, version.Length); + stream.Write(length, 0, length.Length); + } + + private static void WriteChunkHeader(Stream stream, ChunkInfo chunkInfo) + { + stream.Position = chunkInfo.StartPosition; + byte[] lengthBytes = BitConverter.GetBytes(chunkInfo.Length); + byte[] typeBytes = BitConverter.GetBytes((uint)chunkInfo.Type); + + stream.Write(lengthBytes, 0, lengthBytes.Length); + stream.Write(typeBytes, 0, lengthBytes.Length); + } + + public static uint CalculateAlignment(uint currentSize, uint byteAlignment) + { + return (currentSize + byteAlignment - 1) / byteAlignment * byteAlignment; + } + } +} diff --git a/GLTFSerialization/GLTFSerialization/GLBObject.cs b/GLTFSerialization/GLTFSerialization/GLBObject.cs new file mode 100644 index 000000000..188b18c5f --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/GLBObject.cs @@ -0,0 +1,102 @@ +using System.Collections.Generic; +using GLTF.Schema; +using System.IO; + +namespace GLTF +{ + /// + /// Objects containing GLB data and associated parsing information + /// + public class GLBObject : IGLTFObject + { + public GLBObject(GLBObject other) + { + Root = other.Root; + Header = other.Header; + StreamStartPosition = other.StreamStartPosition; + JsonChunkInfo = other.JsonChunkInfo; + BinaryChunkInfo = other.BinaryChunkInfo; + } + + /// + /// Parsed JSON of the GLB + /// + public GLTFRoot Root { get; internal set; } + + /// + /// Read/Write Stream that GLB exists in + /// todo: Stream should probably be passed in or updated via a call + /// + public Stream Stream { get; set; } + + /// + /// Header of GLB + /// + public GLBHeader Header { get { return _glbHeader; } internal set { _glbHeader = value; } } + + /// + /// Start position for the GLB stream + /// + public long StreamStartPosition { get; internal set; } + + /// + /// Information on JSON chunk + /// + public ChunkInfo JsonChunkInfo { get { return _jsonChunkInfo; } internal set { _jsonChunkInfo = value; } } + + /// + /// Information on Binary chunk + /// + public ChunkInfo BinaryChunkInfo { get { return _binaryChunkInfo; } internal set { _binaryChunkInfo = value; } } + + private GLBHeader _glbHeader; + private ChunkInfo _jsonChunkInfo; + private ChunkInfo _binaryChunkInfo; + + internal GLBObject() + { + } + + internal void SetFileLength(uint newHeaderLength) + { + _glbHeader.FileLength = newHeaderLength; + } + + internal void SetJsonChunkStartPosition(long startPosition) + { + _jsonChunkInfo.StartPosition = startPosition; + } + + internal void SetJsonChunkLength(uint jsonChunkLength) + { + _jsonChunkInfo.Length = jsonChunkLength; + } + + internal void SetBinaryChunkStartPosition(long startPosition) + { + _binaryChunkInfo.StartPosition = startPosition; + } + + internal void SetBinaryChunkLength(uint binaryChunkLength) + { + _binaryChunkInfo.Length = binaryChunkLength; + if (Root.Buffers == null) + { + Root.Buffers = new List(); + } + + if (Root.Buffers.Count == 0) + { + Root.Buffers.Add(new GLTFBuffer + { + ByteLength = binaryChunkLength + }); + } + else + { + Root.Buffers[0].ByteLength = binaryChunkLength; + } + } + } + +} diff --git a/GLTFSerialization/GLTFSerialization/GLTFHelpers.cs b/GLTFSerialization/GLTFSerialization/GLTFHelpers.cs index 9eec259cf..a8be08793 100644 --- a/GLTFSerialization/GLTFSerialization/GLTFHelpers.cs +++ b/GLTFSerialization/GLTFSerialization/GLTFHelpers.cs @@ -1,690 +1,914 @@ -using System.Collections.Generic; -using System.IO; -using System.Text.RegularExpressions; -using GLTF.Schema; -using GLTF.Math; - -namespace GLTF -{ - public static class GLTFHelpers - { - private struct PreviousGLTFSizes - { - public int PreviousBufferCount; - public int PreviousBufferViewCount; - public int PreviousAccessorCount; - public int PreviousMeshCount; - public int PreviousNodeCount; - public int PreviousSceneCount; - public int PreviousSkinCount; - public int PreviousAnimationCount; - public int PreviousCameraCount; - public int PreviousMaterialCount; - public int PreviousTextureCount; - public int PreviousImageCount; - public int PreviousSamplerCount; - } - - /// - /// Uses the accessor to parse the buffer into attributes needed to construct the mesh primitive +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; +using GLTF.Schema; +using GLTF.Math; + +namespace GLTF +{ + public static class GLTFHelpers + { + private struct PreviousGLTFSizes + { + public int PreviousBufferCount; + public int PreviousBufferViewCount; + public int PreviousAccessorCount; + public int PreviousMeshCount; + public int PreviousNodeCount; + public int PreviousSceneCount; + public int PreviousSkinCount; + public int PreviousAnimationCount; + public int PreviousCameraCount; + public int PreviousMaterialCount; + public int PreviousTextureCount; + public int PreviousImageCount; + public int PreviousSamplerCount; + } + + /// + /// Removes references to indexes that do not exist. /// - /// A dictionary that contains a mapping of attribute name to data needed to parse - public static void BuildMeshAttributes(ref Dictionary attributes) - { - if (attributes.ContainsKey(SemanticProperties.POSITION)) - { - var attributeAccessor = attributes[SemanticProperties.POSITION]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsVertexArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.INDICES)) - { - var attributeAccessor = attributes[SemanticProperties.INDICES]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTriangles(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.NORMAL)) - { - var attributeAccessor = attributes[SemanticProperties.NORMAL]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsNormalArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.TexCoord(0))) - { - var attributeAccessor = attributes[SemanticProperties.TexCoord(0)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.TexCoord(1))) - { - var attributeAccessor = attributes[SemanticProperties.TexCoord(1)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.TexCoord(2))) - { - var attributeAccessor = attributes[SemanticProperties.TexCoord(2)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.TexCoord(3))) - { - var attributeAccessor = attributes[SemanticProperties.TexCoord(3)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.Color(0))) - { - var attributeAccessor = attributes[SemanticProperties.Color(0)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsColorArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.TANGENT)) - { - var attributeAccessor = attributes[SemanticProperties.TANGENT]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsTangentArray(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.Weight(0))) - { - var attributeAccessor = attributes[SemanticProperties.Weight(0)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - if (attributes.ContainsKey(SemanticProperties.Joint(0))) - { - var attributeAccessor = attributes[SemanticProperties.Joint(0)]; - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - } - - public static void BuildBindPoseSamplers(ref AttributeAccessor attributeAccessor) + /// The node to clean + public static void RemoveUndefinedReferences(GLTFRoot root) { - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - attributeAccessor.AccessorId.Value.AsMatrix4x4Array(ref resultArray, bufferViewCache, offset); - attributeAccessor.AccessorContent = resultArray; - } - - /// - /// Uses the accessor to parse the buffer into arrays needed to construct the animation - /// - /// A dictionary mapping AttributeAccessor lists to their target types - public static void BuildAnimationSamplers(ref Dictionary> samplers) - { - foreach (var samplerSet in samplers) - { - foreach (var attributeAccessor in samplerSet.Value) + int accessorCount = root.Accessors?.Count ?? 0; + int bufferCount = root.Buffers?.Count ?? 0; + int bufferViewCount = root.BufferViews?.Count ?? 0; + int cameraCount = root.Cameras?.Count ?? 0; + int meshCount = root.Meshes?.Count ?? 0; + int nodeCount = root.Nodes?.Count ?? 0; + int samplersCount = root.Samplers?.Count ?? 0; + int skinCount = root.Skins?.Count ?? 0; + int textureCount = root.Textures?.Count ?? 0; + + if (root.Accessors != null) + { + foreach (Accessor accessor in root.Accessors) { - NumericArray resultArray = attributeAccessor.AccessorContent; - int offset = (int)LoadBufferView(attributeAccessor, out byte[] bufferViewCache); - - switch (samplerSet.Key) + if (accessor.BufferView != null && accessor.BufferView.Id >= bufferViewCount) { - case "time": - attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache, offset); - break; - case "translation": - case "scale": - attributeAccessor.AccessorId.Value.AsVector3Array(ref resultArray, bufferViewCache, offset); - break; - case "rotation": - attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); - break; + accessor.BufferView = null; } - - attributeAccessor.AccessorContent = resultArray; } } - } - - /// - /// Merges the right root into the left root - /// This function combines all of the lists of objects on each glTF root together and updates the relative indicies - /// All properties all merged except Asset and Default, which will stay "mergeToRoot"'s value - /// - /// The node to merge into - /// The node to merge from - public static void MergeGLTF(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) - { - PreviousGLTFSizes previousGLTFSize = new PreviousGLTFSizes() + if (root.Animations != null) { - PreviousAccessorCount = mergeToRoot.Accessors?.Count ?? 0, - PreviousBufferCount = mergeToRoot.Buffers?.Count ?? 0, - PreviousAnimationCount = mergeToRoot.Animations?.Count ?? 0, - PreviousBufferViewCount = mergeToRoot.BufferViews?.Count ?? 0, - PreviousCameraCount = mergeToRoot.Cameras?.Count ?? 0, - PreviousImageCount = mergeToRoot.Images?.Count ?? 0, - PreviousMaterialCount = mergeToRoot.Materials?.Count ?? 0, - PreviousMeshCount = mergeToRoot.Meshes?.Count ?? 0, - PreviousNodeCount = mergeToRoot.Nodes?.Count ?? 0, - PreviousSamplerCount = mergeToRoot.Samplers?.Count ?? 0, - PreviousSceneCount = mergeToRoot.Scenes?.Count ?? 0, - PreviousSkinCount = mergeToRoot.Skins?.Count ?? 0, - PreviousTextureCount = mergeToRoot.Textures?.Count ?? 0 - }; - - GLTFRoot mergeFromRootCopy = new GLTFRoot(mergeFromRoot); - - // for each type: - // 1) add the right hand range to the left hand object - // 2) update all ids to be based off of the appended size - - // merge extensions - MergeExtensions(mergeToRoot, mergeFromRootCopy); - - // merge accessors, buffers, and bufferviews - MergeAccessorsBufferViewsAndBuffers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - - // merge materials, samplers, images, and textures - MergeMaterialsImagesTexturesAndSamplers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - - // merge meshes - MergeMeshes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - - // merge cameras - MergeCameras(mergeToRoot, mergeFromRootCopy); - - // merge nodes - MergeNodes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - - // merge animation, and skin - MergeAnimationsAndSkins(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - - // merge scenes - MergeScenes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); - } - - /// - /// Returns whether the input string is a Base64 uri. Images and buffers can both be encoded this way. - /// - /// The uri to check - /// Whether the input string is base64 encoded - public static bool IsBase64Uri(string uri) - { - const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,"; - - Regex regex = new Regex(Base64StringInitializer); - return regex.Match(uri).Success; - } - - private static long LoadBufferView(AttributeAccessor attributeAccessor, out byte[] bufferViewCache) - { - BufferView bufferView = attributeAccessor.AccessorId.Value.BufferView.Value; - long totalOffset = bufferView.ByteOffset + attributeAccessor.Offset; - - if (attributeAccessor.Stream is System.IO.MemoryStream) - { - using (var memoryStream = attributeAccessor.Stream as System.IO.MemoryStream) + foreach (GLTFAnimation animation in root.Animations) { -#if NETFX_CORE || NETSTANDARD1_3 - if (memoryStream.TryGetBuffer(out System.ArraySegment arraySegment)) + if (animation.Samplers != null) { - bufferViewCache = arraySegment.Array; - return totalOffset; + foreach (AnimationSampler animationSampler in animation.Samplers) + { + if (animationSampler.Input != null && animationSampler.Input.Id >= accessorCount) + { + animationSampler.Input = null; + } + if (animationSampler.Output != null && animationSampler.Output.Id >= accessorCount) + { + animationSampler.Output = null; + } + } } -#else - bufferViewCache = memoryStream.GetBuffer(); - return totalOffset; -#endif - } } - attributeAccessor.Stream.Position = totalOffset; - bufferViewCache = new byte[bufferView.ByteLength]; - attributeAccessor.Stream.Read(bufferViewCache, 0, bufferView.ByteLength); - return 0; - } - - private static void MergeExtensions(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) - { - // avoid duplicates for extension merging - if (mergeFromRoot.ExtensionsUsed != null) + if (root.BufferViews != null) { - if (mergeToRoot.ExtensionsUsed == null) + foreach (BufferView bufferView in root.BufferViews) { - mergeToRoot.ExtensionsUsed = new List(mergeFromRoot.ExtensionsUsed.Count); - } - - foreach (string extensionUsedToAdd in mergeFromRoot.ExtensionsUsed) - { - if (!mergeToRoot.ExtensionsUsed.Contains(extensionUsedToAdd)) + if (bufferView.Buffer != null && bufferView.Buffer.Id >= bufferCount) { - mergeToRoot.ExtensionsUsed.Add(extensionUsedToAdd); + bufferView.Buffer = null; } } } - - if (mergeFromRoot.ExtensionsRequired != null) + if (root.Images != null) { - if (mergeToRoot.ExtensionsRequired == null) + foreach (GLTFImage image in root.Images) { - mergeToRoot.ExtensionsRequired = new List(mergeFromRoot.ExtensionsRequired.Count); - } - - foreach (string extensionRequiredToAdd in mergeFromRoot.ExtensionsRequired) - { - if (!mergeToRoot.ExtensionsRequired.Contains(extensionRequiredToAdd)) + if (image.BufferView != null && image.BufferView.Id >= bufferViewCount) { - mergeToRoot.ExtensionsRequired.Add(extensionRequiredToAdd); + image.BufferView = null; } } } - } - - private static void MergeAccessorsBufferViewsAndBuffers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Buffers != null) + if (root.Materials != null) { - if (mergeToRoot.Buffers == null) - { - mergeToRoot.Buffers = new List(mergeFromRoot.Buffers.Count); - } - - mergeToRoot.Buffers.AddRange(mergeFromRoot.Buffers); - } - - if (mergeFromRoot.BufferViews != null) - { - if (mergeToRoot.BufferViews == null) - { - mergeToRoot.BufferViews = new List(mergeFromRoot.BufferViews.Count); - } - - mergeToRoot.BufferViews.AddRange(mergeFromRoot.BufferViews); - for (int i = previousGLTFSizes.PreviousBufferViewCount; i < mergeToRoot.BufferViews.Count; ++i) - { - GLTFId bufferId = mergeToRoot.BufferViews[i].Buffer; - bufferId.Id += previousGLTFSizes.PreviousBufferCount; - bufferId.Root = mergeToRoot; - } - } - - if (mergeFromRoot.Accessors != null) - { - if (mergeToRoot.Accessors == null) - { - mergeToRoot.Accessors = new List(mergeFromRoot.Accessors.Count); - } - - mergeToRoot.Accessors.AddRange(mergeFromRoot.Accessors); - for (int i = previousGLTFSizes.PreviousAccessorCount; i < mergeToRoot.Accessors.Count; ++i) + foreach (GLTFMaterial material in root.Materials) { - Accessor accessor = mergeToRoot.Accessors[i]; - - if (accessor.BufferView != null) + if (material.EmissiveTexture?.Index != null && material.EmissiveTexture.Index.Id >= textureCount) { - BufferViewId bufferViewId = accessor.BufferView; - bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; - bufferViewId.Root = mergeToRoot; + material.EmissiveTexture.Index = null; } - - AccessorSparse accessorSparse = accessor.Sparse; - if (accessorSparse != null) + if (material.NormalTexture?.Index != null && material.NormalTexture.Index.Id >= textureCount) { - BufferViewId indicesId = accessorSparse.Indices.BufferView; - indicesId.Id += previousGLTFSizes.PreviousBufferViewCount; - indicesId.Root = mergeToRoot; - - BufferViewId valuesId = accessorSparse.Values.BufferView; - valuesId.Id += previousGLTFSizes.PreviousBufferViewCount; - valuesId.Root = mergeToRoot; + material.NormalTexture.Index = null; } - } - } - } - - private static void MergeMaterialsImagesTexturesAndSamplers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Samplers != null) - { - if (mergeToRoot.Samplers == null) - { - mergeToRoot.Samplers = new List(mergeFromRoot.Samplers.Count); - } - - mergeToRoot.Samplers.AddRange(mergeFromRoot.Samplers); - } - - if (mergeFromRoot.Images != null) - { - if (mergeToRoot.Images == null) - { - mergeToRoot.Images = new List(mergeFromRoot.Images.Count); - } - - mergeToRoot.Images.AddRange(mergeFromRoot.Images); - for (int i = previousGLTFSizes.PreviousImageCount; i < mergeToRoot.Images.Count; ++i) - { - GLTFImage image = mergeToRoot.Images[i]; - if (image.BufferView != null) + if (material.OcclusionTexture?.Index != null && material.OcclusionTexture.Index.Id >= textureCount) { - BufferViewId bufferViewId = image.BufferView; - bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; - bufferViewId.Root = mergeToRoot; + material.OcclusionTexture.Index = null; } - } - } - - if (mergeFromRoot.Textures != null) - { - if (mergeToRoot.Textures == null) - { - mergeToRoot.Textures = new List(mergeFromRoot.Textures.Count); - } - - mergeToRoot.Textures.AddRange(mergeFromRoot.Textures); - for (int i = previousGLTFSizes.PreviousTextureCount; i < mergeToRoot.Textures.Count; ++i) - { - GLTFTexture texture = mergeToRoot.Textures[i]; - - if (texture.Sampler != null) + if (material.OcclusionTexture?.Index != null && material.OcclusionTexture.Index.Id >= textureCount) { - SamplerId samplerId = texture.Sampler; - samplerId.Id += previousGLTFSizes.PreviousSamplerCount; - samplerId.Root = mergeToRoot; + material.OcclusionTexture.Index = null; } - - if (texture.Source != null) + if (material.PbrMetallicRoughness != null) { - ImageId samplerId = texture.Source; - samplerId.Id += previousGLTFSizes.PreviousImageCount; - samplerId.Root = mergeToRoot; + if (material.PbrMetallicRoughness.BaseColorTexture?.Index != null && material.PbrMetallicRoughness.BaseColorTexture.Index.Id >= textureCount) + { + material.PbrMetallicRoughness.BaseColorTexture.Index = null; + } + if (material.PbrMetallicRoughness.MetallicRoughnessTexture?.Index != null && material.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id >= textureCount) + { + material.PbrMetallicRoughness.MetallicRoughnessTexture.Index = null; + } } } } - - if (mergeFromRoot.Materials != null) + if (root.Meshes != null) { - if (mergeToRoot.Materials == null) + foreach (GLTFMesh mesh in root.Meshes) { - mergeToRoot.Materials = new List(mergeFromRoot.Materials.Count); - } - - mergeToRoot.Materials.AddRange(mergeFromRoot.Materials); - for (int i = previousGLTFSizes.PreviousMaterialCount; i < mergeToRoot.Materials.Count; ++i) - { - GLTFMaterial material = mergeToRoot.Materials[i]; - - PbrMetallicRoughness pbrMetallicRoughness = material.PbrMetallicRoughness; - if (pbrMetallicRoughness != null) + if (mesh.Primitives != null) { - if (pbrMetallicRoughness.BaseColorTexture != null) - { - TextureId textureId = pbrMetallicRoughness.BaseColorTexture.Index; - textureId.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; - } - if (pbrMetallicRoughness.MetallicRoughnessTexture != null) + foreach (MeshPrimitive primitive in mesh.Primitives) { - TextureId textureId = pbrMetallicRoughness.MetallicRoughnessTexture.Index; - textureId.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; + if (primitive.Indices != null && primitive.Indices.Id >= accessorCount) + { + primitive.Indices = null; + } + if (primitive.Material != null && primitive.Material.Id >= accessorCount) + { + primitive.Material = null; + } } } - - MaterialCommonConstant commonConstant = material.CommonConstant; - if (commonConstant?.LightmapTexture != null) + } + } + if (root.Nodes != null) + { + foreach (Node node in root.Nodes) + { + if (node.Camera != null && node.Camera.Id >= cameraCount) { - TextureId textureId = material.CommonConstant.LightmapTexture.Index; - textureId.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; + node.Camera = null; } - - if (material.EmissiveTexture != null) + if (node.Children != null) { - TextureId textureId = material.EmissiveTexture.Index; - material.EmissiveTexture.Index.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; + for (int i = node.Children.Count - 1; i > 0; i--) + { + if (node.Children[i].Id >= nodeCount) + { + node.Children.RemoveAt(i); + } + } } - - if (material.NormalTexture != null) + if (node.Mesh != null && node.Mesh.Id >= meshCount) { - TextureId textureId = material.NormalTexture.Index; - textureId.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; + node.Mesh = null; } - - if (material.OcclusionTexture != null) + if (node.Skin != null && node.Skin.Id >= skinCount) { - TextureId textureId = material.OcclusionTexture.Index; - textureId.Id += previousGLTFSizes.PreviousTextureCount; - textureId.Root = mergeToRoot; + node.Skin = null; } } } - } - - private static void MergeMeshes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Meshes == null) return; - - if (mergeToRoot.Meshes == null) + if (root.Scenes != null) { - mergeToRoot.Meshes = new List(mergeFromRoot.Meshes.Count); - } - - mergeToRoot.Meshes.AddRange(mergeFromRoot.Meshes); - for (int i = previousGLTFSizes.PreviousMeshCount; i < mergeToRoot.Meshes.Count; ++i) - { - GLTFMesh mesh = mergeToRoot.Meshes[i]; - if (mesh.Primitives != null) + foreach (GLTFScene scene in root.Scenes) { - foreach (MeshPrimitive primitive in mesh.Primitives) + if (scene.Nodes != null) { - foreach (var attributeAccessorPair in primitive.Attributes) - { - AccessorId accessorId = attributeAccessorPair.Value; - accessorId.Id += previousGLTFSizes.PreviousAccessorCount; - accessorId.Root = mergeToRoot; - } - - if (primitive.Indices != null) + for (int i = scene.Nodes.Count - 1; i > 0; i--) { - AccessorId accessorId = primitive.Indices; - accessorId.Id += previousGLTFSizes.PreviousAccessorCount; - accessorId.Root = mergeToRoot; - } - - if (primitive.Material != null) - { - MaterialId materialId = primitive.Material; - materialId.Id += previousGLTFSizes.PreviousMaterialCount; - materialId.Root = mergeToRoot; - } - - if (primitive.Targets != null) - { - foreach (Dictionary targetsDictionary in primitive.Targets) + if (scene.Nodes[i].Id >= nodeCount) { - foreach (var targetsPair in targetsDictionary) - { - AccessorId accessorId = targetsPair.Value; - accessorId.Id += previousGLTFSizes.PreviousAccessorCount; - accessorId.Root = mergeToRoot; - } + scene.Nodes.RemoveAt(i); } } } } } - } - - private static void MergeCameras(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) - { - if (mergeFromRoot.Cameras == null) return; - if (mergeToRoot.Cameras == null) + if (root.Skins != null) { - mergeToRoot.Cameras = new List(mergeFromRoot.Cameras.Count); - } - - mergeToRoot.Cameras.AddRange(mergeFromRoot.Cameras); - } - - private static void MergeNodes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Nodes == null) return; - - if (mergeToRoot.Nodes == null) - { - mergeToRoot.Nodes = new List(mergeFromRoot.Nodes.Count); - } - - mergeToRoot.Nodes.AddRange(mergeFromRoot.Nodes); - - for (int i = previousGLTFSizes.PreviousNodeCount; i < mergeToRoot.Nodes.Count; ++i) - { - Node node = mergeToRoot.Nodes[i]; - if (node.Mesh != null) - { - MeshId meshId = node.Mesh; - meshId.Id += previousGLTFSizes.PreviousMeshCount; - node.Mesh.Root = mergeToRoot; - } - - if (node.Camera != null) + foreach (Skin skin in root.Skins) { - CameraId cameraId = node.Camera; - cameraId.Id += previousGLTFSizes.PreviousCameraCount; - cameraId.Root = mergeToRoot; - } - - if (node.Children != null) - { - foreach (NodeId child in node.Children) - { - child.Id += previousGLTFSizes.PreviousNodeCount; - child.Root = mergeToRoot; - } - } - - if (node.Skin != null) - { - SkinId skinId = node.Skin; - skinId.Id += previousGLTFSizes.PreviousSkinCount; - skinId.Root = mergeToRoot; - } - } - } - - private static void MergeAnimationsAndSkins(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Skins != null) - { - if (mergeToRoot.Skins == null) - { - mergeToRoot.Skins = new List(mergeFromRoot.Skins.Count); - } - - mergeToRoot.Skins.AddRange(mergeFromRoot.Skins); - for (int i = previousGLTFSizes.PreviousSkinCount; i < mergeToRoot.Skins.Count; ++i) - { - Skin skin = mergeToRoot.Skins[i]; - if (skin.InverseBindMatrices != null) - { - skin.InverseBindMatrices.Id += previousGLTFSizes.PreviousAccessorCount; - } - - if (skin.Skeleton != null) - { - skin.Skeleton.Id += previousGLTFSizes.PreviousNodeCount; - } - if (skin.Joints != null) { - foreach (NodeId joint in skin.Joints) + for (int i = skin.Joints.Count - 1; i > 0; i--) { - joint.Id += previousGLTFSizes.PreviousNodeCount; + if (skin.Joints[i].Id >= nodeCount) + { + skin.Joints.RemoveAt(i); + } } } + if (skin.Skeleton != null && skin.Skeleton.Id >= nodeCount) + { + skin.Skeleton = null; + } } } - - if (mergeFromRoot.Animations != null) + if (root.Textures != null) { - if (mergeToRoot.Animations == null) + foreach (GLTFTexture texture in root.Textures) { - mergeToRoot.Animations = new List(mergeFromRoot.Animations.Count); + if (texture.Sampler != null && texture.Sampler.Id >= samplersCount) + { + texture.Sampler = null; + } } + } + } - mergeToRoot.Animations.AddRange(mergeFromRoot.Animations); - - for (int i = previousGLTFSizes.PreviousAnimationCount; i < mergeToRoot.Animations.Count; ++i) + /// + /// Uses the accessor to parse the buffer into attributes needed to construct the mesh primitive + /// + /// A dictionary that contains a mapping of attribute name to data needed to parse + /// + /// Uses the accessor to parse the buffer into attributes needed to construct the mesh primitive + /// + /// A dictionary that contains a mapping of attribute name to data needed to parse + public static void BuildMeshAttributes(ref Dictionary attributes) + { + if (attributes.ContainsKey(SemanticProperties.POSITION)) + { + var attributeAccessor = attributes[SemanticProperties.POSITION]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsVertexArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.INDICES)) + { + var attributeAccessor = attributes[SemanticProperties.INDICES]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTriangles(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.NORMAL)) + { + var attributeAccessor = attributes[SemanticProperties.NORMAL]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsNormalArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.TexCoord(0))) + { + var attributeAccessor = attributes[SemanticProperties.TexCoord(0)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.TexCoord(1))) + { + var attributeAccessor = attributes[SemanticProperties.TexCoord(1)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.TexCoord(2))) + { + var attributeAccessor = attributes[SemanticProperties.TexCoord(2)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.TexCoord(3))) + { + var attributeAccessor = attributes[SemanticProperties.TexCoord(3)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTexcoordArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.Color(0))) + { + var attributeAccessor = attributes[SemanticProperties.Color(0)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsColorArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.TANGENT)) + { + var attributeAccessor = attributes[SemanticProperties.TANGENT]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsTangentArray(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.Weight(0))) + { + var attributeAccessor = attributes[SemanticProperties.Weight(0)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + if (attributes.ContainsKey(SemanticProperties.Joint(0))) + { + var attributeAccessor = attributes[SemanticProperties.Joint(0)]; + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + } + + public static void BuildBindPoseSamplers(ref AttributeAccessor attributeAccessor) + { + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + attributeAccessor.AccessorId.Value.AsMatrix4x4Array(ref resultArray, bufferViewCache, offset); + attributeAccessor.AccessorContent = resultArray; + } + + /// + /// Uses the accessor to parse the buffer into arrays needed to construct the animation + /// + /// A dictionary mapping AttributeAccessor lists to their target types + public static void BuildAnimationSamplers(ref Dictionary> samplers) + { + foreach (var samplerSet in samplers) + { + foreach (var attributeAccessor in samplerSet.Value) + { + NumericArray resultArray = attributeAccessor.AccessorContent; + uint offset = LoadBufferView(attributeAccessor, out byte[] bufferViewCache); + + switch (samplerSet.Key) + { + case "time": + attributeAccessor.AccessorId.Value.AsFloatArray(ref resultArray, bufferViewCache, offset); + break; + case "translation": + case "scale": + attributeAccessor.AccessorId.Value.AsVector3Array(ref resultArray, bufferViewCache, offset); + break; + case "rotation": + attributeAccessor.AccessorId.Value.AsVector4Array(ref resultArray, bufferViewCache, offset); + break; + } + + attributeAccessor.AccessorContent = resultArray; + } + } + } + + /// + /// Merges the right root into the left root + /// This function combines all of the lists of objects on each glTF root together and updates the relative indicies + /// All properties all merged except Asset and Default, which will stay "mergeToRoot"'s value + /// + /// The node to merge into + /// The node to merge from + public static void MergeGLTF(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) + { + PreviousGLTFSizes previousGLTFSize = new PreviousGLTFSizes() + { + PreviousAccessorCount = mergeToRoot.Accessors?.Count ?? 0, + PreviousBufferCount = mergeToRoot.Buffers?.Count ?? 0, + PreviousAnimationCount = mergeToRoot.Animations?.Count ?? 0, + PreviousBufferViewCount = mergeToRoot.BufferViews?.Count ?? 0, + PreviousCameraCount = mergeToRoot.Cameras?.Count ?? 0, + PreviousImageCount = mergeToRoot.Images?.Count ?? 0, + PreviousMaterialCount = mergeToRoot.Materials?.Count ?? 0, + PreviousMeshCount = mergeToRoot.Meshes?.Count ?? 0, + PreviousNodeCount = mergeToRoot.Nodes?.Count ?? 0, + PreviousSamplerCount = mergeToRoot.Samplers?.Count ?? 0, + PreviousSceneCount = mergeToRoot.Scenes?.Count ?? 0, + PreviousSkinCount = mergeToRoot.Skins?.Count ?? 0, + PreviousTextureCount = mergeToRoot.Textures?.Count ?? 0 + }; + + GLTFRoot mergeFromRootCopy = new GLTFRoot(mergeFromRoot); + + // for each type: + // 1) add the right hand range to the left hand object + // 2) update all ids to be based off of the appended size + + // merge extensions + MergeExtensions(mergeToRoot, mergeFromRootCopy); + + // merge accessors, buffers, and bufferviews + MergeAccessorsBufferViewsAndBuffers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + + // merge materials, samplers, images, and textures + MergeMaterialsImagesTexturesAndSamplers(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + + // merge meshes + MergeMeshes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + + // merge cameras + MergeCameras(mergeToRoot, mergeFromRootCopy); + + // merge nodes + MergeNodes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + + // merge animation, and skin + MergeAnimationsAndSkins(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + + // merge scenes + MergeScenes(mergeToRoot, mergeFromRootCopy, previousGLTFSize); + } + + /// + /// Returns whether the input string is a Base64 uri. Images and buffers can both be encoded this way. + /// + /// The uri to check + /// Whether the input string is base64 encoded + public static bool IsBase64Uri(string uri) + { + const string Base64StringInitializer = "^data:[a-z-]+/[a-z-]+;base64,"; + + Regex regex = new Regex(Base64StringInitializer); + return regex.Match(uri).Success; + } + + private static uint LoadBufferView(AttributeAccessor attributeAccessor, out byte[] bufferViewCache) + { + BufferView bufferView = attributeAccessor.AccessorId.Value.BufferView.Value; + uint totalOffset = bufferView.ByteOffset + attributeAccessor.Offset; +#if !NETFX_CORE + if (attributeAccessor.Stream is System.IO.MemoryStream) + { + using (var memoryStream = attributeAccessor.Stream as System.IO.MemoryStream) + { +#if NETFX_CORE || NETSTANDARD1_3 + if (memoryStream.TryGetBuffer(out System.ArraySegment arraySegment)) + { + bufferViewCache = arraySegment.Array; + return totalOffset; + } +#else + bufferViewCache = memoryStream.GetBuffer(); + return totalOffset; +#endif + + } + } +#endif + attributeAccessor.Stream.Position = totalOffset; + bufferViewCache = new byte[bufferView.ByteLength]; + + // stream.Read only accepts int for length + uint remainingSize = bufferView.ByteLength; + while (remainingSize != 0) + { + int sizeToLoad = (int)System.Math.Min(remainingSize, int.MaxValue); + attributeAccessor.Stream.Read(bufferViewCache, (int)(bufferView.ByteLength - remainingSize), sizeToLoad); + remainingSize -= (uint)sizeToLoad; + } + return 0; + } + + private static void MergeExtensions(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) + { + // avoid duplicates for extension merging + if (mergeFromRoot.ExtensionsUsed != null) + { + if (mergeToRoot.ExtensionsUsed == null) + { + mergeToRoot.ExtensionsUsed = new List(mergeFromRoot.ExtensionsUsed.Count); + } + + foreach (string extensionUsedToAdd in mergeFromRoot.ExtensionsUsed) + { + if (!mergeToRoot.ExtensionsUsed.Contains(extensionUsedToAdd)) + { + mergeToRoot.ExtensionsUsed.Add(extensionUsedToAdd); + } + } + } + + if (mergeFromRoot.ExtensionsRequired != null) + { + if (mergeToRoot.ExtensionsRequired == null) + { + mergeToRoot.ExtensionsRequired = new List(mergeFromRoot.ExtensionsRequired.Count); + } + + foreach (string extensionRequiredToAdd in mergeFromRoot.ExtensionsRequired) + { + if (!mergeToRoot.ExtensionsRequired.Contains(extensionRequiredToAdd)) + { + mergeToRoot.ExtensionsRequired.Add(extensionRequiredToAdd); + } + } + } + } + + private static void MergeAccessorsBufferViewsAndBuffers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + bool isGLB = false; + + if (mergeFromRoot.Buffers != null) + { + if (mergeToRoot.Buffers == null) + { + mergeToRoot.Buffers = new List(mergeFromRoot.Buffers.Count); + } + + foreach (GLTFBuffer buffer in mergeFromRoot.Buffers) { - GLTFAnimation animation = mergeToRoot.Animations[i]; - foreach (AnimationSampler sampler in animation.Samplers) + if (buffer.Uri != null) { - AccessorId inputId = sampler.Input; - inputId.Id += previousGLTFSizes.PreviousAccessorCount; - inputId.Root = mergeToRoot; - - AccessorId outputId = sampler.Output; - outputId.Id += previousGLTFSizes.PreviousAccessorCount; - outputId.Root = mergeToRoot; + mergeToRoot.Buffers.Add(buffer); } - - foreach (AnimationChannel channel in animation.Channels) + else { - SamplerId samplerId = channel.Sampler; - samplerId.Id += previousGLTFSizes.PreviousSamplerCount; - samplerId.Root = mergeToRoot; - - NodeId nodeId = channel.Target.Node; - nodeId.Id += previousGLTFSizes.PreviousNodeCount; - nodeId.Root = mergeToRoot; + isGLB = true; // assume glb is a uri is null } } - } - } - - private static void MergeScenes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) - { - if (mergeFromRoot.Scenes == null) return; - - if (mergeToRoot.Scenes == null) - { + } + + if (mergeFromRoot.BufferViews != null) + { + if (mergeToRoot.BufferViews == null) + { + mergeToRoot.BufferViews = new List(mergeFromRoot.BufferViews.Count); + } + + mergeToRoot.BufferViews.AddRange(mergeFromRoot.BufferViews); + for (int i = previousGLTFSizes.PreviousBufferViewCount; i < mergeToRoot.BufferViews.Count; ++i) + { + GLTFId bufferId = mergeToRoot.BufferViews[i].Buffer; + if (!(isGLB && bufferId.Id == 0)) // if it is pointing a the special glb buffer (index 0 of a glb) then we dont want to adjust the buffer view, otherwise we do + { + // adjusting bufferview id based on merge amount + bufferId.Id += previousGLTFSizes.PreviousBufferCount; + bufferId.Root = mergeToRoot; + } + } + } + + if (mergeFromRoot.Accessors != null) + { + if (mergeToRoot.Accessors == null) + { + mergeToRoot.Accessors = new List(mergeFromRoot.Accessors.Count); + } + + mergeToRoot.Accessors.AddRange(mergeFromRoot.Accessors); + for (int i = previousGLTFSizes.PreviousAccessorCount; i < mergeToRoot.Accessors.Count; ++i) + { + Accessor accessor = mergeToRoot.Accessors[i]; + + if (accessor.BufferView != null) + { + BufferViewId bufferViewId = accessor.BufferView; + bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; + bufferViewId.Root = mergeToRoot; + } + + AccessorSparse accessorSparse = accessor.Sparse; + if (accessorSparse != null) + { + BufferViewId indicesId = accessorSparse.Indices.BufferView; + indicesId.Id += previousGLTFSizes.PreviousBufferViewCount; + indicesId.Root = mergeToRoot; + + BufferViewId valuesId = accessorSparse.Values.BufferView; + valuesId.Id += previousGLTFSizes.PreviousBufferViewCount; + valuesId.Root = mergeToRoot; + } + } + } + } + + private static void MergeMaterialsImagesTexturesAndSamplers(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + if (mergeFromRoot.Samplers != null) + { + if (mergeToRoot.Samplers == null) + { + mergeToRoot.Samplers = new List(mergeFromRoot.Samplers.Count); + } + + mergeToRoot.Samplers.AddRange(mergeFromRoot.Samplers); + } + + if (mergeFromRoot.Images != null) + { + if (mergeToRoot.Images == null) + { + mergeToRoot.Images = new List(mergeFromRoot.Images.Count); + } + + mergeToRoot.Images.AddRange(mergeFromRoot.Images); + for (int i = previousGLTFSizes.PreviousImageCount; i < mergeToRoot.Images.Count; ++i) + { + GLTFImage image = mergeToRoot.Images[i]; + if (image.BufferView != null) + { + BufferViewId bufferViewId = image.BufferView; + bufferViewId.Id += previousGLTFSizes.PreviousBufferViewCount; + bufferViewId.Root = mergeToRoot; + } + } + } + + if (mergeFromRoot.Textures != null) + { + if (mergeToRoot.Textures == null) + { + mergeToRoot.Textures = new List(mergeFromRoot.Textures.Count); + } + + mergeToRoot.Textures.AddRange(mergeFromRoot.Textures); + for (int i = previousGLTFSizes.PreviousTextureCount; i < mergeToRoot.Textures.Count; ++i) + { + GLTFTexture texture = mergeToRoot.Textures[i]; + + if (texture.Sampler != null) + { + SamplerId samplerId = texture.Sampler; + samplerId.Id += previousGLTFSizes.PreviousSamplerCount; + samplerId.Root = mergeToRoot; + } + + if (texture.Source != null) + { + ImageId samplerId = texture.Source; + samplerId.Id += previousGLTFSizes.PreviousImageCount; + samplerId.Root = mergeToRoot; + } + } + } + + if (mergeFromRoot.Materials != null) + { + if (mergeToRoot.Materials == null) + { + mergeToRoot.Materials = new List(mergeFromRoot.Materials.Count); + } + + mergeToRoot.Materials.AddRange(mergeFromRoot.Materials); + for (int i = previousGLTFSizes.PreviousMaterialCount; i < mergeToRoot.Materials.Count; ++i) + { + GLTFMaterial material = mergeToRoot.Materials[i]; + + PbrMetallicRoughness pbrMetallicRoughness = material.PbrMetallicRoughness; + if (pbrMetallicRoughness != null) + { + if (pbrMetallicRoughness.BaseColorTexture != null) + { + TextureId textureId = pbrMetallicRoughness.BaseColorTexture.Index; + textureId.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + if (pbrMetallicRoughness.MetallicRoughnessTexture != null) + { + TextureId textureId = pbrMetallicRoughness.MetallicRoughnessTexture.Index; + textureId.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + } + + MaterialCommonConstant commonConstant = material.CommonConstant; + if (commonConstant?.LightmapTexture != null) + { + TextureId textureId = material.CommonConstant.LightmapTexture.Index; + textureId.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + + if (material.EmissiveTexture != null) + { + TextureId textureId = material.EmissiveTexture.Index; + material.EmissiveTexture.Index.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + + if (material.NormalTexture != null) + { + TextureId textureId = material.NormalTexture.Index; + textureId.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + + if (material.OcclusionTexture != null) + { + TextureId textureId = material.OcclusionTexture.Index; + textureId.Id += previousGLTFSizes.PreviousTextureCount; + textureId.Root = mergeToRoot; + } + } + } + } + + private static void MergeMeshes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + if (mergeFromRoot.Meshes == null) return; + + if (mergeToRoot.Meshes == null) + { + mergeToRoot.Meshes = new List(mergeFromRoot.Meshes.Count); + } + + mergeToRoot.Meshes.AddRange(mergeFromRoot.Meshes); + for (int i = previousGLTFSizes.PreviousMeshCount; i < mergeToRoot.Meshes.Count; ++i) + { + GLTFMesh mesh = mergeToRoot.Meshes[i]; + if (mesh.Primitives != null) + { + foreach (MeshPrimitive primitive in mesh.Primitives) + { + foreach (var attributeAccessorPair in primitive.Attributes) + { + AccessorId accessorId = attributeAccessorPair.Value; + accessorId.Id += previousGLTFSizes.PreviousAccessorCount; + accessorId.Root = mergeToRoot; + } + + if (primitive.Indices != null) + { + AccessorId accessorId = primitive.Indices; + accessorId.Id += previousGLTFSizes.PreviousAccessorCount; + accessorId.Root = mergeToRoot; + } + + if (primitive.Material != null) + { + MaterialId materialId = primitive.Material; + materialId.Id += previousGLTFSizes.PreviousMaterialCount; + materialId.Root = mergeToRoot; + } + + if (primitive.Targets != null) + { + foreach (Dictionary targetsDictionary in primitive.Targets) + { + foreach (var targetsPair in targetsDictionary) + { + AccessorId accessorId = targetsPair.Value; + accessorId.Id += previousGLTFSizes.PreviousAccessorCount; + accessorId.Root = mergeToRoot; + } + } + } + } + } + } + } + + private static void MergeCameras(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot) + { + if (mergeFromRoot.Cameras == null) return; + if (mergeToRoot.Cameras == null) + { + mergeToRoot.Cameras = new List(mergeFromRoot.Cameras.Count); + } + + mergeToRoot.Cameras.AddRange(mergeFromRoot.Cameras); + } + + private static void MergeNodes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + if (mergeFromRoot.Nodes == null) return; + + if (mergeToRoot.Nodes == null) + { + mergeToRoot.Nodes = new List(mergeFromRoot.Nodes.Count); + } + + mergeToRoot.Nodes.AddRange(mergeFromRoot.Nodes); + + for (int i = previousGLTFSizes.PreviousNodeCount; i < mergeToRoot.Nodes.Count; ++i) + { + Node node = mergeToRoot.Nodes[i]; + if (node.Mesh != null) + { + MeshId meshId = node.Mesh; + meshId.Id += previousGLTFSizes.PreviousMeshCount; + node.Mesh.Root = mergeToRoot; + } + + if (node.Camera != null) + { + CameraId cameraId = node.Camera; + cameraId.Id += previousGLTFSizes.PreviousCameraCount; + cameraId.Root = mergeToRoot; + } + + if (node.Children != null) + { + foreach (NodeId child in node.Children) + { + child.Id += previousGLTFSizes.PreviousNodeCount; + child.Root = mergeToRoot; + } + } + + if (node.Skin != null) + { + SkinId skinId = node.Skin; + skinId.Id += previousGLTFSizes.PreviousSkinCount; + skinId.Root = mergeToRoot; + } + } + } + + private static void MergeAnimationsAndSkins(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + if (mergeFromRoot.Skins != null) + { + if (mergeToRoot.Skins == null) + { + mergeToRoot.Skins = new List(mergeFromRoot.Skins.Count); + } + + mergeToRoot.Skins.AddRange(mergeFromRoot.Skins); + for (int i = previousGLTFSizes.PreviousSkinCount; i < mergeToRoot.Skins.Count; ++i) + { + Skin skin = mergeToRoot.Skins[i]; + if (skin.InverseBindMatrices != null) + { + skin.InverseBindMatrices.Id += previousGLTFSizes.PreviousAccessorCount; + } + + if (skin.Skeleton != null) + { + skin.Skeleton.Id += previousGLTFSizes.PreviousNodeCount; + } + + if (skin.Joints != null) + { + foreach (NodeId joint in skin.Joints) + { + joint.Id += previousGLTFSizes.PreviousNodeCount; + } + } + } + } + + if (mergeFromRoot.Animations != null) + { + if (mergeToRoot.Animations == null) + { + mergeToRoot.Animations = new List(mergeFromRoot.Animations.Count); + } + + mergeToRoot.Animations.AddRange(mergeFromRoot.Animations); + + for (int i = previousGLTFSizes.PreviousAnimationCount; i < mergeToRoot.Animations.Count; ++i) + { + GLTFAnimation animation = mergeToRoot.Animations[i]; + foreach (AnimationSampler sampler in animation.Samplers) + { + AccessorId inputId = sampler.Input; + inputId.Id += previousGLTFSizes.PreviousAccessorCount; + inputId.Root = mergeToRoot; + + AccessorId outputId = sampler.Output; + outputId.Id += previousGLTFSizes.PreviousAccessorCount; + outputId.Root = mergeToRoot; + } + + foreach (AnimationChannel channel in animation.Channels) + { + SamplerId samplerId = channel.Sampler; + samplerId.Id += previousGLTFSizes.PreviousSamplerCount; + samplerId.Root = mergeToRoot; + + NodeId nodeId = channel.Target.Node; + nodeId.Id += previousGLTFSizes.PreviousNodeCount; + nodeId.Root = mergeToRoot; + } + } + } + } + + private static void MergeScenes(GLTFRoot mergeToRoot, GLTFRoot mergeFromRoot, PreviousGLTFSizes previousGLTFSizes) + { + if (mergeFromRoot.Scenes == null) return; + + if (mergeToRoot.Scenes == null) + { mergeToRoot.Scenes = new List(mergeFromRoot.Scenes.Count); - } - - mergeToRoot.Scenes.AddRange(mergeFromRoot.Scenes); - for (int i = previousGLTFSizes.PreviousSceneCount; i < mergeToRoot.Scenes.Count; ++i) - { + } + + mergeToRoot.Scenes.AddRange(mergeFromRoot.Scenes); + for (int i = previousGLTFSizes.PreviousSceneCount; i < mergeToRoot.Scenes.Count; ++i) + { GLTFScene scene = mergeToRoot.Scenes[i]; - foreach (NodeId nodeId in scene.Nodes) - { - nodeId.Id += previousGLTFSizes.PreviousNodeCount; - nodeId.Root = mergeToRoot; - } - } - } - - private static string UpdateCanonicalPath(string oldPath, string newCanonicalPath) - { - string fileName = Path.GetFileName(oldPath); - return newCanonicalPath + Path.DirectorySeparatorChar + fileName; - } - } -} + foreach (NodeId nodeId in scene.Nodes) + { + nodeId.Id += previousGLTFSizes.PreviousNodeCount; + nodeId.Root = mergeToRoot; + } + } + } + + private static string UpdateCanonicalPath(string oldPath, string newCanonicalPath) + { + string fileName = Path.GetFileName(oldPath); + return newCanonicalPath + Path.DirectorySeparatorChar + fileName; + } + } +} diff --git a/GLTFSerialization/GLTFSerialization/GLTFObject.cs b/GLTFSerialization/GLTFSerialization/GLTFObject.cs new file mode 100644 index 000000000..4af4878b4 --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/GLTFObject.cs @@ -0,0 +1,20 @@ +using GLTF.Schema; + +namespace GLTF +{ + /// + /// Represents a glTF file (specifically not GLB) + /// + public class GLTFObject : IGLTFObject + { + public GLTFObject(GLTFRoot root) + { + Root = root; + } + + /// + /// Parse glTF root into + /// + public GLTFRoot Root { get; internal set; } + } +} diff --git a/GLTFSerialization/GLTFSerialization/GLTFParser.cs b/GLTFSerialization/GLTFSerialization/GLTFParser.cs index bde4c46e6..64b1da0a3 100644 --- a/GLTFSerialization/GLTFSerialization/GLTFParser.cs +++ b/GLTFSerialization/GLTFSerialization/GLTFParser.cs @@ -1,29 +1,48 @@ +using GLTF.Schema; using System; +using System.Collections.Generic; using System.IO; -using GLTF.Schema; namespace GLTF { - public class GLTFParser + public enum ChunkFormat : uint { + JSON = 0x4e4f534a, + BIN = 0x004e4942 + } - private enum ChunkFormat : uint - { - JSON = 0x4e4f534a, - BIN = 0x004e4942 - } + /// + /// Information containing parsed GLB Header + /// + public struct GLBHeader + { + public uint Version { get; set; } + public uint FileLength { get; set; } + } + + /// + /// Infomration that contains parsed chunk + /// + public struct ChunkInfo + { + public long StartPosition; + public uint Length; + public ChunkFormat Type; + } + + public class GLTFParser + { + public static readonly uint HEADER_SIZE = 12; + public static readonly uint CHUNK_HEADER_SIZE = 8; + public static readonly uint MAGIC_NUMBER = 0x46546c67; - internal struct GLBHeader - { - public uint Version { get; set; } - public uint FileLength { get; set; } - } - public static void ParseJson(Stream stream, out GLTFRoot gltfRoot, long startPosition = 0) { stream.Position = startPosition; + bool isGLB = IsGLB(stream); + // Check for binary format magic bytes - if (IsGLB(stream)) + if (isGLB) { ParseJsonChunk(stream, startPosition); } @@ -33,10 +52,12 @@ public static void ParseJson(Stream stream, out GLTFRoot gltfRoot, long startPos } gltfRoot = GLTFRoot.Deserialize(new StreamReader(stream)); + gltfRoot.IsGLB = isGLB; } - + + // todo: this needs reimplemented. There is no such thing as a binary chunk index, and the chunk may not be in 0, 1, 2 order // Moves stream position to binary chunk location - public static void SeekToBinaryChunk(Stream stream, int binaryChunkIndex, long startPosition = 0) + public static ChunkInfo SeekToBinaryChunk(Stream stream, int binaryChunkIndex, long startPosition = 0) { stream.Position = startPosition + 4; // start after magic number chunk GLBHeader header = ParseGLBHeader(stream); @@ -53,19 +74,24 @@ public static void SeekToBinaryChunk(Stream stream, int binaryChunkIndex, long s // Load Binary Chunk if (chunkOffset + chunkLength <= header.FileLength) { - uint chunkType = GetUInt32(stream); - if (chunkType != (uint)ChunkFormat.BIN) + ChunkFormat chunkType = (ChunkFormat)GetUInt32(stream); + if (chunkType != ChunkFormat.BIN) { throw new GLTFHeaderInvalidException("Second chunk must be of type BIN if present"); } + + return new ChunkInfo + { + StartPosition = stream.Position - CHUNK_HEADER_SIZE, + Length = chunkLength, + Type = chunkType + }; } - else - { - throw new GLTFHeaderInvalidException("File length does not match chunk header."); - } + + throw new GLTFHeaderInvalidException("File length does not match chunk header."); } - private static GLBHeader ParseGLBHeader(Stream stream) + public static GLBHeader ParseGLBHeader(Stream stream) { uint version = GetUInt32(stream); // 4 uint length = GetUInt32(stream); // 8 @@ -77,11 +103,46 @@ private static GLBHeader ParseGLBHeader(Stream stream) }; } - private static bool IsGLB(Stream stream) + public static bool IsGLB(Stream stream) { return GetUInt32(stream) == 0x46546c67; // 0 } + public static ChunkInfo ParseChunkInfo(Stream stream) + { + ChunkInfo chunkInfo = new ChunkInfo + { + StartPosition = stream.Position + }; + + chunkInfo.Length = GetUInt32(stream); // 12 + chunkInfo.Type = (ChunkFormat)GetUInt32(stream); // 16 + return chunkInfo; + } + + public static List FindChunks(Stream stream, long startPosition = 0) + { + stream.Position = startPosition + 4; // start after magic number bytes (4 bytes past) + ParseGLBHeader(stream); + List allChunks = new List(); + + // we only need to search for top two chunks (the JSON and binary chunks are guarenteed to be the top two chunks) + // other chunks can be in the file but we do not care about them + for (int i = 0; i < 2; ++i) + { + if (stream.Position == stream.Length) + { + break; + } + + ChunkInfo chunkInfo = ParseChunkInfo(stream); + allChunks.Add(chunkInfo); + stream.Position += chunkInfo.Length; + } + + return allChunks; + } + private static void ParseJsonChunk(Stream stream, long startPosition) { GLBHeader header = ParseGLBHeader(stream); // 4, 8 @@ -95,9 +156,8 @@ private static void ParseJsonChunk(Stream stream, long startPosition) throw new GLTFHeaderInvalidException("File length does not match header."); } - int chunkLength = (int)GetUInt32(stream); // 12 - var chunkType = GetUInt32(stream); // 16 - if (chunkType != (uint)ChunkFormat.JSON) + ChunkInfo chunkInfo = ParseChunkInfo(stream); + if (chunkInfo.Type != ChunkFormat.JSON) { throw new GLTFHeaderInvalidException("First chunk must be of type JSON"); } diff --git a/GLTFSerialization/GLTFSerialization/GLTFSerialization.csproj b/GLTFSerialization/GLTFSerialization/GLTFSerialization.csproj index 35dfc2a79..baf140fef 100644 --- a/GLTFSerialization/GLTFSerialization/GLTFSerialization.csproj +++ b/GLTFSerialization/GLTFSerialization/GLTFSerialization.csproj @@ -79,8 +79,12 @@ + + + + @@ -124,15 +128,12 @@ + + + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file diff --git a/GLTFSerialization/GLTFSerialization/IGLTFObject.cs b/GLTFSerialization/GLTFSerialization/IGLTFObject.cs new file mode 100644 index 000000000..3911b2585 --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/IGLTFObject.cs @@ -0,0 +1,12 @@ +using GLTF.Schema; + +namespace GLTF +{ + /// + /// Represents a GLTFObject + /// + public interface IGLTFObject + { + GLTFRoot Root { get; } + } +} diff --git a/GLTFSerialization/GLTFSerialization/Schema/Accessor.cs b/GLTFSerialization/GLTFSerialization/Schema/Accessor.cs index 9d2696c42..99477ee1e 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/Accessor.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/Accessor.cs @@ -5,6 +5,7 @@ using GLTF.Math; using Newtonsoft.Json; using System.Runtime.InteropServices; +using GLTF.Utilities; namespace GLTF.Schema { @@ -21,7 +22,7 @@ public class Accessor : GLTFChildOfRootProperty /// This must be a multiple of the size of the component datatype. /// 0 /// - public int ByteOffset; + public uint ByteOffset; /// /// The datatype of components in the attribute. @@ -46,7 +47,7 @@ public class Accessor : GLTFChildOfRootProperty /// with the number of bytes or number of components. /// 1 /// - public int Count; + public uint Count; /// /// Specifies if the attribute is a scalar, vector, or matrix, @@ -102,10 +103,10 @@ public Accessor() public Accessor(Accessor accessor, GLTFRoot gltfRoot) : base(accessor, gltfRoot) { - if (accessor == null) - { - return; - } + if (accessor == null) + { + return; + } if (accessor.BufferView != null) { @@ -147,7 +148,7 @@ public static Accessor Deserialize(GLTFRoot root, JsonReader reader) accessor.BufferView = BufferViewId.Deserialize(root, reader); break; case "byteOffset": - accessor.ByteOffset = reader.ReadAsInt32().Value; + accessor.ByteOffset = reader.ReadDoubleAsUInt32(); break; case "componentType": accessor.ComponentType = (GLTFComponentType)reader.ReadAsInt32().Value; @@ -156,7 +157,7 @@ public static Accessor Deserialize(GLTFRoot root, JsonReader reader) accessor.Normalized = reader.ReadAsBoolean().Value; break; case "count": - accessor.Count = reader.ReadAsInt32().Value; + accessor.Count = reader.ReadDoubleAsUInt32(); break; case "type": accessor.Type = reader.ReadStringEnum(); @@ -224,7 +225,7 @@ public override void Serialize(JsonWriter writer) writer.WriteEndArray(); } - if(!isMinNull) + if (!isMinNull) { writer.WritePropertyName("min"); writer.WriteStartArray(); @@ -237,7 +238,7 @@ public override void Serialize(JsonWriter writer) if (Sparse != null) { - if(isMinNull || isMaxNull) + if (isMinNull || isMaxNull) { throw new JsonSerializationException("Min and max attribute cannot be null when attribute is sparse"); } @@ -251,39 +252,51 @@ public override void Serialize(JsonWriter writer) writer.WriteEndObject(); } - private static sbyte GetByteElement(byte[] buffer, int byteOffset) + private static sbyte GetByteElement(byte[] buffer, uint byteOffset) { return Convert.ToSByte(GetUByteElement(buffer, byteOffset)); } - private static byte GetUByteElement(byte[] buffer, int byteOffset) + private static byte GetUByteElement(byte[] buffer, uint byteOffset) { return buffer[byteOffset]; // should only be byte size long } - private static short GetShortElement(byte[] buffer, int byteOffset) + private static unsafe short GetShortElement(byte[] buffer, uint byteOffset) { - return BitConverter.ToInt16(buffer, byteOffset); + fixed (byte* offsetBuffer = &buffer[byteOffset]) + { + return *((short*)offsetBuffer); + } } - private static ushort GetUShortElement(byte[] buffer, int byteOffset) + private static unsafe ushort GetUShortElement(byte[] buffer, uint byteOffset) { - return BitConverter.ToUInt16(buffer, byteOffset); + fixed (byte* offsetBuffer = &buffer[byteOffset]) + { + return *((ushort*)offsetBuffer); + } } - private static uint GetUIntElement(byte[] buffer, int byteOffset) + private static unsafe uint GetUIntElement(byte[] buffer, uint byteOffset) { - return BitConverter.ToUInt32(buffer, byteOffset); + fixed (byte* offsetBuffer = &buffer[byteOffset]) + { + return *((uint*)offsetBuffer); + } } - private static float GetFloatElement(byte[] buffer, int byteOffset) + private static unsafe float GetFloatElement(byte[] buffer, uint byteOffset) { - return BitConverter.ToSingle(buffer, byteOffset); + fixed (byte* offsetBuffer = &buffer[byteOffset]) + { + return *((float*)offsetBuffer); + } } private static void GetTypeDetails( GLTFComponentType type, - out int componentSize, + out uint componentSize, out float maxValue) { componentSize = 1; @@ -320,28 +333,28 @@ private static float GetFloatElement(byte[] buffer, int byteOffset) } } - public uint[] AsUIntArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public uint[] AsUIntArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsUInts != null) - { - return contents.AsUInts; - } + if (contents.AsUInts != null) + { + return contents.AsUInts; + } - if (Type != GLTFAccessorAttributeType.SCALAR) - { - return null; - } + if (Type != GLTFAccessorAttributeType.SCALAR) + { + return null; + } var arr = new uint[Count]; var totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize; + uint stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize; - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) arr[idx] = (uint)System.Math.Floor(GetFloatElement(bufferViewData, totalByteOffset + idx * stride)); @@ -353,28 +366,28 @@ public uint[] AsUIntArray(ref NumericArray contents, byte[] bufferViewData, int return arr; } - public float[] AsFloatArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public float[] AsFloatArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsUInts != null) - { - return contents.AsFloats; - } + if (contents.AsUInts != null) + { + return contents.AsFloats; + } - if (Type != GLTFAccessorAttributeType.SCALAR) - { - return null; - } + if (Type != GLTFAccessorAttributeType.SCALAR) + { + return null; + } var arr = new float[Count]; - var totalByteOffset = ByteOffset + offset; + uint totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize; + uint stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize; - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) arr[idx] = GetFloatElement(bufferViewData, totalByteOffset + idx * stride); @@ -386,34 +399,34 @@ public float[] AsFloatArray(ref NumericArray contents, byte[] bufferViewData, in return arr; } - public Vector2[] AsVector2Array(ref NumericArray contents, byte[] bufferViewData, int offset, bool normalizeIntValues = true) + public Vector2[] AsVector2Array(ref NumericArray contents, byte[] bufferViewData, uint offset, bool normalizeIntValues = true) { - if (contents.AsVec2s != null) - { - return contents.AsVec2s; - } + if (contents.AsVec2s != null) + { + return contents.AsVec2s; + } - if (Type != GLTFAccessorAttributeType.VEC2) - { - return null; - } + if (Type != GLTFAccessorAttributeType.VEC2) + { + return null; + } - if (ComponentType == GLTFComponentType.UnsignedInt) - { - return null; - } + if (ComponentType == GLTFComponentType.UnsignedInt) + { + return null; + } var arr = new Vector2[Count]; var totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 2; + uint stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 2; if (normalizeIntValues) maxValue = 1; - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) { @@ -431,34 +444,29 @@ public Vector2[] AsVector2Array(ref NumericArray contents, byte[] bufferViewData return arr; } - public Vector3[] AsVector3Array(ref NumericArray contents, byte[] bufferViewData, int offset, bool normalizeIntValues = true) + public Vector3[] AsVector3Array(ref NumericArray contents, byte[] bufferViewData, uint offset, bool normalizeIntValues = true) { - if (contents.AsVec3s != null) - { - return contents.AsVec3s; - } - - if (Type != GLTFAccessorAttributeType.VEC3) - { - return null; - } + if (contents.AsVec3s != null) + { + return contents.AsVec3s; + } - if (ComponentType == GLTFComponentType.UnsignedInt) - { - return null; - } + if (Type != GLTFAccessorAttributeType.VEC3) + { + return null; + } var arr = new Vector3[Count]; var totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 3; + uint stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 3; if (normalizeIntValues) maxValue = 1; - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) { @@ -478,34 +486,34 @@ public Vector3[] AsVector3Array(ref NumericArray contents, byte[] bufferViewData return arr; } - public Vector4[] AsVector4Array(ref NumericArray contents, byte[] bufferViewData, int offset, bool normalizeIntValues = true) - { - if (contents.AsVec4s != null) - { - return contents.AsVec4s; - } - - if (Type != GLTFAccessorAttributeType.VEC4) - { - return null; - } - - if (ComponentType == GLTFComponentType.UnsignedInt) - { - return null; - } - + public Vector4[] AsVector4Array(ref NumericArray contents, byte[] bufferViewData, uint offset, bool normalizeIntValues = true) + { + if (contents.AsVec4s != null) + { + return contents.AsVec4s; + } + + if (Type != GLTFAccessorAttributeType.VEC4) + { + return null; + } + + if (ComponentType == GLTFComponentType.UnsignedInt) + { + return null; + } + var arr = new Vector4[Count]; var totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 4; + uint stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 4; if (normalizeIntValues) maxValue = 1; - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) { @@ -527,33 +535,33 @@ public Vector4[] AsVector4Array(ref NumericArray contents, byte[] bufferViewData return arr; } - public Color[] AsColorArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public Color[] AsColorArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsColors != null) - { - return contents.AsColors; - } - - if (Type != GLTFAccessorAttributeType.VEC3 && Type != GLTFAccessorAttributeType.VEC4) - { - return null; - } - - if (ComponentType == GLTFComponentType.UnsignedInt) - { - return null; - } - + if (contents.AsColors != null) + { + return contents.AsColors; + } + + if (Type != GLTFAccessorAttributeType.VEC3 && Type != GLTFAccessorAttributeType.VEC4) + { + return null; + } + + if (ComponentType == GLTFComponentType.UnsignedInt) + { + return null; + } + var arr = new Color[Count]; var totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * (Type == GLTFAccessorAttributeType.VEC3 ? 3 : 4); + uint stride = (uint)(BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * (Type == GLTFAccessorAttributeType.VEC3 ? 3 : 4)); - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { if (ComponentType == GLTFComponentType.Float) { @@ -582,169 +590,110 @@ public Color[] AsColorArray(ref NumericArray contents, byte[] bufferViewData, in return arr; } - public Vector2[] AsTexcoordArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public Vector2[] AsTexcoordArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsTexcoords != null) - { - return contents.AsTexcoords; - } + if (contents.AsTexcoords != null) + { + return contents.AsTexcoords; + } contents.AsTexcoords = AsVector2Array(ref contents, bufferViewData, offset); return contents.AsTexcoords; } - public Vector3[] AsVertexArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public Vector3[] AsVertexArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsVertices != null) - { - return contents.AsVertices; - } + if (contents.AsVertices != null) + { + return contents.AsVertices; + } contents.AsVertices = AsVector3Array(ref contents, bufferViewData, offset); return contents.AsVertices; } - public Vector3[] AsNormalArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public Vector3[] AsNormalArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsNormals != null) - { - return contents.AsNormals; - } + if (contents.AsNormals != null) + { + return contents.AsNormals; + } contents.AsNormals = AsVector3Array(ref contents, bufferViewData, offset); return contents.AsNormals; } - public Vector4[] AsTangentArray(ref NumericArray contents, byte[] bufferViewData, int offset) + public Vector4[] AsTangentArray(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsTangents != null) - { - return contents.AsTangents; - } + if (contents.AsTangents != null) + { + return contents.AsTangents; + } contents.AsTangents = AsVector4Array(ref contents, bufferViewData, offset); return contents.AsTangents; } - public uint[] AsTriangles(ref NumericArray contents, byte[] bufferViewData, int offset) + public uint[] AsTriangles(ref NumericArray contents, byte[] bufferViewData, uint offset) { - if (contents.AsTriangles != null) - { - return contents.AsTriangles; - } + if (contents.AsTriangles != null) + { + return contents.AsTriangles; + } contents.AsTriangles = AsUIntArray(ref contents, bufferViewData, offset); return contents.AsTriangles; } - private static int GetDiscreteElement(byte[] bufferViewData, int offset, GLTFComponentType type) + public Matrix4x4[] AsMatrix4x4Array(ref NumericArray contents, byte[] bufferViewData, uint offset, bool normalizeIntValues = true) { - switch(type) + if (contents.AsMatrix4x4s != null) { - case GLTFComponentType.Byte: - { - return GetByteElement(bufferViewData, offset); - } - case GLTFComponentType.UnsignedByte: - { - return GetUByteElement(bufferViewData, offset); - } - case GLTFComponentType.Short: - { - return GetShortElement(bufferViewData, offset); - } - case GLTFComponentType.UnsignedShort: - { - return GetUShortElement(bufferViewData, offset); - } - default: - { - throw new Exception("Unsupported type passed in: " + type); - } + return contents.AsMatrix4x4s; } - } - // technically byte and short are not spec compliant for unsigned types, but various files have it - private static uint GetUnsignedDiscreteElement(byte[] bufferViewData, int offset, GLTFComponentType type) - { - switch(type) + if (Type != GLTFAccessorAttributeType.MAT4) { - case GLTFComponentType.Byte: - { - return (uint)GetByteElement(bufferViewData, offset); - } - case GLTFComponentType.UnsignedByte: - { - return GetUByteElement(bufferViewData, offset); - } - case GLTFComponentType.Short: - { - return (uint)GetShortElement(bufferViewData, offset); - } - case GLTFComponentType.UnsignedShort: - { - return GetUShortElement(bufferViewData, offset); - } - case GLTFComponentType.UnsignedInt: - { - return GetUIntElement(bufferViewData, offset); - } - default: - { - throw new Exception("Unsupported type passed in: " + type); - } + return null; } - } - - public Matrix4x4[] AsMatrix4x4Array(ref NumericArray contents, byte[] bufferViewData, int offset, bool normalizeIntValues = true) - { - if (contents.AsMatrix4x4s != null) - { - return contents.AsMatrix4x4s; - } - - if (Type != GLTFAccessorAttributeType.MAT4) - { - return null; - } Matrix4x4[] arr = new Matrix4x4[Count]; - var totalByteOffset = ByteOffset + offset; + uint totalByteOffset = ByteOffset + offset; - int componentSize; + uint componentSize; float maxValue; GetTypeDetails(ComponentType, out componentSize, out maxValue); - if (normalizeIntValues) - { - maxValue = 1; - } + if (normalizeIntValues) + { + maxValue = 1; + } - var stride = BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 16; + uint stride = (uint)(BufferView.Value.ByteStride > 0 ? BufferView.Value.ByteStride : componentSize * 16); - for (var idx = 0; idx < Count; idx++) + for (uint idx = 0; idx < Count; idx++) { arr[idx] = new Matrix4x4(Matrix4x4.Identity); if (ComponentType == GLTFComponentType.Float) { - for (int i = 0; i < 16; i++) + for (uint i = 0; i < 16; i++) { float value = GetFloatElement(bufferViewData, totalByteOffset + idx * stride + componentSize * i); - arr[idx].SetValue(i, value); + arr[idx].SetValue((int)i, value); } } else { - for (int i = 0; i < 16; i++) + for (uint i = 0; i < 16; i++) { float value = GetDiscreteElement(bufferViewData, totalByteOffset + idx * stride + componentSize * i, ComponentType) / maxValue; - arr[idx].SetValue(i, value); + arr[idx].SetValue((int)i, value); } } } @@ -752,6 +701,66 @@ public Matrix4x4[] AsMatrix4x4Array(ref NumericArray contents, byte[] bufferView contents.AsMatrix4x4s = arr; return arr; } + + private static int GetDiscreteElement(byte[] bufferViewData, uint offset, GLTFComponentType type) + { + switch (type) + { + case GLTFComponentType.Byte: + { + return GetByteElement(bufferViewData, offset); + } + case GLTFComponentType.UnsignedByte: + { + return GetUByteElement(bufferViewData, offset); + } + case GLTFComponentType.Short: + { + return GetShortElement(bufferViewData, offset); + } + case GLTFComponentType.UnsignedShort: + { + return GetUShortElement(bufferViewData, offset); + } + default: + { + throw new Exception("Unsupported type passed in: " + type); + } + } + } + + // technically byte and short are not spec compliant for unsigned types, but various files have it + private static uint GetUnsignedDiscreteElement(byte[] bufferViewData, uint offset, GLTFComponentType type) + { + switch (type) + { + case GLTFComponentType.Byte: + { + return (uint)GetByteElement(bufferViewData, offset); + } + case GLTFComponentType.UnsignedByte: + { + return GetUByteElement(bufferViewData, offset); + } + case GLTFComponentType.Short: + { + return (uint)GetShortElement(bufferViewData, offset); + } + case GLTFComponentType.UnsignedShort: + { + return GetUShortElement(bufferViewData, offset); + } + case GLTFComponentType.UnsignedInt: + { + return GetUIntElement(bufferViewData, offset); + } + default: + { + throw new Exception("Unsupported type passed in: " + type); + } + + } + } } public enum GLTFComponentType @@ -776,8 +785,6 @@ public enum GLTFAccessorAttributeType } /// - /// This struct is a union, and should be used as such - /// [StructLayout(LayoutKind.Explicit)] public struct NumericArray { diff --git a/GLTFSerialization/GLTFSerialization/Schema/BufferView.cs b/GLTFSerialization/GLTFSerialization/Schema/BufferView.cs index e36885e82..ce89a9c7d 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/BufferView.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/BufferView.cs @@ -1,3 +1,4 @@ +using GLTF.Utilities; using Newtonsoft.Json; namespace GLTF.Schema @@ -23,13 +24,13 @@ public class BufferView : GLTFChildOfRootProperty /// The offset into the buffer in bytes. /// 0 /// - public int ByteOffset; + public uint ByteOffset; /// /// The length of the bufferView in bytes. /// 0 /// - public int ByteLength; + public uint ByteLength; /// /// The stride, in bytes, between vertex attributes or other interleavable data. @@ -37,7 +38,7 @@ public class BufferView : GLTFChildOfRootProperty /// 0 /// 255 /// - public int ByteStride; + public uint ByteStride; /// /// The target that the WebGL buffer should be bound to. @@ -73,13 +74,13 @@ public static BufferView Deserialize(GLTFRoot root, JsonReader reader) bufferView.Buffer = BufferId.Deserialize(root, reader); break; case "byteOffset": - bufferView.ByteOffset = reader.ReadAsInt32().Value; + bufferView.ByteOffset = reader.ReadDoubleAsUInt32(); break; case "byteLength": - bufferView.ByteLength = reader.ReadAsInt32().Value; + bufferView.ByteLength = reader.ReadDoubleAsUInt32(); break; case "byteStride": - bufferView.ByteStride = reader.ReadAsInt32().Value; + bufferView.ByteStride = reader.ReadDoubleAsUInt32(); break; case "target": bufferView.Target = (BufferViewTarget)reader.ReadAsInt32().Value; diff --git a/GLTFSerialization/GLTFSerialization/Schema/GLTFBuffer.cs b/GLTFSerialization/GLTFSerialization/Schema/GLTFBuffer.cs index 4c0cb5ffe..01af2a037 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/GLTFBuffer.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/GLTFBuffer.cs @@ -1,3 +1,4 @@ +using GLTF.Utilities; using Newtonsoft.Json; namespace GLTF.Schema @@ -18,7 +19,7 @@ public class GLTFBuffer : GLTFChildOfRootProperty /// The length of the buffer in bytes. /// 0 /// - public int ByteLength; + public uint ByteLength; public GLTFBuffer() { @@ -45,7 +46,7 @@ public static GLTFBuffer Deserialize(GLTFRoot root, JsonReader reader) buffer.Uri = reader.ReadAsString(); break; case "byteLength": - buffer.ByteLength = reader.ReadAsInt32().Value; + buffer.ByteLength = reader.ReadDoubleAsUInt32(); break; default: buffer.DefaultPropertyDeserializer(root, reader); diff --git a/GLTFSerialization/GLTFSerialization/Schema/GLTFImage.cs b/GLTFSerialization/GLTFSerialization/Schema/GLTFImage.cs index 3f69cc234..775021ef5 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/GLTFImage.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/GLTFImage.cs @@ -91,7 +91,7 @@ public override void Serialize(JsonWriter writer) if (BufferView != null) { writer.WritePropertyName("bufferView"); - writer.WriteValue(BufferView); + writer.WriteValue(BufferView.Id); } base.Serialize(writer); diff --git a/GLTFSerialization/GLTFSerialization/Schema/GLTFProperty.cs b/GLTFSerialization/GLTFSerialization/Schema/GLTFProperty.cs index 46fcf1f57..00a833897 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/GLTFProperty.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/GLTFProperty.cs @@ -32,7 +32,7 @@ public GLTFProperty(GLTFProperty property, GLTFRoot gltfRoot = null) if (property.Extensions != null) { Extensions = new Dictionary(property.Extensions.Count); - foreach (KeyValuePair extensionKeyValuePair in Extensions) + foreach (KeyValuePair extensionKeyValuePair in property.Extensions) { Extensions.Add(extensionKeyValuePair.Key, extensionKeyValuePair.Value.Clone(gltfRoot)); } diff --git a/GLTFSerialization/GLTFSerialization/Schema/GLTFRoot.cs b/GLTFSerialization/GLTFSerialization/Schema/GLTFRoot.cs index 0295f5ade..53028add2 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/GLTFRoot.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/GLTFRoot.cs @@ -242,6 +242,11 @@ public GLTFRoot(GLTFRoot gltfRoot) : base(gltfRoot) } } + /// + /// Whether this object is a GLB + /// + public bool IsGLB; + /// /// Return the default scene. When scene is null, scene of index 0 will be returned. /// When scenes list is null or empty, returns null. @@ -337,10 +342,18 @@ public static GLTFRoot Deserialize(TextReader textReader) return root; } - public void Serialize(TextWriter textWriter) + public void Serialize(TextWriter textWriter, bool isGLB = false) { JsonWriter jsonWriter = new JsonTextWriter(textWriter); - jsonWriter.Formatting = Formatting.Indented; + if (isGLB) + { + jsonWriter.Formatting = Formatting.None; + } + else + { + jsonWriter.Formatting = Formatting.Indented; + } + jsonWriter.WriteStartObject(); if (ExtensionsUsed != null && ExtensionsUsed.Count > 0) diff --git a/GLTFSerialization/GLTFSerialization/Schema/MeshPrimitive.cs b/GLTFSerialization/GLTFSerialization/Schema/MeshPrimitive.cs index d08be4a88..cc9745e66 100644 --- a/GLTFSerialization/GLTFSerialization/Schema/MeshPrimitive.cs +++ b/GLTFSerialization/GLTFSerialization/Schema/MeshPrimitive.cs @@ -208,7 +208,8 @@ public static MeshPrimitive Deserialize(GLTFRoot root, JsonReader reader) { Id = reader.ReadAsInt32().Value, Root = root - }); + }, + skipStartObjectRead: true); }); break; default: diff --git a/GLTFSerialization/GLTFSerialization/Utilities/JsonReaderExtensions.cs b/GLTFSerialization/GLTFSerialization/Utilities/JsonReaderExtensions.cs new file mode 100644 index 000000000..c08acefc9 --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/Utilities/JsonReaderExtensions.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace GLTF.Utilities +{ + internal static class JsonReaderExtensions + { + public static uint ReadDoubleAsUInt32(this JsonReader reader) + { + return (uint)System.Math.Round(reader.ReadAsDouble().Value); + } + } +} diff --git a/GLTFSerialization/GLTFSerialization/Utilities/StreamExtensions.cs b/GLTFSerialization/GLTFSerialization/Utilities/StreamExtensions.cs new file mode 100644 index 000000000..1a661ec24 --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/Utilities/StreamExtensions.cs @@ -0,0 +1,65 @@ +namespace System.IO +{ + /// + /// Adding .NET CopyTo as extension method for .NET 3.5 support + /// + internal static class StreamExtensions + { + // We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K). + // The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant + // improvement in Copy performance. + private const int _DefaultCopyBufferSize = 81920; + private static readonly byte[] _CopyBuffer = new byte[_DefaultCopyBufferSize]; + +#if !NETFX_CORE + public static void CopyTo(this Stream source, Stream destination) + { + int highwaterMark = 0; + try + { + int read; + while ((read = source.Read(_CopyBuffer, 0, _CopyBuffer.Length)) != 0) + { + if (read > highwaterMark) highwaterMark = read; + destination.Write(_CopyBuffer, 0, read); + } + } + finally + { + Array.Clear(_CopyBuffer, 0, highwaterMark); // clear only the most we used + } + } +#endif + + /// + /// Implementation to copy a fixed amount of data to self in stream + /// + /// The stream to modify + /// Offset into stream to copy + /// Amount of stream to copy + /// Size of array to use for each copy + public static void CopyToSelf(this Stream source, int destinationOffset, uint amountToCopy) + { + if (destinationOffset <= source.Position) throw new NotImplementedException("desintation offset must be larger than source offset"); + + int highwaterMark = 0; + long initialOffset = source.Position; + try + { + int read; + int amountToRead; + while ((source.Position = initialOffset + amountToCopy - (amountToRead = (int)Math.Min(_CopyBuffer.Length, amountToCopy))) >= 0 && (read = source.Read(_CopyBuffer, 0, amountToRead)) != 0) + { + if (read > highwaterMark) highwaterMark = read; + source.Position = destinationOffset + amountToCopy - read; + source.Write(_CopyBuffer, 0, read); + amountToCopy -= (uint)read; + } + } + finally + { + Array.Clear(_CopyBuffer, 0, highwaterMark); // clear only the most we used + } + } + } +} diff --git a/GLTFSerialization/GLTFSerialization/Utilities/SubStream.cs b/GLTFSerialization/GLTFSerialization/Utilities/SubStream.cs new file mode 100644 index 000000000..b9d45247e --- /dev/null +++ b/GLTFSerialization/GLTFSerialization/Utilities/SubStream.cs @@ -0,0 +1,131 @@ +/// +/// Implementation from: https://social.msdn.microsoft.com/Forums/vstudio/en-US/c409b63b-37df-40ca-9322-458ffe06ea48/how-to-access-part-of-a-filestream-or-memorystream?forum=netfxbcl +/// + +using System; +using System.IO; + +namespace GLTF.Utilities +{ + /// + /// Allows only part of a stream to be accessed + /// Prevents reading of the stream past the desired length + /// SubStream start is based on input position + /// + public class SubStream : Stream + { + private Stream _baseStream; + private readonly long _length; + private long _position; + + public override bool CanRead + { + get + { + CheckDisposed(); + return _baseStream.CanRead; + } + } + + public override bool CanSeek + { + get + { + CheckDisposed(); + return false; + } + } + + public override bool CanWrite + { + get + { + CheckDisposed(); + return false; + } + } + + public override long Length + { + get + { + CheckDisposed(); + return _length; + } + } + + public override long Position + { + get + { + CheckDisposed(); + return _position; + } + set + { + throw new NotImplementedException(); + } + } + + public SubStream(Stream baseStream, long offset, long length) + { + if (baseStream == null) throw new ArgumentNullException(nameof(baseStream)); + if (!baseStream.CanRead) throw new ArgumentException("cannot read base stream"); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if (!baseStream.CanSeek) throw new ArgumentException("cannot seek on base stream"); + + _length = length; + _baseStream = baseStream; + _baseStream.Seek(offset, SeekOrigin.Current); + } + + public override void Flush() + { + CheckDisposed(); + _baseStream.Flush(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + CheckDisposed(); + return _baseStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + CheckDisposed(); + long remaining = _length - _position; + if (remaining <= 0) + { + return 0; + } + + if (remaining < count) + { + count = (int)remaining; + } + + int read = _baseStream.Read(buffer, offset, count); + _position += read; + return read; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + private void CheckDisposed() + { + if (_baseStream == null) + { + throw new ObjectDisposedException(GetType().Name); + } + } + } +} diff --git a/GLTFSerialization/GLTFSerializationUWP/GLTFSerializationUWP.csproj b/GLTFSerialization/GLTFSerializationUWP/GLTFSerializationUWP.csproj index a89b54def..ad9362650 100644 --- a/GLTFSerialization/GLTFSerializationUWP/GLTFSerializationUWP.csproj +++ b/GLTFSerialization/GLTFSerializationUWP/GLTFSerializationUWP.csproj @@ -66,12 +66,24 @@ Extensions\KHR_materials_pbrSpecularGlossinessExtensionFactory.cs + + GLBBuilder.cs + + + GLBObject.cs + GLTFHelpers.cs + + GLTFObject.cs + GLTFParser.cs + + IGLTFObject.cs + Math\Matrix4x4.cs @@ -128,7 +140,7 @@ Schema\CameraPerspective.cs - + Schema\GLTFChildOfRootProperty.cs @@ -186,6 +198,15 @@ Schema\TextureInfo.cs + + Utilities\JsonReaderExtensions.cs + + + Utilities\StreamExtensions.cs + + + Utilities\SubStream.cs + Extensions\GLTFJsonExtensions.cs @@ -200,30 +221,6 @@ - - true - bin\x86\Debug\ - DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP - true - true - full - x86 - false - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE;NETFX_CORE;WINDOWS_UWP - true - true - true - pdbonly - x86 - false - prompt - MinimumRecommendedRules.ruleset - - \ No newline at end of file + diff --git a/GLTFSerialization/Tests/GLTFSerializationTests/GLBBuilderTest.cs b/GLTFSerialization/Tests/GLTFSerializationTests/GLBBuilderTest.cs new file mode 100644 index 000000000..77c0cf431 --- /dev/null +++ b/GLTFSerialization/Tests/GLTFSerializationTests/GLBBuilderTest.cs @@ -0,0 +1,343 @@ +using System.Linq; +using System; +using System.Collections.Generic; +using GLTF; +using GLTF.Schema; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace GLTFSerializationTests +{ + [TestClass] + public class GLBBuilderTest + { + [TestMethod] + public void CreateAndSaveFromEmptyStream() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 10, TestAssetPaths.GLB_EXTENSION); + + FileStream glbStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream); + Assert.IsNull(glbObject.Root); + + MemoryStream stream = new MemoryStream(); + StreamWriter writer = new StreamWriter(stream); + writer.Write(TestAssetPaths.MIN_GLTF_STR); + writer.Flush(); + stream.Position = 0; + + GLTFRoot gltfRoot; + GLTFParser.ParseJson(stream, out gltfRoot); + GLBBuilder.SetRoot(glbObject, gltfRoot); + + GLBBuilder.UpdateStream(glbObject); + + glbStream.Close(); + glbStream = new FileStream(outPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + + glbObject = GLBBuilder.ConstructFromStream(glbStream); + Assert.IsNotNull(glbObject.Root); + glbStream.Close(); + } + + [TestMethod] + public void CreateEmptyStreamAndAppendGLB() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 11, TestAssetPaths.GLB_EXTENSION); + + FileStream glbStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream); + Assert.IsNull(glbObject.Root); + + MemoryStream stream = new MemoryStream(); + StreamWriter writer = new StreamWriter(stream); + writer.Write(TestAssetPaths.MIN_GLTF_STR); + writer.Flush(); + stream.Position = 0; + + GLTFRoot gltfRoot; + GLTFParser.ParseJson(stream, out gltfRoot); + GLBBuilder.SetRoot(glbObject, gltfRoot); + GLBBuilder.UpdateStream(glbObject); + + FileStream glbAppendStream = File.OpenRead(TestAssetPaths.GLB_BOOMBOX_PATH); + GLBObject glbAppendObject = GLBBuilder.ConstructFromStream(glbAppendStream); + GLBBuilder.AddBinaryData(glbObject, glbAppendStream, false, glbAppendObject.BinaryChunkInfo.StartPosition + GLTFParser.CHUNK_HEADER_SIZE); + + glbStream.Close(); + glbStream = new FileStream(outPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + + glbObject = GLBBuilder.ConstructFromStream(glbStream); + Assert.IsNotNull(glbObject.Root); + Assert.AreEqual(glbAppendObject.BinaryChunkInfo.Length, glbObject.BinaryChunkInfo.Length); + glbStream.Close(); + } + + [TestMethod] + public void CreateGLBFromStream() + { + Assert.IsTrue(File.Exists(TestAssetPaths.GLB_BOOMBOX_PATH)); + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOOMBOX_PATH); + FileStream glbOutStream = File.Create(TestAssetPaths.GLB_BOOMBOX_OUT_PATH); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + + Assert.IsNotNull(glbObject.Root); + Assert.IsNotNull(glbObject.Stream); + Assert.AreEqual(0, glbObject.StreamStartPosition); + Assert.AreEqual(GLTFParser.HEADER_SIZE, glbObject.JsonChunkInfo.StartPosition); + Assert.AreEqual(glbStream.Length, glbObject.Header.FileLength); + + glbOutStream.Position = 0; + GLTFRoot glbOutRoot; + GLTFParser.ParseJson(glbOutStream, out glbOutRoot); + GLTFJsonLoadTestHelper.TestGLB(glbOutRoot); + } + + [TestMethod] + public void UpdateStream() + { + Assert.IsTrue(File.Exists(TestAssetPaths.GLB_BOOMBOX_PATH)); + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOOMBOX_PATH); + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 0, TestAssetPaths.GLB_EXTENSION); + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + + for (int i = 0; i < 10; ++i) + { + glbObject.Root.Nodes.Add(new Node + { + Mesh = new MeshId + { + Id = 0, + Root = glbObject.Root + } + }); + } + + GLBBuilder.UpdateStream(glbObject); + glbOutStream.Position = 0; + + GLTFRoot glbOutRoot; + GLTFParser.ParseJson(glbOutStream, out glbOutRoot); + FileStream glbFileStream = glbObject.Stream as FileStream; + Assert.AreEqual(glbFileStream, glbFileStream); + glbOutStream.Position = 0; + List chunkInfo = GLTFParser.FindChunks(glbOutStream); + Assert.AreEqual(2, chunkInfo.Count); + CompareBinaryData(glbObject, glbStream); + } + + [TestMethod] + public void AddBinaryDataToStream() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 1, TestAssetPaths.GLB_EXTENSION); + + Assert.IsTrue(File.Exists(TestAssetPaths.GLB_BOX_PATH)); + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOX_PATH); + + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + + const int bufferSize = 101; + byte[] buffer = new byte[bufferSize]; + AddBinaryDataToStreamHelper(glbObject, new MemoryStream(buffer)); + } + + [TestMethod] + public void AddFirstBinaryDataToStream() + { + MemoryStream stream = new MemoryStream(); + StreamWriter writer = new StreamWriter(stream); + writer.Write(TestAssetPaths.MIN_GLTF_STR); + writer.Flush(); + stream.Position = 0; + + MemoryStream writeStream = new MemoryStream(); + GLBObject glbObject = GLBBuilder.ConstructFromStream(stream, writeStream); + Assert.IsNull(glbObject.Root.Buffers); + + const uint bufferSize = 100; + byte[] buffer = new byte[bufferSize]; + AddBinaryDataToStreamHelper(glbObject, new MemoryStream(buffer)); + } + + [TestMethod] + public void RemoveBinaryDataFromStream() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 2, TestAssetPaths.GLB_EXTENSION); + + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOX_PATH); + + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + + const uint bufferSize = 100; + byte[] buffer = new byte[bufferSize]; + BufferViewId bufferViewId = AddBinaryDataToStreamHelper(glbObject, new MemoryStream(buffer)); + uint length = (uint)bufferViewId.Value.ByteLength; + uint previousFileLength = glbObject.Header.FileLength; + uint previousBufferLength = glbObject.BinaryChunkInfo.Length; + int previousBufferViewCount = glbObject.Root.BufferViews.Count; + GLBBuilder.RemoveBinaryData(glbObject, bufferViewId); + Assert.AreEqual(previousFileLength - length, glbObject.Header.FileLength); + Assert.AreEqual(previousBufferLength - length, glbObject.BinaryChunkInfo.Length); + Assert.AreEqual(previousBufferLength - length, (uint)glbObject.Root.Buffers[0].ByteLength); + Assert.AreEqual(previousBufferViewCount - 1, glbObject.Root.BufferViews.Count); + } + + [TestMethod] + public void RemoveMiddleDataFromStream() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 3, TestAssetPaths.GLB_EXTENSION); + + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOX_PATH); + + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + + const uint numBuffersToAdd = 5; + const uint bufferSize = 100; + BufferViewId[] bufferViews = new BufferViewId[numBuffersToAdd]; + for (int i = 0; i < numBuffersToAdd; ++i) + { + byte[] buffer = new byte[bufferSize]; + bufferViews[i] = AddBinaryDataToStreamHelper(glbObject, new MemoryStream(buffer)); + } + + uint previousFileLength = glbObject.Header.FileLength; + uint previousBufferLength = glbObject.BinaryChunkInfo.Length; + int previousBufferViewCount = glbObject.Root.BufferViews.Count; + GLBBuilder.RemoveBinaryData(glbObject, bufferViews[2]); // remove from the middle + Assert.AreEqual(previousFileLength, glbObject.Header.FileLength); + Assert.AreEqual(previousBufferLength, glbObject.BinaryChunkInfo.Length); + Assert.AreEqual(previousBufferViewCount - 1, glbObject.Root.BufferViews.Count); + } + + [TestMethod] + public void RemoveAllDataFromStream() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 3, TestAssetPaths.GLB_EXTENSION); + + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOX_PATH); + + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + BufferViewId id0 = new BufferViewId + { + Id = 0, + Root = glbObject.Root + }; + + int numBufferViews = glbObject.Root.BufferViews.Count; + for (int i = 0; i < numBufferViews; ++i) + { + GLBBuilder.RemoveBinaryData(glbObject, id0); + } + + Assert.AreEqual(0, glbObject.Root.Buffers.Count); + Assert.AreEqual(0, glbObject.Root.BufferViews.Count); + } + + [TestMethod] + public void MergeGLBs() + { + Assert.IsTrue(File.Exists(TestAssetPaths.GLB_BOX_PATH)); + FileStream glbStream = File.OpenRead(TestAssetPaths.GLB_BOX_PATH); + FileStream glbStream1 = File.OpenRead(TestAssetPaths.GLB_BOOMBOX_PATH); + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 4, TestAssetPaths.GLB_EXTENSION); + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbStream, glbOutStream); + GLBObject glbObject1 = GLBBuilder.ConstructFromStream(glbStream1); + uint initialGLBLength = glbObject.BinaryChunkInfo.Length; + GLBBuilder.MergeGLBs(glbObject, glbObject1); + + Assert.AreEqual(initialGLBLength + glbObject1.BinaryChunkInfo.Length, glbObject.BinaryChunkInfo.Length); + } + + [TestMethod] + public void GLBSaveWithoutBinary() + { + string outPath = + TestAssetPaths.GetOutPath(TestAssetPaths.GLB_BOX_OUT_PATH_TEMPLATE, 15, TestAssetPaths.GLB_EXTENSION); + FileStream glbOutStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite); + + // first create from empty stream + GLBObject glbObject = GLBBuilder.ConstructFromStream(glbOutStream); + uint initialGLBLength = glbObject.BinaryChunkInfo.Length; + + MemoryStream stream = new MemoryStream(); + StreamWriter writer = new StreamWriter(stream); + writer.Write(TestAssetPaths.MIN_GLTF_STR); + writer.Flush(); + stream.Position = 0; + + GLTFRoot newRoot; + GLTFParser.ParseJson(stream, out newRoot); + GLBBuilder.SetRoot(glbObject, newRoot); + GLBBuilder.UpdateStream(glbObject); + Assert.AreEqual(glbObject.Header.FileLength, glbObject.JsonChunkInfo.StartPosition + glbObject.JsonChunkInfo.Length + GLTFParser.CHUNK_HEADER_SIZE); + glbOutStream.Close(); + glbOutStream = new FileStream(outPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + List chunks = GLTFParser.FindChunks(glbOutStream); + Assert.AreEqual(1, chunks.Count); + + glbOutStream.Position = 0; + GLBObject testObject = GLBBuilder.ConstructFromStream(glbOutStream); + + Assert.AreEqual(glbObject.JsonChunkInfo.Length, testObject.JsonChunkInfo.Length); + Assert.AreEqual(glbObject.BinaryChunkInfo.Length, testObject.BinaryChunkInfo.Length); + } + + private BufferViewId AddBinaryDataToStreamHelper(GLBObject glbObject, Stream blobToAdd) + { + int previousCount = 0; + if (glbObject.Root.BufferViews != null) + { + previousCount = glbObject.Root.BufferViews.Count; + } + + uint previousGLBLength = glbObject.Header.FileLength; + uint previousChunkLength = glbObject.BinaryChunkInfo.Length; + + uint bufferSize = GLBBuilder.CalculateAlignment((uint)blobToAdd.Length, 4); + BufferViewId bufferViewId = GLBBuilder.AddBinaryData(glbObject, blobToAdd); + + uint headerModifier = previousChunkLength == 0 ? GLTFParser.CHUNK_HEADER_SIZE : 0; + Assert.AreEqual(previousCount + 1, glbObject.Root.BufferViews.Count); + Assert.AreEqual(previousCount, bufferViewId.Id); + Assert.AreEqual(previousGLBLength + bufferSize + headerModifier, glbObject.Header.FileLength); + Assert.AreEqual(previousChunkLength + bufferSize, glbObject.BinaryChunkInfo.Length); + Assert.AreEqual(previousChunkLength + bufferSize, glbObject.Root.Buffers[0].ByteLength); + Assert.AreEqual(glbObject.Header.FileLength, glbObject.Stream.Length); + + return bufferViewId; + } + + private void CompareBinaryData(GLBObject resultObject, FileStream sourceStream) + { + MemoryStream outStream = new MemoryStream(); + GLBObject sourceGLB = GLBBuilder.ConstructFromStream(sourceStream, outStream); + byte[] resultObjectBinary = new byte[resultObject.BinaryChunkInfo.Length]; + resultObject.Stream.Position = resultObject.BinaryChunkInfo.StartPosition; + resultObject.Stream.Read(resultObjectBinary, 0, resultObjectBinary.Length); + + byte[] sourceObjectBinary = new byte[sourceGLB.BinaryChunkInfo.Length]; + sourceGLB.Stream.Position = sourceGLB.BinaryChunkInfo.StartPosition; + sourceGLB.Stream.Read(sourceObjectBinary, 0, sourceObjectBinary.Length); + + Assert.IsTrue(resultObjectBinary.SequenceEqual(sourceObjectBinary)); + } + } +} diff --git a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoadTestHelper.cs b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoadTestHelper.cs index 1f8ea04c0..01e7e5919 100644 --- a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoadTestHelper.cs +++ b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoadTestHelper.cs @@ -49,7 +49,7 @@ public override IExtension Deserialize(GLTFRoot root, JProperty extensionToken) class GLTFJsonLoadTestHelper { - private static void TestAccessor(Accessor accessor, GLTFAccessorAttributeType type, int count, GLTFComponentType componentType, int bufferViewId, List max, List min) + private static void TestAccessor(Accessor accessor, GLTFAccessorAttributeType type, uint count, GLTFComponentType componentType, int bufferViewId, List max, List min) { Assert.AreEqual(type, accessor.Type); Assert.AreEqual(count, accessor.Count); @@ -97,7 +97,7 @@ private static void TestAssetData(GLTFRoot gltfRoot) Assert.AreEqual("glTF Tools for Unity", gltfRoot.Asset.Generator); } - private static void TestBufferView(BufferView bufferView, int buffer, int byteOffset, int byteLenth) + private static void TestBufferView(BufferView bufferView, int buffer, uint byteOffset, uint byteLenth) { Assert.AreEqual(buffer, bufferView.Buffer.Id); Assert.AreEqual(byteOffset, bufferView.ByteOffset); @@ -135,14 +135,14 @@ private static void TestBuffers(GLTFRoot gltfRoot) List buffers = gltfRoot.Buffers; Assert.AreEqual(1, buffers.Count); Assert.AreEqual("BoomBox.bin", buffers[0].Uri); - Assert.AreEqual(207816, buffers[0].ByteLength); + Assert.AreEqual((uint)207816, buffers[0].ByteLength); } private static void TestGLBBuffers(GLTFRoot gltfRoot) { List buffers = gltfRoot.Buffers; Assert.AreEqual(1, buffers.Count); - Assert.AreEqual(11247948, buffers[0].ByteLength); + Assert.AreEqual((uint)11247948, buffers[0].ByteLength); } private static void TestImages(GLTFRoot gltfRoot) diff --git a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoaderTest.cs b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoaderTest.cs index 07fac5b22..4a9dc4fb7 100644 --- a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoaderTest.cs +++ b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFLoaderTest.cs @@ -1,10 +1,8 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Threading.Tasks; -using System.IO; -using GLTF; -using GLTF.Schema; +using GLTF; using GLTF.Math; -using System.Collections.Generic; +using GLTF.Schema; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.IO; namespace GLTFSerializationTests { @@ -20,8 +18,8 @@ public class GLTFJsonLoaderTest [TestMethod] public void LoadGLTFFromStream() { - Assert.IsTrue(File.Exists(GLTF_PATH)); - FileStream gltfStream = File.OpenRead(GLTF_PATH); + Assert.IsTrue(File.Exists(TestAssetPaths.GLTF_PATH)); + FileStream gltfStream = File.OpenRead(TestAssetPaths.GLTF_PATH); GLTFRoot.RegisterExtension(new TestExtensionFactory()); GLTFRoot gltfRoot = null; diff --git a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFSerializationTests.csproj b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFSerializationTests.csproj index 232b4a0db..3a3ac250b 100644 --- a/GLTFSerialization/Tests/GLTFSerializationTests/GLTFSerializationTests.csproj +++ b/GLTFSerialization/Tests/GLTFSerializationTests/GLTFSerializationTests.csproj @@ -9,7 +9,7 @@ Properties GLTFSerializationTests GLTFSerializationTests - v4.5.2 + v4.6 512 {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15.0 @@ -19,6 +19,7 @@ UnitTest + true @@ -69,11 +70,13 @@ + + diff --git a/GLTFSerialization/Tests/GLTFSerializationTests/TestAssetPaths.cs b/GLTFSerialization/Tests/GLTFSerializationTests/TestAssetPaths.cs new file mode 100644 index 000000000..cbb1afa5c --- /dev/null +++ b/GLTFSerialization/Tests/GLTFSerializationTests/TestAssetPaths.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace GLTFSerializationTests +{ + public static class TestAssetPaths + { + public static readonly string GLTF_PATH = Directory.GetCurrentDirectory() + "/../../../../External/glTF/BoomBox.gltf"; + public static readonly string GLTF_PBR_SPECGLOSS_PATH = Directory.GetCurrentDirectory() + "/../../../../External/glTF-pbrSpecularGlossiness/Lantern.gltf"; + public static readonly string GLB_BOOMBOX_PATH = Directory.GetCurrentDirectory() + "/../../../../External/glTF-Binary/BoomBox.glb"; + public static readonly string GLB_BOOMBOX_OUT_PATH = Directory.GetCurrentDirectory() + "/../../../../External/glTF-Binary/BoomBox_out.glb"; + public static readonly string GLB_BOX_PATH = Directory.GetCurrentDirectory() + "/../../../../External/glTF-Binary/Box.glb"; + public static readonly string GLB_BOX_OUT_PATH_TEMPLATE = Directory.GetCurrentDirectory() + "/../../../../External/glTF-Binary/BoxOut"; + public static readonly string GLB_EXTENSION = ".glb"; + public static readonly string MIN_GLTF_STR = @" + { + ""asset"": { + ""version"": ""2.0"" + } + } + "; + public static string GetOutPath(string pathTemplate, int num, string extension) + { + return pathTemplate + num + extension; + } + } +} diff --git a/GLTFSerialization/Tests/GLTFSerializationUWPTests/GLTFUWPLoaderTest.cs b/GLTFSerialization/Tests/GLTFSerializationUWPTests/GLTFUWPLoaderTest.cs index aa5d4d145..177b15c7d 100644 --- a/GLTFSerialization/Tests/GLTFSerializationUWPTests/GLTFUWPLoaderTest.cs +++ b/GLTFSerialization/Tests/GLTFSerializationUWPTests/GLTFUWPLoaderTest.cs @@ -24,6 +24,8 @@ public async Task LoadGLTFFromStreamUWP() StorageFile sampleFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(GLTF_PATH)); IRandomAccessStream gltfStream = await sampleFile.OpenAsync(FileAccessMode.Read); + var reader = new DataReader(gltfStream.GetInputStreamAt(0)); + GLTFRoot gltfRoot; GLTFParser.ParseJson(gltfStream.AsStream(), out gltfRoot); GLTFJsonLoadTestHelper.TestGLTF(gltfRoot); diff --git a/UnityGLTF/.gitignore b/UnityGLTF/.gitignore index f735c67c8..334adc544 100644 --- a/UnityGLTF/.gitignore +++ b/UnityGLTF/.gitignore @@ -1,50 +1,52 @@ -/[Ll]ibrary/ -/[Tt]emp/ -/[Oo]bj/ -/[Bb]uild/ -/[Bb]uilds/ -/[Pp]lugins/ -/Assets/AssetStoreTools* -/Assets/ShaderForge* -/Assets/VSCode* -/UnityPackageManager/ - -# Visual Studio 2015 cache directory -/.vs/ -# Visual Studio Code directory -/.vscode/ - -# Autogenerated VS/MD/Consulo solution and project files -ExportedObj/ -.consulo/ -*.csproj -*.unityproj -*.sln -*.suo -*.tmp -*.user -*.userprefs -*.pidb -*.booproj -*.svd +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Pp]lugins/ +/Assets/AssetStoreTools* +/Assets/ShaderForge* +/Assets/VSCode* +/UnityPackageManager/ + +# Visual Studio 2015 cache directory +/.vs/ +# Visual Studio Code directory +/.vscode/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd *.pdb +*.pfx* !*-dll.csproj !*-dll.sln - -# Unity3D generated meta files -*.pidb.meta - -# Unity3D Generated File On Crash Reports -sysinfo.txt - -# Builds -*.apk -*.unitypackage - -/www/node_modules/ - -# UWP-specific folders and files -/[Aa]pp/ -/UWP/ + +# Unity3D generated meta files +*.pidb.meta + +# Unity3D Generated File On Crash Reports +sysinfo.txt + +# Builds +*.apk +*.unitypackage + +/www/node_modules/ + +# UWP-specific folders and files +/[Aa]pp/ +/UWP/ +/WSA/ WSATestCertificate.pfx* diff --git a/UnityGLTF/Assets/UnityGLTF/Examples.meta b/UnityGLTF/Assets/UnityGLTF/Examples.meta index a8c742d27..56b853ffd 100644 --- a/UnityGLTF/Assets/UnityGLTF/Examples.meta +++ b/UnityGLTF/Assets/UnityGLTF/Examples.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 1745e704d7dde164680df8201506d4f2 +guid: efdd854fd74b41040b57067411fa59c3 folderAsset: yes -timeCreated: 1506549705 +timeCreated: 1537993542 licenseType: Pro DefaultImporter: userData: diff --git a/UnityGLTF/Assets/UnityGLTF/Examples/RootMergeComponent.cs b/UnityGLTF/Assets/UnityGLTF/Examples/RootMergeComponent.cs index 802bbf528..0af096c30 100644 --- a/UnityGLTF/Assets/UnityGLTF/Examples/RootMergeComponent.cs +++ b/UnityGLTF/Assets/UnityGLTF/Examples/RootMergeComponent.cs @@ -34,7 +34,7 @@ IEnumerator Start() yield return loader1.LoadStream(Path.GetFileName(asset1Path)); var asset1Stream = loader1.LoadedStream; GLTFRoot asset1Root; - GLTFParser.ParseJson(asset0Stream, out asset1Root); + GLTFParser.ParseJson(asset1Stream, out asset1Root); string newPath = "../../" + URIHelper.GetDirectoryName(asset0Path); diff --git a/UnityGLTF/Assets/UnityGLTF/Plugins/Newtonsoft.Json.xml b/UnityGLTF/Assets/UnityGLTF/Plugins/Newtonsoft.Json.xml index e69de29bb..7067e4477 100755 --- a/UnityGLTF/Assets/UnityGLTF/Plugins/Newtonsoft.Json.xml +++ b/UnityGLTF/Assets/UnityGLTF/Plugins/Newtonsoft.Json.xml @@ -0,0 +1,8922 @@ + + + + Newtonsoft.Json + + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Gets or sets a value indicating whether binary data reading should compatible with incorrect Json.NET 3.5 written binary. + + + true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false. + + + + + Gets or sets a value indicating whether the root object will be read as a JSON array. + + + true if the root object will be read as a JSON array; otherwise, false. + + + + + Gets or sets the used when reading values from BSON. + + The used when reading values from BSON. + + + + Initializes a new instance of the class. + + The stream. + + + + Initializes a new instance of the class. + + The reader. + + + + Initializes a new instance of the class. + + The stream. + if set to true the root object will be read as a JSON array. + The used when reading values from BSON. + + + + Initializes a new instance of the class. + + The reader. + if set to true the root object will be read as a JSON array. + The used when reading values from BSON. + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Changes the to Closed. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets the used when writing values to BSON. + When set to no conversion will occur. + + The used when writing values to BSON. + + + + Initializes a new instance of the class. + + The stream. + + + + Initializes a new instance of the class. + + The writer. + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Writes the end. + + The token. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes raw JSON where a value is expected and updates the writer's state. + + The raw JSON to write. + + + + Writes the beginning of a JSON array. + + + + + Writes the beginning of a JSON object. + + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Closes this stream and the underlying stream. + + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value that represents a BSON object id. + + The Object ID value to write. + + + + Writes a BSON regex. + + The regex pattern. + The regex options. + + + + Represents a BSON Oid (object id). + + + + + Gets or sets the value of the Oid. + + The value of the Oid. + + + + Initializes a new instance of the class. + + The Oid value. + + + + Converts a binary value to and from a base 64 string value. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Create a custom object + + The object type to convert. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Creates an object which will then be populated by the serializer. + + Type of the object. + The created object. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Gets a value indicating whether this can write JSON. + + + true if this can write JSON; otherwise, false. + + + + + Provides a base class for converting a to and from JSON. + + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts an Entity Framework EntityKey to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON and BSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON and BSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts an to and from its name string value. + + + + + Gets or sets a value indicating whether the written enum text should be camel case. + + true if the written enum text will be camel case; otherwise, false. + + + + Gets or sets a value indicating whether integer values are allowed. + + true if integers are allowed; otherwise, false. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + true if the written enum text will be camel case; otherwise, false. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from a string (e.g. "1.2.3.4"). + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing property value of the JSON that is being converted. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from the ISO 8601 date format (e.g. 2008-04-12T12:53Z). + + + + + Gets or sets the date time styles used when converting a date to and from JSON. + + The date time styles used when converting a date to and from JSON. + + + + Gets or sets the date time format used when converting a date to and from JSON. + + The date time format used when converting a date to and from JSON. + + + + Gets or sets the culture used when converting a date to and from JSON. + + The culture used when converting a date to and from JSON. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Converts a to and from a JavaScript date constructor (e.g. new Date(52231943)). + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing property value of the JSON that is being converted. + The calling serializer. + The object value. + + + + Converts XML to and from JSON. + + + + + Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produces multiple root elements. + + The name of the deserialize root element. + + + + Gets or sets a flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + true if the array attibute is written to the XML; otherwise, false. + + + + Gets or sets a value indicating whether to write the root JSON object. + + true if the JSON root object is omitted; otherwise, false. + + + + Writes the JSON representation of the object. + + The to write to. + The calling serializer. + The value. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Checks if the attributeName is a namespace attribute. + + Attribute name to test. + The attribute name prefix if it has one, otherwise an empty string. + true if attribute name is for a namespace attribute, otherwise false. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Specifies how constructors are used when initializing objects during deserialization by the . + + + + + First attempt to use the public default constructor, then fall back to single parameterized constructor, then the non-public default constructor. + + + + + Json.NET will use a non-public default constructor before falling back to a parameterized constructor. + + + + + Specifies how dates are formatted when writing JSON text. + + + + + Dates are written in the ISO 8601 format, e.g. "2012-03-21T05:40Z". + + + + + Dates are written in the Microsoft JSON format, e.g. "\/Date(1198908717056)\/". + + + + + Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. + + + + + Date formatted strings are not parsed to a date type and are read as strings. + + + + + Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + + + + + Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + + + + + Specifies how to treat the time value when converting between string and . + + + + + Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. + + + + + Treat as a UTC. If the object represents a local time, it is converted to a UTC. + + + + + Treat as a local time if a is being converted to a string. + If a string is being converted to , convert to a local time if a time zone is specified. + + + + + Time zone information should be preserved when converting. + + + + + Specifies float format handling options when writing special floating point numbers, e.g. , + and with . + + + + + Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity". + + + + + Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. + Note that this will produce non-valid JSON. + + + + + Write special floating point values as the property's default value in JSON, e.g. 0.0 for a property, null for a property. + + + + + Specifies how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Floating point numbers are parsed to . + + + + + Floating point numbers are parsed to . + + + + + Specifies formatting options for the . + + + + + No special formatting is applied. This is the default. + + + + + Causes child objects to be indented according to the and settings. + + + + + Provides an interface for using pooled arrays. + + The array type content. + + + + Rent a array from the pool. This array must be returned when it is no longer needed. + + The minimum required length of the array. The returned array may be longer. + The rented array from the pool. This array must be returned when it is no longer needed. + + + + Return an array to the pool. + + The array that is being returned. + + + + Instructs the to use the specified constructor when deserializing that object. + + + + + Instructs the how to serialize the collection. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + The exception thrown when an error occurs during JSON serialization or deserialization. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Instructs the to deserialize properties with no matching class member into the specified collection + and write values during serialization. + + + + + Gets or sets a value that indicates whether to write extension data when serializing the object. + + + true to write extension data when serializing the object; otherwise, false. The default is true. + + + + + Gets or sets a value that indicates whether to read extension data when deserializing the object. + + + true to read extension data when deserializing the object; otherwise, false. The default is true. + + + + + Initializes a new instance of the class. + + + + + Instructs the to always serialize the member, and require the member has a value. + + + + + Specifies how JSON comments are handled when loading JSON. + + + + + Ignore comments. + + + + + Load comments as a with type . + + + + + Specifies how line information is handled when loading JSON. + + + + + Ignore line information. + + + + + Load line information. + + + + + Represents a view of a . + + + + + Initializes a new instance of the class. + + The name. + + + + When overridden in a derived class, returns whether resetting an object changes its value. + + + true if resetting the component changes its value; otherwise, false. + + The component to test for reset capability. + + + + When overridden in a derived class, gets the current value of the property on a component. + + + The value of a property for a given component. + + The component with the property for which to retrieve the value. + + + + + When overridden in a derived class, resets the value for this property of the component to the default value. + + The component with the property value that is to be reset to the default value. + + + + + When overridden in a derived class, sets the value of the component to a different value. + + The component with the property value that is to be set. + The new value. + + + + + When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted. + + + true if the property should be persisted; otherwise, false. + + The component with the property to be examined for persistence. + + + + When overridden in a derived class, gets the type of the component this property is bound to. + + + A that represents the type of component this property is bound to. When the or methods are invoked, the object specified might be an instance of this type. + + + + + When overridden in a derived class, gets a value indicating whether this property is read-only. + + + true if the property is read-only; otherwise, false. + + + + + When overridden in a derived class, gets the type of the property. + + + A that represents the type of the property. + + + + + Gets the hash code for the name of the member. + + + + The hash code for the name of the member. + + + + + Specifies the settings used when loading JSON. + + + + + Gets or sets how JSON comments are handled when loading JSON. + + The JSON comment handling. + + + + Gets or sets how JSON line info is handled when loading JSON. + + The JSON line info handling. + + + + Specifies the settings used when merging JSON. + + + + + Gets or sets the method used when merging JSON arrays. + + The method used when merging JSON arrays. + + + + Gets or sets how how null value properties are merged. + + How null value properties are merged. + + + + Specifies how JSON arrays are merged together. + + + + Concatenate arrays. + + + Union arrays, skipping items that already exist. + + + Replace all array items. + + + Merge array items together, matched by index. + + + + Specifies how null value properties are merged. + + + + + The content's null value properties will be ignored during merging. + + + + + The content's null value properties will be merged. + + + + + Represents a raw JSON string. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class. + + The raw json. + + + + Creates an instance of with the content of the reader's current token. + + The reader. + An instance of with the content of the reader's current token. + + + + Represents a collection of objects. + + The type of token + + + + Gets the with the specified key. + + + + + + Compares tokens to determine whether they are equal. + + + + + Determines whether the specified objects are equal. + + The first object of type to compare. + The second object of type to compare. + + true if the specified objects are equal; otherwise, false. + + + + + Returns a hash code for the specified object. + + The for which a hash code is to be returned. + A hash code for the specified object. + The type of is a reference type and is null. + + + + Contains the LINQ to JSON extension methods. + + + + + Returns a collection of tokens that contains the ancestors of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains the ancestors of every token in the source collection. + + + + Returns a collection of tokens that contains every token in the source collection, and the ancestors of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains every token in the source collection, the ancestors of every token in the source collection. + + + + Returns a collection of tokens that contains the descendants of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains the descendants of every token in the source collection. + + + + Returns a collection of tokens that contains every token in the source collection, and the descendants of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains every token in the source collection, and the descendants of every token in the source collection. + + + + Returns a collection of child properties of every object in the source collection. + + An of that contains the source collection. + An of that contains the properties of every object in the source collection. + + + + Returns a collection of child values of every object in the source collection with the given key. + + An of that contains the source collection. + The token key. + An of that contains the values of every token in the source collection with the given key. + + + + Returns a collection of child values of every object in the source collection. + + An of that contains the source collection. + An of that contains the values of every token in the source collection. + + + + Returns a collection of converted child values of every object in the source collection with the given key. + + The type to convert the values to. + An of that contains the source collection. + The token key. + An that contains the converted values of every token in the source collection with the given key. + + + + Returns a collection of converted child values of every object in the source collection. + + The type to convert the values to. + An of that contains the source collection. + An that contains the converted values of every token in the source collection. + + + + Converts the value. + + The type to convert the value to. + A cast as a of . + A converted value. + + + + Converts the value. + + The source collection type. + The type to convert the value to. + A cast as a of . + A converted value. + + + + Returns a collection of child tokens of every array in the source collection. + + The source collection type. + An of that contains the source collection. + An of that contains the values of every token in the source collection. + + + + Returns a collection of converted child tokens of every array in the source collection. + + An of that contains the source collection. + The type to convert the values to. + The source collection type. + An that contains the converted values of every token in the source collection. + + + + Returns the input typed as . + + An of that contains the source collection. + The input typed as . + + + + Returns the input typed as . + + The source collection type. + An of that contains the source collection. + The input typed as . + + + + Represents a JSON constructor. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets or sets the name of this constructor. + + The constructor name. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified name and content. + + The constructor name. + The contents of the constructor. + + + + Initializes a new instance of the class with the specified name and content. + + The constructor name. + The contents of the constructor. + + + + Initializes a new instance of the class with the specified name. + + The constructor name. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified key. + + The with the specified key. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Represents a token that can contain other tokens. + + + + + Occurs when the list changes or an item in the list changes. + + + + + Occurs before an item is added to the collection. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Raises the event. + + The instance containing the event data. + + + + Raises the event. + + The instance containing the event data. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Get the first child token of this token. + + + A containing the first child token of the . + + + + + Get the last child token of this token. + + + A containing the last child token of the . + + + + + Returns a collection of the child tokens of this token, in document order. + + + An of containing the child tokens of this , in document order. + + + + + Returns a collection of the child values of this token, in document order. + + The type to convert the values to. + + A containing the child values of this , in document order. + + + + + Returns a collection of the descendant tokens for this token in document order. + + An containing the descendant tokens of the . + + + + Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order. + + An containing this token, and all the descendant tokens of the . + + + + Adds the specified content as children of this . + + The content to be added. + + + + Adds the specified content as the first children of this . + + The content to be added. + + + + Creates an that can be used to add tokens to the . + + An that is ready to have content written to it. + + + + Replaces the children nodes of this token with the specified content. + + The content. + + + + Removes the child nodes from this token. + + + + + Merge the specified content into this . + + The content to be merged. + + + + Merge the specified content into this using . + + The content to be merged. + The used to merge the content. + + + + Gets the count of child JSON tokens. + + The count of child JSON tokens + + + + Represents a collection of objects. + + The type of token + + + + An empty collection of objects. + + + + + Initializes a new instance of the struct. + + The enumerable. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + + + + Gets the with the specified key. + + + + + + Determines whether the specified is equal to this instance. + + The to compare with this instance. + + true if the specified is equal to this instance; otherwise, false. + + + + + Determines whether the specified is equal to this instance. + + The to compare with this instance. + + true if the specified is equal to this instance; otherwise, false. + + + + + Returns a hash code for this instance. + + + A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + + + + + Represents a JSON object. + + + + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Occurs when a property value changes. + + + + + Occurs when a property value is changing. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified content. + + The contents of the object. + + + + Initializes a new instance of the class with the specified content. + + The contents of the object. + + + + Gets the node type for this . + + The type. + + + + Gets an of this object's properties. + + An of this object's properties. + + + + Gets a the specified name. + + The property name. + A with the specified name or null. + + + + Gets an of this object's property values. + + An of this object's property values. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets or sets the with the specified property name. + + + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + + + + Creates a from an object. + + The object that will be used to create . + A with the values of the specified object + + + + Creates a from an object. + + The object that will be used to create . + The that will be used to read the object. + A with the values of the specified object + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified property name. + + Name of the property. + The with the specified property name. + + + + Gets the with the specified property name. + The exact property name will be searched for first and if no matching property is found then + the will be used to match a property. + + Name of the property. + One of the enumeration values that specifies how the strings will be compared. + The with the specified property name. + + + + Tries to get the with the specified property name. + The exact property name will be searched for first and if no matching property is found then + the will be used to match a property. + + Name of the property. + The value. + One of the enumeration values that specifies how the strings will be compared. + true if a value was successfully retrieved; otherwise, false. + + + + Adds the specified property name. + + Name of the property. + The value. + + + + Removes the property with the specified name. + + Name of the property. + true if item was successfully removed; otherwise, false. + + + + Tries the get value. + + Name of the property. + The value. + true if a value was successfully retrieved; otherwise, false. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Raises the event with the provided arguments. + + Name of the property. + + + + Raises the event with the provided arguments. + + Name of the property. + + + + Returns the properties for this instance of a component. + + + A that represents the properties for this component instance. + + + + + Returns the properties for this instance of a component using the attribute array as a filter. + + An array of type that is used as a filter. + + A that represents the filtered properties for this component instance. + + + + + Returns a collection of custom attributes for this instance of a component. + + + An containing the attributes for this object. + + + + + Returns the class name of this instance of a component. + + + The class name of the object, or null if the class does not have a name. + + + + + Returns the name of this instance of a component. + + + The name of the object, or null if the object does not have a name. + + + + + Returns a type converter for this instance of a component. + + + A that is the converter for this object, or null if there is no for this object. + + + + + Returns the default event for this instance of a component. + + + An that represents the default event for this object, or null if this object does not have events. + + + + + Returns the default property for this instance of a component. + + + A that represents the default property for this object, or null if this object does not have properties. + + + + + Returns an editor of the specified type for this instance of a component. + + A that represents the editor for this object. + + An of the specified type that is the editor for this object, or null if the editor cannot be found. + + + + + Returns the events for this instance of a component using the specified attribute array as a filter. + + An array of type that is used as a filter. + + An that represents the filtered events for this component instance. + + + + + Returns the events for this instance of a component. + + + An that represents the events for this component instance. + + + + + Returns an object that contains the property described by the specified property descriptor. + + A that represents the property whose owner is to be found. + + An that represents the owner of the specified property. + + + + + Represents a JSON array. + + + + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified content. + + The contents of the array. + + + + Initializes a new instance of the class with the specified content. + + The contents of the array. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + + + + Creates a from an object. + + The object that will be used to create . + A with the values of the specified object + + + + Creates a from an object. + + The object that will be used to create . + The that will be used to read the object. + A with the values of the specified object + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets or sets the at the specified index. + + + + + + Determines the index of a specific item in the . + + The object to locate in the . + + The index of if found in the list; otherwise, -1. + + + + + Inserts an item to the at the specified index. + + The zero-based index at which should be inserted. + The object to insert into the . + + is not a valid index in the . + The is read-only. + + + + Removes the item at the specified index. + + The zero-based index of the item to remove. + + is not a valid index in the . + The is read-only. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Adds an item to the . + + The object to add to the . + The is read-only. + + + + Removes all items from the . + + The is read-only. + + + + Determines whether the contains a specific value. + + The object to locate in the . + + true if is found in the ; otherwise, false. + + + + + Copies to. + + The array. + Index of the array. + + + + Gets a value indicating whether the is read-only. + + true if the is read-only; otherwise, false. + + + + Removes the first occurrence of a specific object from the . + + The object to remove from the . + + true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + + The is read-only. + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Gets the at the reader's current position. + + + + + Initializes a new instance of the class. + + The token to read from. + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Gets the path of the current JSON token. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets the at the writer's current position. + + + + + Gets the token being writen. + + The token being writen. + + + + Initializes a new instance of the class writing to the given . + + The container being written to. + + + + Initializes a new instance of the class. + + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the end. + + The token. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Represents an abstract JSON token. + + + + + Gets a comparer that can compare two tokens for value equality. + + A that can compare two nodes for value equality. + + + + Gets or sets the parent. + + The parent. + + + + Gets the root of this . + + The root of this . + + + + Gets the node type for this . + + The type. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Compares the values of two tokens, including the values of all descendant tokens. + + The first to compare. + The second to compare. + true if the tokens are equal; otherwise false. + + + + Gets the next sibling token of this node. + + The that contains the next sibling token. + + + + Gets the previous sibling token of this node. + + The that contains the previous sibling token. + + + + Gets the path of the JSON token. + + + + + Adds the specified content immediately after this token. + + A content object that contains simple content or a collection of content objects to be added after this token. + + + + Adds the specified content immediately before this token. + + A content object that contains simple content or a collection of content objects to be added before this token. + + + + Returns a collection of the ancestor tokens of this token. + + A collection of the ancestor tokens of this token. + + + + Returns a collection of tokens that contain this token, and the ancestors of this token. + + A collection of tokens that contain this token, and the ancestors of this token. + + + + Returns a collection of the sibling tokens after this token, in document order. + + A collection of the sibling tokens after this tokens, in document order. + + + + Returns a collection of the sibling tokens before this token, in document order. + + A collection of the sibling tokens before this token, in document order. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets the with the specified key converted to the specified type. + + The type to convert the token to. + The token key. + The converted token value. + + + + Get the first child token of this token. + + A containing the first child token of the . + + + + Get the last child token of this token. + + A containing the last child token of the . + + + + Returns a collection of the child tokens of this token, in document order. + + An of containing the child tokens of this , in document order. + + + + Returns a collection of the child tokens of this token, in document order, filtered by the specified type. + + The type to filter the child tokens on. + A containing the child tokens of this , in document order. + + + + Returns a collection of the child values of this token, in document order. + + The type to convert the values to. + A containing the child values of this , in document order. + + + + Removes this token from its parent. + + + + + Replaces this token with the specified token. + + The value. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Returns the indented JSON for this token. + + + The indented JSON for this token. + + + + + Returns the JSON for this token using the given formatting and converters. + + Indicates how the output is formatted. + A collection of which will be used when writing the token. + The JSON for this token using the given formatting and converters. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to []. + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from [] to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Creates an for this token. + + An that can be used to read this token and its descendants. + + + + Creates a from an object. + + The object that will be used to create . + A with the value of the specified object + + + + Creates a from an object using the specified . + + The object that will be used to create . + The that will be used when reading the object. + A with the value of the specified object + + + + Creates the specified .NET type from the . + + The object type that the token will be deserialized to. + The new object created from the JSON value. + + + + Creates the specified .NET type from the . + + The object type that the token will be deserialized to. + The new object created from the JSON value. + + + + Creates the specified .NET type from the using the specified . + + The object type that the token will be deserialized to. + The that will be used when creating the object. + The new object created from the JSON value. + + + + Creates the specified .NET type from the using the specified . + + The object type that the token will be deserialized to. + The that will be used when creating the object. + The new object created from the JSON value. + + + + Creates a from a . + + An positioned at the token to read into this . + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Creates a from a . + + An positioned at the token to read into this . + The used to load the JSON. + If this is null, default load settings will be used. + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + Creates a from a . + + An positioned at the token to read into this . + The used to load the JSON. + If this is null, default load settings will be used. + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Creates a from a . + + An positioned at the token to read into this . + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Selects a using a JPath expression. Selects the token that matches the object path. + + + A that contains a JPath expression. + + A , or null. + + + + Selects a using a JPath expression. Selects the token that matches the object path. + + + A that contains a JPath expression. + + A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + A . + + + + Selects a collection of elements using a JPath expression. + + + A that contains a JPath expression. + + An that contains the selected elements. + + + + Selects a collection of elements using a JPath expression. + + + A that contains a JPath expression. + + A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + An that contains the selected elements. + + + + Creates a new instance of the . All child tokens are recursively cloned. + + A new instance of the . + + + + Adds an object to the annotation list of this . + + The annotation to add. + + + + Get the first annotation object of the specified type from this . + + The type of the annotation to retrieve. + The first annotation object that matches the specified type, or null if no annotation is of the specified type. + + + + Gets the first annotation object of the specified type from this . + + The of the annotation to retrieve. + The first annotation object that matches the specified type, or null if no annotation is of the specified type. + + + + Gets a collection of annotations of the specified type for this . + + The type of the annotations to retrieve. + An that contains the annotations for this . + + + + Gets a collection of annotations of the specified type for this . + + The of the annotations to retrieve. + An of that contains the annotations that match the specified type for this . + + + + Removes the annotations of the specified type from this . + + The type of annotations to remove. + + + + Removes the annotations of the specified type from this . + + The of annotations to remove. + + + + Represents a JSON property. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets the property name. + + The property name. + + + + Gets or sets the property value. + + The property value. + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + The property name. + The property content. + + + + Initializes a new instance of the class. + + The property name. + The property content. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Specifies the type of token. + + + + + No token type has been set. + + + + + A JSON object. + + + + + A JSON array. + + + + + A JSON constructor. + + + + + A JSON object property. + + + + + A comment. + + + + + An integer value. + + + + + A float value. + + + + + A string value. + + + + + A boolean value. + + + + + A null value. + + + + + An undefined value. + + + + + A date value. + + + + + A raw JSON value. + + + + + A collection of bytes value. + + + + + A Guid value. + + + + + A Uri value. + + + + + A TimeSpan value. + + + + + Represents a value in JSON (string, integer, date, etc). + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Creates a comment with the given value. + + The value. + A comment with the given value. + + + + Creates a string with the given value. + + The value. + A string with the given value. + + + + Creates a null value. + + A null value. + + + + Creates a undefined value. + + A undefined value. + + + + Gets the node type for this . + + The type. + + + + Gets or sets the underlying token value. + + The underlying token value. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Determines whether the specified is equal to the current . + + The to compare with the current . + + true if the specified is equal to the current ; otherwise, false. + + + The parameter is null. + + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + + + + Returns a that represents this instance. + + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format. + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format provider. + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format. + The format provider. + + A that represents this instance. + + + + + Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + + An object to compare with this instance. + + A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: + Value + Meaning + Less than zero + This instance is less than . + Zero + This instance is equal to . + Greater than zero + This instance is greater than . + + + is not the same type as this instance. + + + + + Specifies metadata property handling options for the . + + + + + Read metadata properties located at the start of a JSON object. + + + + + Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance. + + + + + Do not try to read metadata properties. + + + + + A camel case naming strategy. + + + + + Initializes a new instance of the class. + + + A flag indicating whether dictionary keys should be processed. + + + A flag indicating whether explicitly specified property names should be processed, + e.g. a property name customized with a . + + + + + Initializes a new instance of the class. + + + + + Resolves the specified property name. + + The property name to resolve. + The resolved property name. + + + + The default naming strategy. Property names and dictionary keys are unchanged. + + + + + Resolves the specified property name. + + The property name to resolve. + The resolved property name. + + + + Represents a trace writer that writes to the application's instances. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + + The that will be used to filter the trace messages passed to the writer. + + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Provides methods to get attributes. + + + + + Returns a collection of all of the attributes, or an empty collection if there are no attributes. + + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. + + The type of the attributes. + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Represents a trace writer. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + The that will be used to filter the trace messages passed to the writer. + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Contract details for a used by the . + + + + + Gets or sets the default collection items . + + The converter. + + + + Gets or sets a value indicating whether the collection items preserve object references. + + true if collection items preserve object references; otherwise, false. + + + + Gets or sets the collection item reference loop handling. + + The reference loop handling. + + + + Gets or sets the collection item type name handling. + + The type name handling. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Represents a trace writer that writes to memory. When the trace message limit is + reached then old trace messages will be removed as new messages are added. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + + The that will be used to filter the trace messages passed to the writer. + + + + + Initializes a new instance of the class. + + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Returns an enumeration of the most recent trace messages. + + An enumeration of the most recent trace messages. + + + + Returns a of the most recent trace messages. + + + A of the most recent trace messages. + + + + + A base class for resolving how property names and dictionary keys are serialized. + + + + + A flag indicating whether dictionary keys should be processed. + Defaults to false. + + + + + A flag indicating whether explicitly specified property names, + e.g. a property name customized with a , should be processed. + Defaults to false. + + + + + Gets the serialized name for a given property name. + + The initial property name. + A flag indicating whether the property has had a name explicitly specfied. + The serialized property name. + + + + Gets the serialized key for a given dictionary key. + + The initial dictionary key. + The serialized dictionary key. + + + + Resolves the specified property name. + + The property name to resolve. + The resolved property name. + + + + Provides methods to get attributes from a , , or . + + + + + Initializes a new instance of the class. + + The instance to get attributes for. This parameter should be a , , or . + + + + Returns a collection of all of the attributes, or an empty collection if there are no attributes. + + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. + + The type of the attributes. + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + A snake case naming strategy. + + + + + Initializes a new instance of the class. + + + A flag indicating whether dictionary keys should be processed. + + + A flag indicating whether explicitly specified property names should be processed, + e.g. a property name customized with a . + + + + + Initializes a new instance of the class. + + + + + Resolves the specified property name. + + The property name to resolve. + The resolved property name. + + + + Contract details for a used by the . + + + + + Gets or sets the ISerializable object constructor. + + The ISerializable object constructor. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Get and set values for a using dynamic methods. + + + + + Initializes a new instance of the class. + + The member info. + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Provides data for the Error event. + + + + + Gets the current object the error event is being raised against. + + The current object the error event is being raised against. + + + + Gets the error context. + + The error context. + + + + Initializes a new instance of the class. + + The current object. + The error context. + + + + Resolves member mappings for a type, camel casing property names. + + + + + Initializes a new instance of the class. + + + + + Used by to resolves a for a given . + + + + + Gets a value indicating whether members are being get and set using dynamic code generation. + This value is determined by the runtime permissions available. + + + true if using dynamic code generation; otherwise, false. + + + + + Gets or sets the default members search flags. + + The default members search flags. + + + + Gets or sets a value indicating whether compiler generated members should be serialized. + + + true if serialized compiler generated members; otherwise, false. + + + + + Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. + + + true if the interface will be ignored when serializing and deserializing types; otherwise, false. + + + + + Gets or sets a value indicating whether to ignore the attribute when serializing and deserializing types. + + + true if the attribute will be ignored when serializing and deserializing types; otherwise, false. + + + + + Gets or sets the naming strategy used to resolve how property names and dictionary keys are serialized. + + The naming strategy used to resolve how property names and dictionary keys are serialized. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + + If set to true the will use a cached shared with other resolvers of the same type. + Sharing the cache will significantly improve performance with multiple resolver instances because expensive reflection will only + happen once. This setting can cause unexpected behavior if different instances of the resolver are suppose to produce different + results. When set to false it is highly recommended to reuse instances with the . + + + + + Resolves the contract for a given type. + + The type to resolve a contract for. + The contract for a given type. + + + + Gets the serializable members for the type. + + The type to get serializable members for. + The serializable members for the type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates the constructor parameters. + + The constructor to create properties for. + The type's member properties. + Properties for the given . + + + + Creates a for the given . + + The matching member property. + The constructor parameter. + A created for the given . + + + + Resolves the default for the contract. + + Type of the object. + The contract's default . + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Determines which contract type is created for the given type. + + Type of the object. + A for the given type. + + + + Creates properties for the given . + + The type to create properties for. + /// The member serialization mode for the type. + Properties for the given . + + + + Creates the used by the serializer to get and set values from a member. + + The member. + The used by the serializer to get and set values from a member. + + + + Creates a for the given . + + The member's parent . + The member to create a for. + A created for the given . + + + + Resolves the name of the property. + + Name of the property. + Resolved name of the property. + + + + Resolves the key of the dictionary. By default is used to resolve dictionary keys. + + Key of the dictionary. + Resolved key of the dictionary. + + + + Gets the resolved name of the property. + + Name of the property. + Name of the property. + + + + The default serialization binder used when resolving and loading classes from type names. + + + + + When overridden in a derived class, controls the binding of a serialized object to a type. + + Specifies the name of the serialized object. + Specifies the name of the serialized object. + + The type of the object the formatter creates a new instance of. + + + + + Provides information surrounding an error. + + + + + Gets the error. + + The error. + + + + Gets the original object that caused the error. + + The original object that caused the error. + + + + Gets the member that caused the error. + + The member that caused the error. + + + + Gets the path of the JSON location where the error occurred. + + The path of the JSON location where the error occurred. + + + + Gets or sets a value indicating whether this is handled. + + true if handled; otherwise, false. + + + + Used by to resolves a for a given . + + + + + + + + + Resolves the contract for a given type. + + The type to resolve a contract for. + The contract for a given type. + + + + Provides methods to get and set values. + + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Contract details for a used by the . + + + + + Gets the of the collection items. + + The of the collection items. + + + + Gets a value indicating whether the collection type is a multidimensional array. + + true if the collection type is a multidimensional array; otherwise, false. + + + + Gets or sets the function used to create the object. When set this function will override . + + The function used to create the object. + + + + Gets a value indicating whether the creator has a parameter with the collection values. + + true if the creator has a parameter with the collection values; otherwise, false. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Handles serialization callback events. + + The object that raised the callback event. + The streaming context. + + + + Handles serialization error callback events. + + The object that raised the callback event. + The streaming context. + The error context. + + + + Sets extension data for an object during deserialization. + + The object to set extension data on. + The extension data key. + The extension data value. + + + + Gets extension data for an object during serialization. + + The object to set extension data on. + + + + Contract details for a used by the . + + + + + Gets the underlying type for the contract. + + The underlying type for the contract. + + + + Gets or sets the type created during deserialization. + + The type created during deserialization. + + + + Gets or sets whether this type contract is serialized as a reference. + + Whether this type contract is serialized as a reference. + + + + Gets or sets the default for this contract. + + The converter. + + + + Gets or sets all methods called immediately after deserialization of the object. + + The methods called immediately after deserialization of the object. + + + + Gets or sets all methods called during deserialization of the object. + + The methods called during deserialization of the object. + + + + Gets or sets all methods called after serialization of the object graph. + + The methods called after serialization of the object graph. + + + + Gets or sets all methods called before serialization of the object. + + The methods called before serialization of the object. + + + + Gets or sets all method called when an error is thrown during the serialization of the object. + + The methods called when an error is thrown during the serialization of the object. + + + + Gets or sets the method called immediately after deserialization of the object. + + The method called immediately after deserialization of the object. + + + + Gets or sets the method called during deserialization of the object. + + The method called during deserialization of the object. + + + + Gets or sets the method called after serialization of the object graph. + + The method called after serialization of the object graph. + + + + Gets or sets the method called before serialization of the object. + + The method called before serialization of the object. + + + + Gets or sets the method called when an error is thrown during the serialization of the object. + + The method called when an error is thrown during the serialization of the object. + + + + Gets or sets the default creator method used to create the object. + + The default creator method used to create the object. + + + + Gets or sets a value indicating whether the default creator is non public. + + true if the default object creator is non-public; otherwise, false. + + + + Contract details for a used by the . + + + + + Gets or sets the property name resolver. + + The property name resolver. + + + + Gets or sets the dictionary key resolver. + + The dictionary key resolver. + + + + Gets the of the dictionary keys. + + The of the dictionary keys. + + + + Gets the of the dictionary values. + + The of the dictionary values. + + + + Gets or sets the function used to create the object. When set this function will override . + + The function used to create the object. + + + + Gets a value indicating whether the creator has a parameter with the dictionary values. + + true if the creator has a parameter with the dictionary values; otherwise, false. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Maps a JSON property to a .NET member or constructor parameter. + + + + + Gets or sets the name of the property. + + The name of the property. + + + + Gets or sets the type that declared this property. + + The type that declared this property. + + + + Gets or sets the order of serialization of a member. + + The numeric order of serialization. + + + + Gets or sets the name of the underlying member or parameter. + + The name of the underlying member or parameter. + + + + Gets the that will get and set the during serialization. + + The that will get and set the during serialization. + + + + Gets or sets the for this property. + + The for this property. + + + + Gets or sets the type of the property. + + The type of the property. + + + + Gets or sets the for the property. + If set this converter takes presidence over the contract converter for the property type. + + The converter. + + + + Gets or sets the member converter. + + The member converter. + + + + Gets or sets a value indicating whether this is ignored. + + true if ignored; otherwise, false. + + + + Gets or sets a value indicating whether this is readable. + + true if readable; otherwise, false. + + + + Gets or sets a value indicating whether this is writable. + + true if writable; otherwise, false. + + + + Gets or sets a value indicating whether this has a member attribute. + + true if has a member attribute; otherwise, false. + + + + Gets the default value. + + The default value. + + + + Gets or sets a value indicating whether this is required. + + A value indicating whether this is required. + + + + Gets or sets a value indicating whether this property preserves object references. + + + true if this instance is reference; otherwise, false. + + + + + Gets or sets the property null value handling. + + The null value handling. + + + + Gets or sets the property default value handling. + + The default value handling. + + + + Gets or sets the property reference loop handling. + + The reference loop handling. + + + + Gets or sets the property object creation handling. + + The object creation handling. + + + + Gets or sets or sets the type name handling. + + The type name handling. + + + + Gets or sets a predicate used to determine whether the property should be serialize. + + A predicate used to determine whether the property should be serialize. + + + + Gets or sets a predicate used to determine whether the property should be deserialized. + + A predicate used to determine whether the property should be deserialized. + + + + Gets or sets a predicate used to determine whether the property should be serialized. + + A predicate used to determine whether the property should be serialized. + + + + Gets or sets an action used to set whether the property has been deserialized. + + An action used to set whether the property has been deserialized. + + + + Returns a that represents this instance. + + + A that represents this instance. + + + + + Gets or sets the converter used when serializing the property's collection items. + + The collection's items converter. + + + + Gets or sets whether this property's collection items are serialized as a reference. + + Whether this property's collection items are serialized as a reference. + + + + Gets or sets the the type name handling used when serializing the property's collection items. + + The collection's items type name handling. + + + + Gets or sets the the reference loop handling used when serializing the property's collection items. + + The collection's items reference loop handling. + + + + A collection of objects. + + + + + Initializes a new instance of the class. + + The type. + + + + When implemented in a derived class, extracts the key from the specified element. + + The element from which to extract the key. + The key for the specified element. + + + + Adds a object. + + The property to add to the collection. + + + + Gets the closest matching object. + First attempts to get an exact case match of propertyName and then + a case insensitive match. + + Name of the property. + A matching property if found. + + + + Gets a property by property name. + + The name of the property to get. + Type property name string comparison. + A matching property if found. + + + + Used to resolve references when serializing and deserializing JSON by the . + + + + + Resolves a reference to its object. + + The serialization context. + The reference to resolve. + The object that + + + + Gets the reference for the sepecified object. + + The serialization context. + The object to get a reference for. + The reference to the object. + + + + Determines whether the specified object is referenced. + + The serialization context. + The object to test for a reference. + + true if the specified object is referenced; otherwise, false. + + + + + Adds a reference to the specified object. + + The serialization context. + The reference. + The object to reference. + + + + Contract details for a used by the . + + + + + Gets or sets the object member serialization. + + The member object serialization. + + + + Gets or sets a value that indicates whether the object's properties are required. + + + A value indicating whether the object's properties are required. + + + + + Gets the object's properties. + + The object's properties. + + + + Gets the constructor parameters required for any non-default constructor + + + + + Gets a collection of instances that define the parameters used with . + + + + + Gets or sets the override constructor used to create the object. + This is set when a constructor is marked up using the + JsonConstructor attribute. + + The override constructor. + + + + Gets or sets the parametrized constructor used to create the object. + + The parametrized constructor. + + + + Gets or sets the function used to create the object. When set this function will override . + This function is called with a collection of arguments which are defined by the collection. + + The function used to create the object. + + + + Gets or sets the extension data setter. + + + + + Gets or sets the extension data getter. + + + + + Gets or sets the extension data value type. + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Lookup and create an instance of the JsonConverter type described by the argument. + + The JsonConverter type to create. + Optional arguments to pass to an initializing constructor of the JsonConverter. + If null, the default constructor is used. + + + + Get and set values for a using reflection. + + + + + Initializes a new instance of the class. + + The member info. + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + When applied to a method, specifies that the method is called when an error occurs serializing an object. + + + + + Represents a method that constructs an object. + + The object type to create. + + + + Specifies how strings are escaped when writing JSON text. + + + + + Only control characters (e.g. newline) are escaped. + + + + + All non-ASCII and control characters (e.g. newline) are escaped. + + + + + HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. + + + + + Converts the value to the specified type. If the value is unable to be converted, the + value is checked whether it assignable to the specified type. + + The value to convert. + The culture to use when converting. + The type to convert or cast the value to. + + The converted type. If conversion was unsuccessful, the initial value + is returned if assignable to the target type. + + + + + Gets a dictionary of the names and values of an Enum type. + + + + + + Gets a dictionary of the names and values of an Enum type. + + The enum type to get names and values for. + + + + + Builds a string. Unlike StringBuilder this class lets you reuse it's internal buffer. + + + + + Determines whether the collection is null or empty. + + The collection. + + true if the collection is null or empty; otherwise, false. + + + + + Adds the elements of the specified collection to the specified generic IList. + + The list to add to. + The collection of elements to add. + + + + Gets the type of the typed collection's items. + + The type. + The type of the typed collection's items. + + + + Gets the member's underlying type. + + The member. + The underlying type of the member. + + + + Determines whether the member is an indexed property. + + The member. + + true if the member is an indexed property; otherwise, false. + + + + + Determines whether the property is an indexed property. + + The property. + + true if the property is an indexed property; otherwise, false. + + + + + Gets the member's value on the object. + + The member. + The target object. + The member's value on the object. + + + + Sets the member's value on the target object. + + The member. + The target. + The value. + + + + Determines whether the specified MemberInfo can be read. + + The MemberInfo to determine whether can be read. + /// if set to true then allow the member to be gotten non-publicly. + + true if the specified MemberInfo can be read; otherwise, false. + + + + + Determines whether the specified MemberInfo can be set. + + The MemberInfo to determine whether can be set. + if set to true then allow the member to be set non-publicly. + if set to true then allow the member to be set if read-only. + + true if the specified MemberInfo can be set; otherwise, false. + + + + + Determines whether the string is all white space. Empty string will return false. + + The string to test whether it is all white space. + + true if the string is all white space; otherwise, false. + + + + + Indicating whether a property is required. + + + + + The property is not required. The default state. + + + + + The property must be defined in JSON but can be a null value. + + + + + The property must be defined in JSON and cannot be a null value. + + + + + The property is not required but it cannot be a null value. + + + + + Specifies reference handling options for the . + Note that references cannot be preserved when a value is set via a non-default constructor such as types that implement ISerializable. + + + + + + + + Do not preserve references when serializing types. + + + + + Preserve references when serializing into a JSON object structure. + + + + + Preserve references when serializing into a JSON array structure. + + + + + Preserve references when serializing. + + + + + Provides an interface to enable a class to return line and position information. + + + + + Gets a value indicating whether the class can return line information. + + + true if LineNumber and LinePosition can be provided; otherwise, false. + + + + + Gets the current line number. + + The current line number or 0 if no line information is available (for example, HasLineInfo returns false). + + + + Gets the current line position. + + The current line position or 0 if no line information is available (for example, HasLineInfo returns false). + + + + Instructs the how to serialize the collection. + + + + + Gets or sets a value indicating whether null items are allowed in the collection. + + true if null items are allowed in the collection; otherwise, false. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with a flag indicating whether the array can contain null items + + A flag indicating whether the array can contain null items. + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Instructs the how to serialize the object. + + + + + Gets or sets the id. + + The id. + + + + Gets or sets the title. + + The title. + + + + Gets or sets the description. + + The description. + + + + Gets or sets the collection's items converter. + + The collection's items converter. + + + + The parameter list to use when constructing the described by ItemConverterType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the that exactly matches the number, + order, and type of these parameters. + + + [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + + + + + Gets or sets the of the . + + The of the . + + + + The parameter list to use when constructing the described by NamingStrategyType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the that exactly matches the number, + order, and type of these parameters. + + + [JsonContainer(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] + + + + + Gets or sets a value that indicates whether to preserve object references. + + + true to keep object reference; otherwise, false. The default is false. + + + + + Gets or sets a value that indicates whether to preserve collection's items references. + + + true to keep collection's items object references; otherwise, false. The default is false. + + + + + Gets or sets the reference loop handling used when serializing the collection's items. + + The reference loop handling. + + + + Gets or sets the type name handling used when serializing the collection's items. + + The type name handling. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Specifies default value handling options for the . + + + + + + + + + Include members where the member value is the same as the member's default value when serializing objects. + Included members are written to JSON. Has no effect when deserializing. + + + + + Ignore members where the member value is the same as the member's default value when serializing objects + so that is is not written to JSON. + This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, + decimals and floating point numbers; and false for booleans). The default value ignored can be changed by + placing the on the property. + + + + + Members with a default value but no JSON will be set to their default value when deserializing. + + + + + Ignore members where the member value is the same as the member's default value when serializing objects + and sets members to their default value when deserializing. + + + + + Instructs the to use the specified when serializing the member or class. + + + + + Gets the of the . + + The of the . + + + + The parameter list to use when constructing the described by ConverterType. + If null, the default constructor is used. + + + + + Initializes a new instance of the class. + + Type of the . + + + + Initializes a new instance of the class. + + Type of the . + Parameter list to use when constructing the . Can be null. + + + + Instructs the how to serialize the object. + + + + + Gets or sets the member serialization. + + The member serialization. + + + + Gets or sets a value that indicates whether the object's properties are required. + + + A value indicating whether the object's properties are required. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified member serialization. + + The member serialization. + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Specifies the settings on a object. + + + + + Gets or sets how reference loops (e.g. a class referencing itself) is handled. + + Reference loop handling. + + + + Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. + + Missing member handling. + + + + Gets or sets how objects are created during deserialization. + + The object creation handling. + + + + Gets or sets how null values are handled during serialization and deserialization. + + Null value handling. + + + + Gets or sets how null default are handled during serialization and deserialization. + + The default value handling. + + + + Gets or sets a collection that will be used during serialization. + + The converters. + + + + Gets or sets how object references are preserved by the serializer. + + The preserve references handling. + + + + Gets or sets how type name writing and reading is handled by the serializer. + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + The type name handling. + + + + Gets or sets how metadata properties are used during deserialization. + + The metadata properties handling. + + + + Gets or sets how a type name assembly is written and resolved by the serializer. + + The type name assembly format. + + + + Gets or sets how constructors are used during deserialization. + + The constructor handling. + + + + Gets or sets the contract resolver used by the serializer when + serializing .NET objects to JSON and vice versa. + + The contract resolver. + + + + Gets or sets the equality comparer used by the serializer when comparing references. + + The equality comparer. + + + + Gets or sets the used by the serializer when resolving references. + + The reference resolver. + + + + Gets or sets a function that creates the used by the serializer when resolving references. + + A function that creates the used by the serializer when resolving references. + + + + Gets or sets the used by the serializer when writing trace messages. + + The trace writer. + + + + Gets or sets the used by the serializer when resolving type names. + + The binder. + + + + Gets or sets the error handler called during serialization and deserialization. + + The error handler called during serialization and deserialization. + + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + + + + Get or set how and values are formatted when writing JSON text, and the expected date format when reading JSON text. + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling during serialization and deserialization. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written as JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Gets a value indicating whether there will be a check for additional content after deserializing an object. + + + true if there will be a check for additional content after deserializing an object; otherwise, false. + + + + + Initializes a new instance of the class. + + + + + + Represents a reader that provides validation. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Sets an event handler for receiving schema validation errors. + + + + + Gets the text value of the current JSON token. + + + + + + Gets the depth of the current token in the JSON document. + + The depth of the current token in the JSON document. + + + + Gets the path of the current JSON token. + + + + + Gets the quotation mark character used to enclose the value of a string. + + + + + + Gets the type of the current JSON token. + + + + + + Gets the Common Language Runtime (CLR) type for the current JSON token. + + + + + + Initializes a new instance of the class that + validates the content returned from the given . + + The to read from while validating. + + + + Gets or sets the schema. + + The schema. + + + + Gets the used to construct this . + + The specified in the constructor. + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a []. + + + A [] or a null reference if the next JSON token is null. + + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Specifies the member serialization options for the . + + + + + All public members are serialized by default. Members can be excluded using or . + This is the default member serialization mode. + + + + + Only members marked with or are serialized. + This member serialization mode can also be set by marking the class with . + + + + + All public and private fields are serialized. Members can be excluded using or . + This member serialization mode can also be set by marking the class with + and setting IgnoreSerializableAttribute on to false. + + + + + Specifies how object creation is handled by the . + + + + + Reuse existing objects, create new objects when needed. + + + + + Only reuse existing objects. + + + + + Always create new objects. + + + + + Represents a reader that provides fast, non-cached, forward-only access to JSON text data. + + + + + Initializes a new instance of the class with the specified . + + The TextReader containing the XML data to read. + + + + Gets or sets the reader's character buffer pool. + + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a []. + + A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Changes the state to closed. + + + + + Gets a value indicating whether the class can return line information. + + + true if LineNumber and LinePosition can be provided; otherwise, false. + + + + + Gets the current line number. + + + The current line number or 0 if no line information is available (for example, HasLineInfo returns false). + + + + + Gets the current line position. + + + The current line position or 0 if no line information is available (for example, HasLineInfo returns false). + + + + + Instructs the to always serialize the member with the specified name. + + + + + Gets or sets the used when serializing the property's collection items. + + The collection's items . + + + + The parameter list to use when constructing the described by ItemConverterType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the that exactly matches the number, + order, and type of these parameters. + + + [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + + + + + Gets or sets the of the . + + The of the . + + + + The parameter list to use when constructing the described by NamingStrategyType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the that exactly matches the number, + order, and type of these parameters. + + + [JsonProperty(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] + + + + + Gets or sets the null value handling used when serializing this property. + + The null value handling. + + + + Gets or sets the default value handling used when serializing this property. + + The default value handling. + + + + Gets or sets the reference loop handling used when serializing this property. + + The reference loop handling. + + + + Gets or sets the object creation handling used when deserializing this property. + + The object creation handling. + + + + Gets or sets the type name handling used when serializing this property. + + The type name handling. + + + + Gets or sets whether this property's value is serialized as a reference. + + Whether this property's value is serialized as a reference. + + + + Gets or sets the order of serialization of a member. + + The numeric order of serialization. + + + + Gets or sets a value indicating whether this property is required. + + + A value indicating whether this property is required. + + + + + Gets or sets the name of the property. + + The name of the property. + + + + Gets or sets the the reference loop handling used when serializing the property's collection items. + + The collection's items reference loop handling. + + + + Gets or sets the the type name handling used when serializing the property's collection items. + + The collection's items type name handling. + + + + Gets or sets whether this property's collection items are serialized as a reference. + + Whether this property's collection items are serialized as a reference. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified name. + + Name of the property. + + + + Instructs the not to serialize the public field or public read/write property value. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets the writer's character array pool. + + + + + Gets or sets how many IndentChars to write for each level in the hierarchy when is set to Formatting.Indented. + + + + + Gets or sets which character to use to quote attribute values. + + + + + Gets or sets which character to use for indenting when is set to Formatting.Indented. + + + + + Gets or sets a value indicating whether object names will be surrounded with quotes. + + + + + Creates an instance of the JsonWriter class using the specified . + + The TextWriter to write to. + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the specified end token. + + The end token to write. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + A flag to indicate whether the text should be escaped when it is written as a JSON property name. + + + + Writes indent characters. + + + + + Writes the JSON value delimiter. + + + + + Writes an indent space. + + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes out the given white space. + + The string of white space characters. + + + + The exception thrown when an error occurs while reading JSON text. + + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + The exception thrown when an error occurs while reading JSON text. + + + + + Gets the line number indicating where the error occurred. + + The line number indicating where the error occurred. + + + + Gets the line position indicating where the error occurred. + + The line position indicating where the error occurred. + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Converts an object to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + + Gets the of the JSON produced by the JsonConverter. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The of the JSON produced by the JsonConverter. + + + + Gets a value indicating whether this can read JSON. + + true if this can read JSON; otherwise, false. + + + + Gets a value indicating whether this can write JSON. + + true if this can write JSON; otherwise, false. + + + + Represents a collection of . + + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Specifies the state of the reader. + + + + + The Read method has not been called. + + + + + The end of the file has been reached successfully. + + + + + Reader is at a property. + + + + + Reader is at the start of an object. + + + + + Reader is in an object. + + + + + Reader is at the start of an array. + + + + + Reader is in an array. + + + + + The Close method has been called. + + + + + Reader has just read a value. + + + + + Reader is at the start of a constructor. + + + + + Reader in a constructor. + + + + + An error occurred that prevents the read operation from continuing. + + + + + The end of the file has been reached successfully. + + + + + Gets the current reader state. + + The current reader state. + + + + Gets or sets a value indicating whether the underlying stream or + should be closed when the reader is closed. + + + true to close the underlying stream or when + the reader is closed; otherwise false. The default is true. + + + + + Gets or sets a value indicating whether multiple pieces of JSON content can + be read from a continuous stream without erroring. + + + true to support reading multiple pieces of JSON content; otherwise false. The default is false. + + + + + Gets the quotation mark character used to enclose the value of a string. + + + + + Get or set how time zones are handling when reading JSON. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how custom date formatted strings are parsed when reading JSON. + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Gets the type of the current JSON token. + + + + + Gets the text value of the current JSON token. + + + + + Gets The Common Language Runtime (CLR) type for the current JSON token. + + + + + Gets the depth of the current token in the JSON document. + + The depth of the current token in the JSON document. + + + + Gets the path of the current JSON token. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Initializes a new instance of the class with the specified . + + + + + Reads the next JSON token from the stream. + + true if the next token was read successfully; false if there are no more tokens to read. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a []. + + A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Skips the children of the current token. + + + + + Sets the current token. + + The new token. + + + + Sets the current token and value. + + The new token. + The value. + + + + Sets the state based on current token type. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + Releases unmanaged and - optionally - managed resources + + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + Changes the to Closed. + + + + + Provides methods for converting between common language runtime types and JSON types. + + + + + + + + Gets or sets a function that creates default . + Default settings are automatically used by serialization methods on , + and and on . + To serialize without using any default settings create a with + . + + + + + Represents JavaScript's boolean value true as a string. This field is read-only. + + + + + Represents JavaScript's boolean value false as a string. This field is read-only. + + + + + Represents JavaScript's null as a string. This field is read-only. + + + + + Represents JavaScript's undefined as a string. This field is read-only. + + + + + Represents JavaScript's positive infinity as a string. This field is read-only. + + + + + Represents JavaScript's negative infinity as a string. This field is read-only. + + + + + Represents JavaScript's NaN as a string. This field is read-only. + + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation using the specified. + + The value to convert. + The format the date will be converted to. + The time zone handling when the date is converted to a string. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation using the specified. + + The value to convert. + The format the date will be converted to. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + The string delimiter character. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + The string delimiter character. + The string escape handling. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Serializes the specified object to a JSON string. + + The object to serialize. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using formatting. + + The object to serialize. + Indicates how the output is formatted. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a collection of . + + The object to serialize. + A collection converters used while serializing. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using formatting and a collection of . + + The object to serialize. + Indicates how the output is formatted. + A collection converters used while serializing. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using . + + The object to serialize. + The used to serialize the object. + If this is null, default serialization settings will be used. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a type, formatting and . + + The object to serialize. + The used to serialize the object. + If this is null, default serialization settings will be used. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using formatting and . + + The object to serialize. + Indicates how the output is formatted. + The used to serialize the object. + If this is null, default serialization settings will be used. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a type, formatting and . + + The object to serialize. + Indicates how the output is formatted. + The used to serialize the object. + If this is null, default serialization settings will be used. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + A JSON string representation of the object. + + + + + Deserializes the JSON to a .NET object. + + The JSON to deserialize. + The deserialized object from the JSON string. + + + + Deserializes the JSON to a .NET object using . + + The JSON to deserialize. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type. + + The JSON to deserialize. + The of object being deserialized. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type. + + The type of the object to deserialize to. + The JSON to deserialize. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the given anonymous type. + + + The anonymous type to deserialize to. This can't be specified + traditionally and must be infered from the anonymous type passed + as a parameter. + + The JSON to deserialize. + The anonymous type object. + The deserialized anonymous type from the JSON string. + + + + Deserializes the JSON to the given anonymous type using . + + + The anonymous type to deserialize to. This can't be specified + traditionally and must be infered from the anonymous type passed + as a parameter. + + The JSON to deserialize. + The anonymous type object. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized anonymous type from the JSON string. + + + + Deserializes the JSON to the specified .NET type using a collection of . + + The type of the object to deserialize to. + The JSON to deserialize. + Converters to use while deserializing. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using . + + The type of the object to deserialize to. + The object to deserialize. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using a collection of . + + The JSON to deserialize. + The type of the object to deserialize. + Converters to use while deserializing. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using . + + The JSON to deserialize. + The type of the object to deserialize to. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Populates the object with values from the JSON string. + + The JSON to populate values from. + The target object to populate values onto. + + + + Populates the object with values from the JSON string using . + + The JSON to populate values from. + The target object to populate values onto. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + + + + Serializes the XML node to a JSON string. + + The node to serialize. + A JSON string of the XmlNode. + + + + Serializes the XML node to a JSON string using formatting. + + The node to serialize. + Indicates how the output is formatted. + A JSON string of the XmlNode. + + + + Serializes the XML node to a JSON string using formatting and omits the root object if is true. + + The node to serialize. + Indicates how the output is formatted. + Omits writing the root object. + A JSON string of the XmlNode. + + + + Deserializes the XmlNode from a JSON string. + + The JSON string. + The deserialized XmlNode + + + + Deserializes the XmlNode from a JSON string nested in a root elment specified by . + + The JSON string. + The name of the root element to append when deserializing. + The deserialized XmlNode + + + + Deserializes the XmlNode from a JSON string nested in a root elment specified by + and writes a .NET array attribute for collections. + + The JSON string. + The name of the root element to append when deserializing. + + A flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + The deserialized XmlNode + + + + Serializes the to a JSON string. + + The node to convert to JSON. + A JSON string of the XNode. + + + + Serializes the to a JSON string using formatting. + + The node to convert to JSON. + Indicates how the output is formatted. + A JSON string of the XNode. + + + + Serializes the to a JSON string using formatting and omits the root object if is true. + + The node to serialize. + Indicates how the output is formatted. + Omits writing the root object. + A JSON string of the XNode. + + + + Deserializes the from a JSON string. + + The JSON string. + The deserialized XNode + + + + Deserializes the from a JSON string nested in a root elment specified by . + + The JSON string. + The name of the root element to append when deserializing. + The deserialized XNode + + + + Deserializes the from a JSON string nested in a root elment specified by + and writes a .NET array attribute for collections. + + The JSON string. + The name of the root element to append when deserializing. + + A flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + The deserialized XNode + + + + The exception thrown when an error occurs during JSON serialization or deserialization. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Serializes and deserializes objects into and from the JSON format. + The enables you to control how objects are encoded into JSON. + + + + + Occurs when the errors during serialization and deserialization. + + + + + Gets or sets the used by the serializer when resolving references. + + + + + Gets or sets the used by the serializer when resolving type names. + + + + + Gets or sets the used by the serializer when writing trace messages. + + The trace writer. + + + + Gets or sets the equality comparer used by the serializer when comparing references. + + The equality comparer. + + + + Gets or sets how type name writing and reading is handled by the serializer. + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + + + + Gets or sets how a type name assembly is written and resolved by the serializer. + + The type name assembly format. + + + + Gets or sets how object references are preserved by the serializer. + + + + + Get or set how reference loops (e.g. a class referencing itself) is handled. + + + + + Get or set how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. + + + + + Get or set how null values are handled during serialization and deserialization. + + + + + Get or set how null default are handled during serialization and deserialization. + + + + + Gets or sets how objects are created during deserialization. + + The object creation handling. + + + + Gets or sets how constructors are used during deserialization. + + The constructor handling. + + + + Gets or sets how metadata properties are used during deserialization. + + The metadata properties handling. + + + + Gets a collection that will be used during serialization. + + Collection that will be used during serialization. + + + + Gets or sets the contract resolver used by the serializer when + serializing .NET objects to JSON and vice versa. + + + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling during serialization and deserialization. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written as JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Get or set how and values are formatted when writing JSON text, and the expected date format when reading JSON text. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Gets a value indicating whether there will be a check for additional JSON content after deserializing an object. + + + true if there will be a check for additional JSON content after deserializing an object; otherwise, false. + + + + + Initializes a new instance of the class. + + + + + Creates a new instance. + The will not use default settings + from . + + + A new instance. + The will not use default settings + from . + + + + + Creates a new instance using the specified . + The will not use default settings + from . + + The settings to be applied to the . + + A new instance using the specified . + The will not use default settings + from . + + + + + Creates a new instance. + The will use default settings + from . + + + A new instance. + The will use default settings + from . + + + + + Creates a new instance using the specified . + The will use default settings + from as well as the specified . + + The settings to be applied to the . + + A new instance using the specified . + The will use default settings + from as well as the specified . + + + + + Populates the JSON values onto the target object. + + The that contains the JSON structure to reader values from. + The target object to populate values onto. + + + + Populates the JSON values onto the target object. + + The that contains the JSON structure to reader values from. + The target object to populate values onto. + + + + Deserializes the JSON structure contained by the specified . + + The that contains the JSON structure to deserialize. + The being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The of object being deserialized. + The instance of being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The type of the object to deserialize. + The instance of being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The of object being deserialized. + The instance of being deserialized. + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + + + + Contains the JSON schema extension methods. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + + Determines whether the is valid. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + + true if the specified is valid; otherwise, false. + + + + + + Determines whether the is valid. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + When this method returns, contains any error messages generated while validating. + + true if the specified is valid; otherwise, false. + + + + + + Validates the specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + + + + + Validates the specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + The validation event handler. + + + + + Returns detailed information about the schema exception. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets the line number indicating where the error occurred. + + The line number indicating where the error occurred. + + + + Gets the line position indicating where the error occurred. + + The line position indicating where the error occurred. + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + + Resolves from an id. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets the loaded schemas. + + The loaded schemas. + + + + Initializes a new instance of the class. + + + + + Gets a for the specified reference. + + The id. + A for the specified reference. + + + + + Specifies undefined schema Id handling options for the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Do not infer a schema Id. + + + + + Use the .NET type name as the schema Id. + + + + + Use the assembly qualified .NET type name as the schema Id. + + + + + + Returns detailed information related to the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets the associated with the validation error. + + The JsonSchemaException associated with the validation error. + + + + Gets the path of the JSON location where the validation error occurred. + + The path of the JSON location where the validation error occurred. + + + + Gets the text description corresponding to the validation error. + + The text description. + + + + + Represents the callback method that will handle JSON schema validation events and the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + + An in-memory representation of a JSON Schema. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets the id. + + + + + Gets or sets the title. + + + + + Gets or sets whether the object is required. + + + + + Gets or sets whether the object is read only. + + + + + Gets or sets whether the object is visible to users. + + + + + Gets or sets whether the object is transient. + + + + + Gets or sets the description of the object. + + + + + Gets or sets the types of values allowed by the object. + + The type. + + + + Gets or sets the pattern. + + The pattern. + + + + Gets or sets the minimum length. + + The minimum length. + + + + Gets or sets the maximum length. + + The maximum length. + + + + Gets or sets a number that the value should be divisble by. + + A number that the value should be divisble by. + + + + Gets or sets the minimum. + + The minimum. + + + + Gets or sets the maximum. + + The maximum. + + + + Gets or sets a flag indicating whether the value can not equal the number defined by the "minimum" attribute. + + A flag indicating whether the value can not equal the number defined by the "minimum" attribute. + + + + Gets or sets a flag indicating whether the value can not equal the number defined by the "maximum" attribute. + + A flag indicating whether the value can not equal the number defined by the "maximum" attribute. + + + + Gets or sets the minimum number of items. + + The minimum number of items. + + + + Gets or sets the maximum number of items. + + The maximum number of items. + + + + Gets or sets the of items. + + The of items. + + + + Gets or sets a value indicating whether items in an array are validated using the instance at their array position from . + + + true if items are validated using their array position; otherwise, false. + + + + + Gets or sets the of additional items. + + The of additional items. + + + + Gets or sets a value indicating whether additional items are allowed. + + + true if additional items are allowed; otherwise, false. + + + + + Gets or sets whether the array items must be unique. + + + + + Gets or sets the of properties. + + The of properties. + + + + Gets or sets the of additional properties. + + The of additional properties. + + + + Gets or sets the pattern properties. + + The pattern properties. + + + + Gets or sets a value indicating whether additional properties are allowed. + + + true if additional properties are allowed; otherwise, false. + + + + + Gets or sets the required property if this property is present. + + The required property if this property is present. + + + + Gets or sets the a collection of valid enum values allowed. + + A collection of valid enum values allowed. + + + + Gets or sets disallowed types. + + The disallow types. + + + + Gets or sets the default value. + + The default value. + + + + Gets or sets the collection of that this schema extends. + + The collection of that this schema extends. + + + + Gets or sets the format. + + The format. + + + + Initializes a new instance of the class. + + + + + Reads a from the specified . + + The containing the JSON Schema to read. + The object representing the JSON Schema. + + + + Reads a from the specified . + + The containing the JSON Schema to read. + The to use when resolving schema references. + The object representing the JSON Schema. + + + + Load a from a string that contains schema JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + Parses the specified json. + + The json. + The resolver. + A populated from the string that contains JSON. + + + + Writes this schema to a . + + A into which this method will write. + + + + Writes this schema to a using the specified . + + A into which this method will write. + The resolver used. + + + + Returns a that represents the current . + + + A that represents the current . + + + + + + Generates a from a specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets how undefined schemas are handled by the serializer. + + + + + Gets or sets the contract resolver. + + The contract resolver. + + + + Generate a from the specified type. + + The type to generate a from. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + The used to resolve schema references. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + Specify whether the generated root will be nullable. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + The used to resolve schema references. + Specify whether the generated root will be nullable. + A generated from the specified type. + + + + + The value types allowed by the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + No type specified. + + + + + String type. + + + + + Float type. + + + + + Integer type. + + + + + Boolean type. + + + + + Object type. + + + + + Array type. + + + + + Null type. + + + + + Any type. + + + + + Specifies missing member handling options for the . + + + + + Ignore a missing member and do not attempt to deserialize it. + + + + + Throw a when a missing member is encountered during deserialization. + + + + + Specifies null value handling options for the . + + + + + + + + + Include null values when serializing and deserializing objects. + + + + + Ignore null values when serializing and deserializing objects. + + + + + Specifies reference loop handling options for the . + + + + + Throw a when a loop is encountered. + + + + + Ignore loop references and do not serialize. + + + + + Serialize loop references. + + + + + Specifies type name handling options for the . + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + + + + Do not include the .NET type name when serializing types. + + + + + Include the .NET type name when serializing into a JSON object structure. + + + + + Include the .NET type name when serializing into a JSON array structure. + + + + + Always include the .NET type name when serializing. + + + + + Include the .NET type name when the type of the object being serialized is not the same as its declared type. + + + + + Specifies the type of JSON token. + + + + + This is returned by the if a method has not been called. + + + + + An object start token. + + + + + An array start token. + + + + + A constructor start token. + + + + + An object property name. + + + + + A comment. + + + + + Raw JSON. + + + + + An integer. + + + + + A float. + + + + + A string. + + + + + A boolean. + + + + + A null token. + + + + + An undefined token. + + + + + An object end token. + + + + + An array end token. + + + + + A constructor end token. + + + + + A Date. + + + + + Byte data. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets a value indicating whether the underlying stream or + should be closed when the writer is closed. + + + true to close the underlying stream or when + the writer is closed; otherwise false. The default is true. + + + + + Gets the top. + + The top. + + + + Gets the state of the writer. + + + + + Gets the path of the writer. + + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling when writing JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written to JSON text. + + + + + Get or set how and values are formatting when writing JSON text. + + + + + Gets or sets the culture used when writing JSON. Defaults to . + + + + + Creates an instance of the JsonWriter class. + + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the end of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the end of an array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the end constructor. + + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + A flag to indicate whether the text should be escaped when it is written as a JSON property name. + + + + Writes the end of the current JSON object or array. + + + + + Writes the current token and its children. + + The to read the token from. + + + + Writes the current token. + + The to read the token from. + A flag indicating whether the current token's children should be written. + + + + Writes the token and its value. + + The to write. + + The value to write. + A value is only required for tokens that have an associated value, e.g. the property name for . + A null value can be passed to the method for token's that don't have a value, e.g. . + + + + Writes the token. + + The to write. + + + + Writes the specified end token. + + The end token to write. + + + + Writes indent characters. + + + + + Writes the JSON value delimiter. + + + + + Writes an indent space. + + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON without changing the writer's state. + + The raw JSON to write. + + + + Writes raw JSON where a value is expected and updates the writer's state. + + The raw JSON to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes out the given white space. + + The string of white space characters. + + + + Releases unmanaged and - optionally - managed resources + + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + Sets the state of the JsonWriter, + + The JsonToken being written. + The value being written. + + + + Specifies the state of the . + + + + + An exception has been thrown, which has left the in an invalid state. + You may call the method to put the in the Closed state. + Any other method calls results in an being thrown. + + + + + The method has been called. + + + + + An object is being written. + + + + + A array is being written. + + + + + A constructor is being written. + + + + + A property is being written. + + + + + A write method has not been called. + + + + diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs deleted file mode 100644 index 7a67237e9..000000000 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Collections; -#if WINDOWS_UWP -using System.Threading.Tasks; -#else -using System.Threading; -#endif - -namespace UnityGLTF -{ - /// - /// Creates a thread to run multithreaded operations on - /// - public class AsyncAction - { - private bool _workerThreadRunning = false; - private Exception _savedException; - - public IEnumerator RunOnWorkerThread(Action action) - { - _workerThreadRunning = true; - -#if WINDOWS_UWP - Task.Factory.StartNew(() => -#else - ThreadPool.QueueUserWorkItem((_) => -#endif - { - try - { - action(); - } - catch (Exception e) - { - _savedException = e; - } - - _workerThreadRunning = false; - }); - - yield return Wait(); - - if (_savedException != null) - { - throw _savedException; - } - } - - private IEnumerator Wait() - { - while (_workerThreadRunning) - { - yield return null; - } - } - } -} diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs.meta b/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs.meta deleted file mode 100644 index 80fa05d3c..000000000 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/AsyncAction.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: b8f897e9c6abac54f918fc1f558d6c42 -timeCreated: 1506549712 -licenseType: Pro -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/TaskExtensions.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Async/TaskExtensions.cs index a68020d22..9b811a584 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Async/TaskExtensions.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Async/TaskExtensions.cs @@ -1,4 +1,4 @@ -#if WINDOWS_UWP +#if UNITY_2017_1_OR_NEWER using System.Collections; using System.Threading.Tasks; @@ -15,4 +15,4 @@ public static IEnumerator AsCoroutine(this Task task) } } } -#endif +#endif \ No newline at end of file diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/AssetCache.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/AssetCache.cs index d07ed7037..c06aa1f0a 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/AssetCache.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/AssetCache.cs @@ -19,12 +19,12 @@ public class AssetCache : IDisposable /// Loaded raw texture data /// public Texture2D[] ImageCache { get; private set; } - + /// /// Textures to be used for assets. Textures from image cache with samplers applied /// public TextureCacheData[] TextureCache { get; private set; } - + /// /// Cache for materials to be applied to the meshes /// @@ -88,17 +88,17 @@ public void Dispose() foreach (BufferCacheData bufferCacheData in BufferCache) { if (bufferCacheData != null) - { - if (bufferCacheData.Stream != null) - { + { + if (bufferCacheData.Stream != null) + { #if !WINDOWS_UWP - bufferCacheData.Stream.Close(); + bufferCacheData.Stream.Close(); #else buffer.Stream.Dispose(); #endif } - bufferCacheData.Dispose(); + bufferCacheData.Dispose(); } } BufferCache = null; diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/BufferCacheData.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/BufferCacheData.cs index 8e0b2daf7..306f1ce79 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/BufferCacheData.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/BufferCacheData.cs @@ -4,7 +4,7 @@ namespace UnityGLTF.Cache { public class BufferCacheData : IDisposable { - public long ChunkOffset { get; set; } + public uint ChunkOffset { get; set; } public System.IO.Stream Stream { get; set; } public void Dispose() diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/RefCountedCacheData.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/RefCountedCacheData.cs index 4ff759c6c..16a9f6b88 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/RefCountedCacheData.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Cache/RefCountedCacheData.cs @@ -78,11 +78,14 @@ private void DestroyCachedData() // Destroy the cached meshes for (int i = 0; i < MeshCache.Count; i++) { - for (int j = 0; j < MeshCache[i].Length; j++) + if (MeshCache[i] != null) { - if (MeshCache[i][j] != null) + for (int j = 0; j < MeshCache[i].Length; j++) { - MeshCache[i][j].Unload(); + if (MeshCache[i][j] != null) + { + MeshCache[i][j].Unload(); + } } } } diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Editor.meta b/UnityGLTF/Assets/UnityGLTF/Scripts/Editor.meta index a415886ed..b26e1b5fe 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Editor.meta +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Editor.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 33a3aac936565284d8670075bd9d3bea +guid: 3d3936188a676ff4a9194add4e985b7c folderAsset: yes -timeCreated: 1506549705 +timeCreated: 1537993542 licenseType: Pro DefaultImporter: userData: diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Editor/GLTFExportMenu.cs.meta b/UnityGLTF/Assets/UnityGLTF/Scripts/Editor/GLTFExportMenu.cs.meta index e7f76ec6c..fb32fe62c 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Editor/GLTFExportMenu.cs.meta +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Editor/GLTFExportMenu.cs.meta @@ -1,6 +1,6 @@ fileFormatVersion: 2 -guid: 35cd456936631ca498f494972d58a592 -timeCreated: 1506549711 +guid: fd3d2ca14e7c1084888c77f15c69ae56 +timeCreated: 1537993549 licenseType: Pro MonoImporter: serializedVersion: 2 diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/Extensions/SchemaExtensions.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/Extensions/SchemaExtensions.cs index 7f5dec8b9..6d6c69bd1 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/Extensions/SchemaExtensions.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/Extensions/SchemaExtensions.cs @@ -1,502 +1,502 @@ -using GLTF; -using GLTF.Schema; -using UnityEngine; - -namespace UnityGLTF.Extensions -{ - public static class SchemaExtensions - { - // glTF matrix: column vectors, column-major storage, +Y up, +Z forward, -X right, right-handed - // unity matrix: column vectors, column-major storage, +Y up, +Z forward, +X right, left-handed - // multiply by a negative X scale to convert handedness - public static readonly GLTF.Math.Vector3 CoordinateSpaceConversionScale = new GLTF.Math.Vector3(-1, 1, 1); - public static bool CoordinateSpaceConversionRequiresHandednessFlip - { - get - { - return CoordinateSpaceConversionScale.X * CoordinateSpaceConversionScale.Y * CoordinateSpaceConversionScale.Z < 0.0f; - } - } - - public static readonly GLTF.Math.Vector4 TangentSpaceConversionScale = new GLTF.Math.Vector4(-1, 1, 1, -1); - - /// - /// Get the converted unity translation, rotation, and scale from a gltf node - /// - /// gltf node - /// unity translation vector - /// unity rotation quaternion - /// unity scale vector - public static void GetUnityTRSProperties(this Node node, out Vector3 position, out Quaternion rotation, - out Vector3 scale) - { - if (!node.UseTRS) - { - Matrix4x4 unityMat = node.Matrix.ToUnityMatrix4x4Convert(); - unityMat.GetTRSProperties(out position, out rotation, out scale); - } - else - { - position = node.Translation.ToUnityVector3Convert(); - rotation = node.Rotation.ToUnityQuaternionConvert(); - scale = node.Scale.ToUnityVector3Raw(); - } - } - - /// - /// Set a gltf node's converted translation, rotation, and scale from a unity transform - /// - /// gltf node to modify - /// unity transform to convert - public static void SetUnityTransform(this Node node, Transform transform) - { - node.Translation = transform.localPosition.ToGltfVector3Convert(); - node.Rotation = transform.localRotation.ToGltfQuaternionConvert(); - node.Scale = transform.localScale.ToGltfVector3Raw(); - } - - // todo: move to utility class - /// - /// Get unity translation, rotation, and scale from a unity matrix - /// - /// unity matrix to get properties from - /// unity translation vector - /// unity rotation quaternion - /// unity scale vector - public static void GetTRSProperties(this Matrix4x4 mat, out Vector3 position, out Quaternion rotation, - out Vector3 scale) - { - position = mat.GetColumn(3); - - Vector3 x = mat.GetColumn(0); - Vector3 y = mat.GetColumn(1); - Vector3 z = mat.GetColumn(2); - Vector3 calculatedZ = Vector3.Cross(x, y); - bool mirrored = Vector3.Dot(calculatedZ, z) < 0.0f; - - scale = new Vector3(x.magnitude * (mirrored ? -1.0f : 1.0f), y.magnitude, z.magnitude); - - rotation = Quaternion.LookRotation(mat.GetColumn(2), mat.GetColumn(1)); - } - - /// - /// Get converted unity translation, rotation, and scale from a gltf matrix - /// - /// gltf matrix to get and convert properties from - /// unity translation vector - /// unity rotation quaternion - /// unity scale vector - public static void GetTRSProperties(this GLTF.Math.Matrix4x4 gltfMat, out Vector3 position, out Quaternion rotation, - out Vector3 scale) - { - gltfMat.ToUnityMatrix4x4Convert().GetTRSProperties(out position, out rotation, out scale); - } - - /// - /// Get a gltf column vector from a gltf matrix - /// - /// gltf matrix - /// the specified column vector from the matrix - /// - public static GLTF.Math.Vector4 GetColumn(this GLTF.Math.Matrix4x4 mat, uint columnNum) - { - switch (columnNum) - { - case 0: - { - return new GLTF.Math.Vector4(mat.M11, mat.M21, mat.M31, mat.M41); - } - case 1: - { - return new GLTF.Math.Vector4(mat.M12, mat.M22, mat.M32, mat.M42); - } - case 2: - { - return new GLTF.Math.Vector4(mat.M13, mat.M23, mat.M33, mat.M43); - } - case 3: - { - return new GLTF.Math.Vector4(mat.M14, mat.M24, mat.M34, mat.M44); - } - default: - throw new System.Exception("column num is out of bounds"); - } - } - - /// - /// Convert gltf quaternion to a unity quaternion - /// - /// gltf quaternion - /// unity quaternion - public static Quaternion ToUnityQuaternionConvert(this GLTF.Math.Quaternion gltfQuat) - { - // get raw matrix conversion (gltf matrix stored in a unity matrix for easier math) - Vector3 origAxis = new Vector3(gltfQuat.X, gltfQuat.Y, gltfQuat.Z); - float axisFlipScale = CoordinateSpaceConversionRequiresHandednessFlip ? -1.0f : 1.0f; - Vector3 newAxis = axisFlipScale * Vector3.Scale(origAxis, CoordinateSpaceConversionScale.ToUnityVector3Raw()); - - // then put the quaternion back together and return it - return new Quaternion(newAxis.x, newAxis.y, newAxis.z, gltfQuat.W); - } - - /// - /// Convert unity quaternion to a gltf quaternion - /// - /// unity quaternion - /// gltf quaternion - public static GLTF.Math.Quaternion ToGltfQuaternionConvert(this Quaternion unityQuat) - { - // get the original axis and apply conversion scale as well as potential rotation axis flip - Vector3 origAxis = new Vector3(unityQuat.x, unityQuat.y, unityQuat.z); - float axisFlipScale = CoordinateSpaceConversionRequiresHandednessFlip ? -1.0f : 1.0f; - Vector3 newAxis = axisFlipScale * Vector3.Scale(origAxis, CoordinateSpaceConversionScale.ToUnityVector3Raw()); - - // then put the quaternion back together and return it - return new GLTF.Math.Quaternion(newAxis.x, newAxis.y, newAxis.z, unityQuat.w); - } - - /// - /// Convert gltf matrix to a unity matrix - /// - /// gltf matrix - /// unity matrix - public static Matrix4x4 ToUnityMatrix4x4Convert(this GLTF.Math.Matrix4x4 gltfMat) - { - Matrix4x4 rawUnityMat = gltfMat.ToUnityMatrix4x4Raw(); - Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); - Matrix4x4 convert = Matrix4x4.Scale(coordinateSpaceConversionScale); - Matrix4x4 unityMat = convert * rawUnityMat * convert; - return unityMat; - } - - /// - /// Convert gltf matrix to a unity matrix - /// - /// unity matrix - /// gltf matrix - public static GLTF.Math.Matrix4x4 ToGltfMatrix4x4Convert(this Matrix4x4 unityMat) - { - Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); - Matrix4x4 convert = Matrix4x4.Scale(coordinateSpaceConversionScale); - GLTF.Math.Matrix4x4 gltfMat = (convert * unityMat * convert).ToGltfMatrix4x4Raw(); - return gltfMat; - } - - /// - /// Convert gltf Vector3 to unity Vector3 - /// - /// gltf vector3 - /// unity vector3 - public static Vector3 ToUnityVector3Convert(this GLTF.Math.Vector3 gltfVec3) - { - Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); - Vector3 unityVec3 = Vector3.Scale(gltfVec3.ToUnityVector3Raw(), coordinateSpaceConversionScale); - return unityVec3; - } - - /// - /// Convert unity Vector3 to gltf Vector3 - /// - /// unity Vector3 - /// gltf Vector3 - public static GLTF.Math.Vector3 ToGltfVector3Convert(this Vector3 unityVec3) - { - Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); - GLTF.Math.Vector3 gltfVec3 = Vector3.Scale(unityVec3, coordinateSpaceConversionScale).ToGltfVector3Raw(); - return gltfVec3; - } - - public static GLTF.Math.Vector3 ToGltfVector3Raw(this Vector3 unityVec3) - { - GLTF.Math.Vector3 gltfVec3 = new GLTF.Math.Vector3(unityVec3.x, unityVec3.y, unityVec3.z); - return gltfVec3; - } - - public static GLTF.Math.Vector4 ToGltfVector4Raw(this Vector4 unityVec4) - { - GLTF.Math.Vector4 gltfVec4 = new GLTF.Math.Vector4(unityVec4.x, unityVec4.y, unityVec4.z, unityVec4.w); - return gltfVec4; - } - - public static Matrix4x4 ToUnityMatrix4x4Raw(this GLTF.Math.Matrix4x4 gltfMat) - { - Vector4 rawUnityCol0 = gltfMat.GetColumn(0).ToUnityVector4Raw(); - Vector4 rawUnityCol1 = gltfMat.GetColumn(1).ToUnityVector4Raw(); - Vector4 rawUnityCol2 = gltfMat.GetColumn(2).ToUnityVector4Raw(); - Vector4 rawUnityCol3 = gltfMat.GetColumn(3).ToUnityVector4Raw(); - Matrix4x4 rawUnityMat = new UnityEngine.Matrix4x4(); - rawUnityMat.SetColumn(0, rawUnityCol0); - rawUnityMat.SetColumn(1, rawUnityCol1); - rawUnityMat.SetColumn(2, rawUnityCol2); - rawUnityMat.SetColumn(3, rawUnityCol3); - - return rawUnityMat; - } - - public static GLTF.Math.Matrix4x4 ToGltfMatrix4x4Raw(this Matrix4x4 unityMat) - { - GLTF.Math.Vector4 c0 = unityMat.GetColumn(0).ToGltfVector4Raw(); - GLTF.Math.Vector4 c1 = unityMat.GetColumn(1).ToGltfVector4Raw(); - GLTF.Math.Vector4 c2 = unityMat.GetColumn(2).ToGltfVector4Raw(); - GLTF.Math.Vector4 c3 = unityMat.GetColumn(3).ToGltfVector4Raw(); - GLTF.Math.Matrix4x4 rawGltfMat = new GLTF.Math.Matrix4x4(c0.X, c0.Y, c0.Z, c0.W, c1.X, c1.Y, c1.Z, c1.W, c2.X, c2.Y, c2.Z, c2.W, c3.X, c3.Y, c3.Z, c3.W); - return rawGltfMat; - } - - public static Vector2 ToUnityVector2Raw(this GLTF.Math.Vector2 vec2) - { - return new Vector2(vec2.X, vec2.Y); - } - - public static Vector2[] ToUnityVector2Raw(this GLTF.Math.Vector2[] inVecArr) - { - Vector2[] outVecArr = new Vector2[inVecArr.Length]; - for (int i = 0; i < inVecArr.Length; ++i) - { - outVecArr[i] = inVecArr[i].ToUnityVector2Raw(); - } - return outVecArr; - } - - public static Vector3 ToUnityVector3Raw(this GLTF.Math.Vector3 vec3) - { - return new Vector3(vec3.X, vec3.Y, vec3.Z); - } - - public static Vector3[] ToUnityVector3Raw(this GLTF.Math.Vector3[] inVecArr) - { - Vector3[] outVecArr = new Vector3[inVecArr.Length]; - for (int i = 0; i < inVecArr.Length; ++i) - { - outVecArr[i] = inVecArr[i].ToUnityVector3Raw(); - } - return outVecArr; - } - - public static Vector4 ToUnityVector4Raw(this GLTF.Math.Vector4 vec4) - { - return new Vector4(vec4.X, vec4.Y, vec4.Z, vec4.W); - } - - public static Vector4[] ToUnityVector4Raw(this GLTF.Math.Vector4[] inVecArr) - { - Vector4[] outVecArr = new Vector4[inVecArr.Length]; - for (int i = 0; i < inVecArr.Length; ++i) - { - outVecArr[i] = inVecArr[i].ToUnityVector4Raw(); - } - return outVecArr; - } - - public static UnityEngine.Color ToUnityColorRaw(this GLTF.Math.Color color) - { - return new UnityEngine.Color(color.R, color.G, color.B, color.A); - } - - public static GLTF.Math.Color ToNumericsColorRaw(this UnityEngine.Color color) - { - return new GLTF.Math.Color(color.r, color.g, color.b, color.a); - } - - public static UnityEngine.Color[] ToUnityColorRaw(this GLTF.Math.Color[] inColorArr) - { - UnityEngine.Color[] outColorArr = new UnityEngine.Color[inColorArr.Length]; - for (int i = 0; i < inColorArr.Length; ++i) - { - outColorArr[i] = inColorArr[i].ToUnityColorRaw(); - } - return outColorArr; - } - - public static int[] ToIntArrayRaw(this uint[] uintArr) - { - int[] intArr = new int[uintArr.Length]; - for (int i = 0; i < uintArr.Length; ++i) - { - uint uintVal = uintArr[i]; - Debug.Assert(uintVal <= int.MaxValue); - intArr[i] = (int)uintVal; - } - - return intArr; - } - - public static GLTF.Math.Quaternion ToGltfQuaternionRaw(this Quaternion unityQuat) - { - return new GLTF.Math.Quaternion(unityQuat.x, unityQuat.y, unityQuat.z, unityQuat.w); - } - - public static Quaternion ToUnityQuaternionRaw(this GLTF.Math.Quaternion quaternion) - { - return new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W); - } - - /// - /// Flips the V component of the UV (1-V) to put from glTF into Unity space - /// - /// The attribute accessor to modify - public static void FlipTexCoordArrayV(ref AttributeAccessor attributeAccessor) - { - for (var i = 0; i < attributeAccessor.AccessorContent.AsVec2s.Length; i++) - { - attributeAccessor.AccessorContent.AsVec2s[i].Y = 1.0f - attributeAccessor.AccessorContent.AsVec2s[i].Y; - } - } - - /// - /// Flip the V component of the UV (1-V) - /// - /// The array to copy from and modify - /// Copied Vector2 with coordinates in glTF space - public static UnityEngine.Vector2[] FlipTexCoordArrayVAndCopy(UnityEngine.Vector2[] array) - { - var returnArray = new UnityEngine.Vector2[array.Length]; - - for (var i = 0; i < array.Length; i++) - { - returnArray[i].x = array[i].x; - returnArray[i].y = 1.0f - array[i].y; - } - - return returnArray; - } - - /// - /// Converts vector3 to specified coordinate space - /// - /// The attribute accessor to modify - /// The coordinate space to move into - public static void ConvertVector3CoordinateSpace(ref AttributeAccessor attributeAccessor, GLTF.Math.Vector3 coordinateSpaceCoordinateScale) - { - for (int i = 0; i < attributeAccessor.AccessorContent.AsVertices.Length; i++) - { - attributeAccessor.AccessorContent.AsVertices[i].X *= coordinateSpaceCoordinateScale.X; - attributeAccessor.AccessorContent.AsVertices[i].Y *= coordinateSpaceCoordinateScale.Y; - attributeAccessor.AccessorContent.AsVertices[i].Z *= coordinateSpaceCoordinateScale.Z; - } - } - - /// - /// Converts and copies based on the specified coordinate scale - /// - /// The array to convert and copy - /// The specified coordinate space - /// The copied and converted coordinate space - public static UnityEngine.Vector3[] ConvertVector3CoordinateSpaceAndCopy(Vector3[] array, GLTF.Math.Vector3 coordinateSpaceCoordinateScale) - { - var returnArray = new UnityEngine.Vector3[array.Length]; - - for (int i = 0; i < array.Length; i++) - { - returnArray[i].x = array[i].x * coordinateSpaceCoordinateScale.X; - returnArray[i].y = array[i].y * coordinateSpaceCoordinateScale.Y; - returnArray[i].z = array[i].z * coordinateSpaceCoordinateScale.Z; - } - - return returnArray; - } - - /// - /// Converts vector4 to specified coordinate space - /// - /// The attribute accessor to modify - /// The coordinate space to move into - public static void ConvertVector4CoordinateSpace(ref AttributeAccessor attributeAccessor, GLTF.Math.Vector4 coordinateSpaceCoordinateScale) - { - for (int i = 0; i < attributeAccessor.AccessorContent.AsVec4s.Length; i++) - { - attributeAccessor.AccessorContent.AsVec4s[i].X *= coordinateSpaceCoordinateScale.X; - attributeAccessor.AccessorContent.AsVec4s[i].Y *= coordinateSpaceCoordinateScale.Y; - attributeAccessor.AccessorContent.AsVec4s[i].Z *= coordinateSpaceCoordinateScale.Z; - attributeAccessor.AccessorContent.AsVec4s[i].W *= coordinateSpaceCoordinateScale.W; - } - } - - /// - /// Converts and copies based on the specified coordinate scale - /// - /// The array to convert and copy - /// The specified coordinate space - /// The copied and converted coordinate space - public static Vector4[] ConvertVector4CoordinateSpaceAndCopy(Vector4[] array, GLTF.Math.Vector4 coordinateSpaceCoordinateScale) - { - var returnArray = new Vector4[array.Length]; - - for (var i = 0; i < array.Length; i++) - { - returnArray[i].x = array[i].x * coordinateSpaceCoordinateScale.X; - returnArray[i].y = array[i].y * coordinateSpaceCoordinateScale.Y; - returnArray[i].z = array[i].z * coordinateSpaceCoordinateScale.Z; - returnArray[i].w = array[i].w * coordinateSpaceCoordinateScale.W; - } - - return returnArray; - } - - /// - /// Rewinds the indicies into Unity coordinate space from glTF space - /// - /// The attribute accessor to modify - public static void FlipFaces(ref AttributeAccessor attributeAccessor) - { - for (int i = 0; i < attributeAccessor.AccessorContent.AsTriangles.Length; i += 3) - { - uint temp = attributeAccessor.AccessorContent.AsUInts[i]; - attributeAccessor.AccessorContent.AsUInts[i] = attributeAccessor.AccessorContent.AsUInts[i + 2]; - attributeAccessor.AccessorContent.AsUInts[i + 2] = temp; - } - } - - /// - /// Rewinds the indices from glTF space to Unity space - /// - /// The indices to copy and modify - /// Indices in glTF space that are copied - public static int[] FlipFacesAndCopy(int[] triangles) - { - int[] returnArr = new int[triangles.Length]; - for (int i = 0; i < triangles.Length; i += 3) - { - int temp = triangles[i]; - returnArr[i] = triangles[i + 2]; - returnArr[i + 1] = triangles[i + 1]; - returnArr[i + 2] = temp; - } - - return returnArr; - } - - public static Matrix4x4 ToUnityMatrix4x4(this GLTF.Math.Matrix4x4 matrix) - { - return new Matrix4x4() - { - m00 = matrix.M11, - m01 = matrix.M12, - m02 = matrix.M13, - m03 = matrix.M14, - m10 = matrix.M21, - m11 = matrix.M22, - m12 = matrix.M23, - m13 = matrix.M24, - m20 = matrix.M31, - m21 = matrix.M32, - m22 = matrix.M33, - m23 = matrix.M34, - m30 = matrix.M41, - m31 = matrix.M42, - m32 = matrix.M43, - m33 = matrix.M44 - }; - } - - public static Matrix4x4[] ToUnityMatrix4x4(this GLTF.Math.Matrix4x4[] inMatrixArr) - { - Matrix4x4[] outMatrixArr = new Matrix4x4[inMatrixArr.Length]; - for (int i = 0; i < inMatrixArr.Length; ++i) - { - outMatrixArr[i] = inMatrixArr[i].ToUnityMatrix4x4(); - } - return outMatrixArr; - } - } -} +using GLTF; +using GLTF.Schema; +using UnityEngine; + +namespace UnityGLTF.Extensions +{ + public static class SchemaExtensions + { + // glTF matrix: column vectors, column-major storage, +Y up, +Z forward, -X right, right-handed + // unity matrix: column vectors, column-major storage, +Y up, +Z forward, +X right, left-handed + // multiply by a negative X scale to convert handedness + public static readonly GLTF.Math.Vector3 CoordinateSpaceConversionScale = new GLTF.Math.Vector3(-1, 1, 1); + public static bool CoordinateSpaceConversionRequiresHandednessFlip + { + get + { + return CoordinateSpaceConversionScale.X * CoordinateSpaceConversionScale.Y * CoordinateSpaceConversionScale.Z < 0.0f; + } + } + + public static readonly GLTF.Math.Vector4 TangentSpaceConversionScale = new GLTF.Math.Vector4(-1, 1, 1, -1); + + /// + /// Get the converted unity translation, rotation, and scale from a gltf node + /// + /// gltf node + /// unity translation vector + /// unity rotation quaternion + /// unity scale vector + public static void GetUnityTRSProperties(this Node node, out Vector3 position, out Quaternion rotation, + out Vector3 scale) + { + if (!node.UseTRS) + { + Matrix4x4 unityMat = node.Matrix.ToUnityMatrix4x4Convert(); + unityMat.GetTRSProperties(out position, out rotation, out scale); + } + else + { + position = node.Translation.ToUnityVector3Convert(); + rotation = node.Rotation.ToUnityQuaternionConvert(); + scale = node.Scale.ToUnityVector3Raw(); + } + } + + /// + /// Set a gltf node's converted translation, rotation, and scale from a unity transform + /// + /// gltf node to modify + /// unity transform to convert + public static void SetUnityTransform(this Node node, Transform transform) + { + node.Translation = transform.localPosition.ToGltfVector3Convert(); + node.Rotation = transform.localRotation.ToGltfQuaternionConvert(); + node.Scale = transform.localScale.ToGltfVector3Raw(); + } + + // todo: move to utility class + /// + /// Get unity translation, rotation, and scale from a unity matrix + /// + /// unity matrix to get properties from + /// unity translation vector + /// unity rotation quaternion + /// unity scale vector + public static void GetTRSProperties(this Matrix4x4 mat, out Vector3 position, out Quaternion rotation, + out Vector3 scale) + { + position = mat.GetColumn(3); + + Vector3 x = mat.GetColumn(0); + Vector3 y = mat.GetColumn(1); + Vector3 z = mat.GetColumn(2); + Vector3 calculatedZ = Vector3.Cross(x, y); + bool mirrored = Vector3.Dot(calculatedZ, z) < 0.0f; + + scale = new Vector3(x.magnitude * (mirrored ? -1.0f : 1.0f), y.magnitude, z.magnitude); + + rotation = Quaternion.LookRotation(mat.GetColumn(2), mat.GetColumn(1)); + } + + /// + /// Get converted unity translation, rotation, and scale from a gltf matrix + /// + /// gltf matrix to get and convert properties from + /// unity translation vector + /// unity rotation quaternion + /// unity scale vector + public static void GetTRSProperties(this GLTF.Math.Matrix4x4 gltfMat, out Vector3 position, out Quaternion rotation, + out Vector3 scale) + { + gltfMat.ToUnityMatrix4x4Convert().GetTRSProperties(out position, out rotation, out scale); + } + + /// + /// Get a gltf column vector from a gltf matrix + /// + /// gltf matrix + /// the specified column vector from the matrix + /// + public static GLTF.Math.Vector4 GetColumn(this GLTF.Math.Matrix4x4 mat, uint columnNum) + { + switch (columnNum) + { + case 0: + { + return new GLTF.Math.Vector4(mat.M11, mat.M21, mat.M31, mat.M41); + } + case 1: + { + return new GLTF.Math.Vector4(mat.M12, mat.M22, mat.M32, mat.M42); + } + case 2: + { + return new GLTF.Math.Vector4(mat.M13, mat.M23, mat.M33, mat.M43); + } + case 3: + { + return new GLTF.Math.Vector4(mat.M14, mat.M24, mat.M34, mat.M44); + } + default: + throw new System.Exception("column num is out of bounds"); + } + } + + /// + /// Convert gltf quaternion to a unity quaternion + /// + /// gltf quaternion + /// unity quaternion + public static Quaternion ToUnityQuaternionConvert(this GLTF.Math.Quaternion gltfQuat) + { + // get raw matrix conversion (gltf matrix stored in a unity matrix for easier math) + Vector3 origAxis = new Vector3(gltfQuat.X, gltfQuat.Y, gltfQuat.Z); + float axisFlipScale = CoordinateSpaceConversionRequiresHandednessFlip ? -1.0f : 1.0f; + Vector3 newAxis = axisFlipScale * Vector3.Scale(origAxis, CoordinateSpaceConversionScale.ToUnityVector3Raw()); + + // then put the quaternion back together and return it + return new Quaternion(newAxis.x, newAxis.y, newAxis.z, gltfQuat.W); + } + + /// + /// Convert unity quaternion to a gltf quaternion + /// + /// unity quaternion + /// gltf quaternion + public static GLTF.Math.Quaternion ToGltfQuaternionConvert(this Quaternion unityQuat) + { + // get the original axis and apply conversion scale as well as potential rotation axis flip + Vector3 origAxis = new Vector3(unityQuat.x, unityQuat.y, unityQuat.z); + float axisFlipScale = CoordinateSpaceConversionRequiresHandednessFlip ? -1.0f : 1.0f; + Vector3 newAxis = axisFlipScale * Vector3.Scale(origAxis, CoordinateSpaceConversionScale.ToUnityVector3Raw()); + + // then put the quaternion back together and return it + return new GLTF.Math.Quaternion(newAxis.x, newAxis.y, newAxis.z, unityQuat.w); + } + + /// + /// Convert gltf matrix to a unity matrix + /// + /// gltf matrix + /// unity matrix + public static Matrix4x4 ToUnityMatrix4x4Convert(this GLTF.Math.Matrix4x4 gltfMat) + { + Matrix4x4 rawUnityMat = gltfMat.ToUnityMatrix4x4Raw(); + Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); + Matrix4x4 convert = Matrix4x4.Scale(coordinateSpaceConversionScale); + Matrix4x4 unityMat = convert * rawUnityMat * convert; + return unityMat; + } + + /// + /// Convert gltf matrix to a unity matrix + /// + /// unity matrix + /// gltf matrix + public static GLTF.Math.Matrix4x4 ToGltfMatrix4x4Convert(this Matrix4x4 unityMat) + { + Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); + Matrix4x4 convert = Matrix4x4.Scale(coordinateSpaceConversionScale); + GLTF.Math.Matrix4x4 gltfMat = (convert * unityMat * convert).ToGltfMatrix4x4Raw(); + return gltfMat; + } + + /// + /// Convert gltf Vector3 to unity Vector3 + /// + /// gltf vector3 + /// unity vector3 + public static Vector3 ToUnityVector3Convert(this GLTF.Math.Vector3 gltfVec3) + { + Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); + Vector3 unityVec3 = Vector3.Scale(gltfVec3.ToUnityVector3Raw(), coordinateSpaceConversionScale); + return unityVec3; + } + + /// + /// Convert unity Vector3 to gltf Vector3 + /// + /// unity Vector3 + /// gltf Vector3 + public static GLTF.Math.Vector3 ToGltfVector3Convert(this Vector3 unityVec3) + { + Vector3 coordinateSpaceConversionScale = CoordinateSpaceConversionScale.ToUnityVector3Raw(); + GLTF.Math.Vector3 gltfVec3 = Vector3.Scale(unityVec3, coordinateSpaceConversionScale).ToGltfVector3Raw(); + return gltfVec3; + } + + public static GLTF.Math.Vector3 ToGltfVector3Raw(this Vector3 unityVec3) + { + GLTF.Math.Vector3 gltfVec3 = new GLTF.Math.Vector3(unityVec3.x, unityVec3.y, unityVec3.z); + return gltfVec3; + } + + public static GLTF.Math.Vector4 ToGltfVector4Raw(this Vector4 unityVec4) + { + GLTF.Math.Vector4 gltfVec4 = new GLTF.Math.Vector4(unityVec4.x, unityVec4.y, unityVec4.z, unityVec4.w); + return gltfVec4; + } + + public static Matrix4x4 ToUnityMatrix4x4Raw(this GLTF.Math.Matrix4x4 gltfMat) + { + Vector4 rawUnityCol0 = gltfMat.GetColumn(0).ToUnityVector4Raw(); + Vector4 rawUnityCol1 = gltfMat.GetColumn(1).ToUnityVector4Raw(); + Vector4 rawUnityCol2 = gltfMat.GetColumn(2).ToUnityVector4Raw(); + Vector4 rawUnityCol3 = gltfMat.GetColumn(3).ToUnityVector4Raw(); + Matrix4x4 rawUnityMat = new UnityEngine.Matrix4x4(); + rawUnityMat.SetColumn(0, rawUnityCol0); + rawUnityMat.SetColumn(1, rawUnityCol1); + rawUnityMat.SetColumn(2, rawUnityCol2); + rawUnityMat.SetColumn(3, rawUnityCol3); + + return rawUnityMat; + } + + public static GLTF.Math.Matrix4x4 ToGltfMatrix4x4Raw(this Matrix4x4 unityMat) + { + GLTF.Math.Vector4 c0 = unityMat.GetColumn(0).ToGltfVector4Raw(); + GLTF.Math.Vector4 c1 = unityMat.GetColumn(1).ToGltfVector4Raw(); + GLTF.Math.Vector4 c2 = unityMat.GetColumn(2).ToGltfVector4Raw(); + GLTF.Math.Vector4 c3 = unityMat.GetColumn(3).ToGltfVector4Raw(); + GLTF.Math.Matrix4x4 rawGltfMat = new GLTF.Math.Matrix4x4(c0.X, c0.Y, c0.Z, c0.W, c1.X, c1.Y, c1.Z, c1.W, c2.X, c2.Y, c2.Z, c2.W, c3.X, c3.Y, c3.Z, c3.W); + return rawGltfMat; + } + + public static Vector2 ToUnityVector2Raw(this GLTF.Math.Vector2 vec2) + { + return new Vector2(vec2.X, vec2.Y); + } + + public static Vector2[] ToUnityVector2Raw(this GLTF.Math.Vector2[] inVecArr) + { + Vector2[] outVecArr = new Vector2[inVecArr.Length]; + for (int i = 0; i < inVecArr.Length; ++i) + { + outVecArr[i] = inVecArr[i].ToUnityVector2Raw(); + } + return outVecArr; + } + + public static Vector3 ToUnityVector3Raw(this GLTF.Math.Vector3 vec3) + { + return new Vector3(vec3.X, vec3.Y, vec3.Z); + } + + public static Vector3[] ToUnityVector3Raw(this GLTF.Math.Vector3[] inVecArr) + { + Vector3[] outVecArr = new Vector3[inVecArr.Length]; + for (int i = 0; i < inVecArr.Length; ++i) + { + outVecArr[i] = inVecArr[i].ToUnityVector3Raw(); + } + return outVecArr; + } + + public static Vector4 ToUnityVector4Raw(this GLTF.Math.Vector4 vec4) + { + return new Vector4(vec4.X, vec4.Y, vec4.Z, vec4.W); + } + + public static Vector4[] ToUnityVector4Raw(this GLTF.Math.Vector4[] inVecArr) + { + Vector4[] outVecArr = new Vector4[inVecArr.Length]; + for (int i = 0; i < inVecArr.Length; ++i) + { + outVecArr[i] = inVecArr[i].ToUnityVector4Raw(); + } + return outVecArr; + } + + public static UnityEngine.Color ToUnityColorRaw(this GLTF.Math.Color color) + { + return new UnityEngine.Color(color.R, color.G, color.B, color.A); + } + + public static GLTF.Math.Color ToNumericsColorRaw(this UnityEngine.Color color) + { + return new GLTF.Math.Color(color.r, color.g, color.b, color.a); + } + + public static UnityEngine.Color[] ToUnityColorRaw(this GLTF.Math.Color[] inColorArr) + { + UnityEngine.Color[] outColorArr = new UnityEngine.Color[inColorArr.Length]; + for (int i = 0; i < inColorArr.Length; ++i) + { + outColorArr[i] = inColorArr[i].ToUnityColorRaw(); + } + return outColorArr; + } + + public static int[] ToIntArrayRaw(this uint[] uintArr) + { + int[] intArr = new int[uintArr.Length]; + for (int i = 0; i < uintArr.Length; ++i) + { + uint uintVal = uintArr[i]; + Debug.Assert(uintVal <= int.MaxValue); + intArr[i] = (int)uintVal; + } + + return intArr; + } + + public static GLTF.Math.Quaternion ToGltfQuaternionRaw(this Quaternion unityQuat) + { + return new GLTF.Math.Quaternion(unityQuat.x, unityQuat.y, unityQuat.z, unityQuat.w); + } + + public static Quaternion ToUnityQuaternionRaw(this GLTF.Math.Quaternion quaternion) + { + return new Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W); + } + + /// + /// Flips the V component of the UV (1-V) to put from glTF into Unity space + /// + /// The attribute accessor to modify + public static void FlipTexCoordArrayV(ref AttributeAccessor attributeAccessor) + { + for (var i = 0; i < attributeAccessor.AccessorContent.AsVec2s.Length; i++) + { + attributeAccessor.AccessorContent.AsVec2s[i].Y = 1.0f - attributeAccessor.AccessorContent.AsVec2s[i].Y; + } + } + + /// + /// Flip the V component of the UV (1-V) + /// + /// The array to copy from and modify + /// Copied Vector2 with coordinates in glTF space + public static UnityEngine.Vector2[] FlipTexCoordArrayVAndCopy(UnityEngine.Vector2[] array) + { + var returnArray = new UnityEngine.Vector2[array.Length]; + + for (var i = 0; i < array.Length; i++) + { + returnArray[i].x = array[i].x; + returnArray[i].y = 1.0f - array[i].y; + } + + return returnArray; + } + + /// + /// Converts vector3 to specified coordinate space + /// + /// The attribute accessor to modify + /// The coordinate space to move into + public static void ConvertVector3CoordinateSpace(ref AttributeAccessor attributeAccessor, GLTF.Math.Vector3 coordinateSpaceCoordinateScale) + { + for (int i = 0; i < attributeAccessor.AccessorContent.AsVertices.Length; i++) + { + attributeAccessor.AccessorContent.AsVertices[i].X *= coordinateSpaceCoordinateScale.X; + attributeAccessor.AccessorContent.AsVertices[i].Y *= coordinateSpaceCoordinateScale.Y; + attributeAccessor.AccessorContent.AsVertices[i].Z *= coordinateSpaceCoordinateScale.Z; + } + } + + /// + /// Converts and copies based on the specified coordinate scale + /// + /// The array to convert and copy + /// The specified coordinate space + /// The copied and converted coordinate space + public static UnityEngine.Vector3[] ConvertVector3CoordinateSpaceAndCopy(Vector3[] array, GLTF.Math.Vector3 coordinateSpaceCoordinateScale) + { + var returnArray = new UnityEngine.Vector3[array.Length]; + + for (int i = 0; i < array.Length; i++) + { + returnArray[i].x = array[i].x * coordinateSpaceCoordinateScale.X; + returnArray[i].y = array[i].y * coordinateSpaceCoordinateScale.Y; + returnArray[i].z = array[i].z * coordinateSpaceCoordinateScale.Z; + } + + return returnArray; + } + + /// + /// Converts vector4 to specified coordinate space + /// + /// The attribute accessor to modify + /// The coordinate space to move into + public static void ConvertVector4CoordinateSpace(ref AttributeAccessor attributeAccessor, GLTF.Math.Vector4 coordinateSpaceCoordinateScale) + { + for (int i = 0; i < attributeAccessor.AccessorContent.AsVec4s.Length; i++) + { + attributeAccessor.AccessorContent.AsVec4s[i].X *= coordinateSpaceCoordinateScale.X; + attributeAccessor.AccessorContent.AsVec4s[i].Y *= coordinateSpaceCoordinateScale.Y; + attributeAccessor.AccessorContent.AsVec4s[i].Z *= coordinateSpaceCoordinateScale.Z; + attributeAccessor.AccessorContent.AsVec4s[i].W *= coordinateSpaceCoordinateScale.W; + } + } + + /// + /// Converts and copies based on the specified coordinate scale + /// + /// The array to convert and copy + /// The specified coordinate space + /// The copied and converted coordinate space + public static Vector4[] ConvertVector4CoordinateSpaceAndCopy(Vector4[] array, GLTF.Math.Vector4 coordinateSpaceCoordinateScale) + { + var returnArray = new Vector4[array.Length]; + + for (var i = 0; i < array.Length; i++) + { + returnArray[i].x = array[i].x * coordinateSpaceCoordinateScale.X; + returnArray[i].y = array[i].y * coordinateSpaceCoordinateScale.Y; + returnArray[i].z = array[i].z * coordinateSpaceCoordinateScale.Z; + returnArray[i].w = array[i].w * coordinateSpaceCoordinateScale.W; + } + + return returnArray; + } + + /// + /// Rewinds the indicies into Unity coordinate space from glTF space + /// + /// The attribute accessor to modify + public static void FlipFaces(ref AttributeAccessor attributeAccessor) + { + for (int i = 0; i < attributeAccessor.AccessorContent.AsTriangles.Length; i += 3) + { + uint temp = attributeAccessor.AccessorContent.AsUInts[i]; + attributeAccessor.AccessorContent.AsUInts[i] = attributeAccessor.AccessorContent.AsUInts[i + 2]; + attributeAccessor.AccessorContent.AsUInts[i + 2] = temp; + } + } + + /// + /// Rewinds the indices from glTF space to Unity space + /// + /// The indices to copy and modify + /// Indices in glTF space that are copied + public static int[] FlipFacesAndCopy(int[] triangles) + { + int[] returnArr = new int[triangles.Length]; + for (int i = 0; i < triangles.Length; i += 3) + { + int temp = triangles[i]; + returnArr[i] = triangles[i + 2]; + returnArr[i + 1] = triangles[i + 1]; + returnArr[i + 2] = temp; + } + + return returnArr; + } + + public static Matrix4x4 ToUnityMatrix4x4(this GLTF.Math.Matrix4x4 matrix) + { + return new Matrix4x4() + { + m00 = matrix.M11, + m01 = matrix.M12, + m02 = matrix.M13, + m03 = matrix.M14, + m10 = matrix.M21, + m11 = matrix.M22, + m12 = matrix.M23, + m13 = matrix.M24, + m20 = matrix.M31, + m21 = matrix.M32, + m22 = matrix.M33, + m23 = matrix.M34, + m30 = matrix.M41, + m31 = matrix.M42, + m32 = matrix.M43, + m33 = matrix.M44 + }; + } + + public static Matrix4x4[] ToUnityMatrix4x4(this GLTF.Math.Matrix4x4[] inMatrixArr) + { + Matrix4x4[] outMatrixArr = new Matrix4x4[inMatrixArr.Length]; + for (int i = 0; i < inMatrixArr.Length; ++i) + { + outMatrixArr[i] = inMatrixArr[i].ToUnityMatrix4x4(); + } + return outMatrixArr; + } + } +} diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneExporter.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneExporter.cs index 74beeed41..4a835ef33 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneExporter.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneExporter.cs @@ -149,7 +149,7 @@ public void SaveGLB(string path, string fileName) _root.Scene = ExportScene(fileName, _rootTransforms); - _buffer.ByteLength = (int)_bufferWriter.BaseStream.Length; + _buffer.ByteLength = (uint)_bufferWriter.BaseStream.Length; _root.Serialize(jsonWriter); @@ -257,7 +257,7 @@ public void SaveGLTFandBin(string path, string fileName) _root.Scene = ExportScene(fileName, _rootTransforms); _buffer.Uri = fileName + ".bin"; - _buffer.ByteLength = (int)_bufferWriter.BaseStream.Length; + _buffer.ByteLength = (uint)_bufferWriter.BaseStream.Length; var gltfFile = File.CreateText(Path.Combine(path, fileName + ".gltf")); _root.Serialize(gltfFile); @@ -1143,7 +1143,7 @@ private SamplerId ExportSampler(Texture texture) private AccessorId ExportAccessor(int[] arr, bool isIndices = false) { - var count = arr.Length; + uint count = (uint)arr.Length; if (count == 0) { @@ -1171,7 +1171,7 @@ private AccessorId ExportAccessor(int[] arr, bool isIndices = false) } } - var byteOffset = _bufferWriter.BaseStream.Position; + uint byteOffset = (uint)_bufferWriter.BaseStream.Position; if (max <= byte.MaxValue && min >= byte.MinValue) { @@ -1231,9 +1231,9 @@ private AccessorId ExportAccessor(int[] arr, bool isIndices = false) accessor.Min = new List { min }; accessor.Max = new List { max }; - var byteLength = _bufferWriter.BaseStream.Position - byteOffset; + uint byteLength = (uint)_bufferWriter.BaseStream.Position - byteOffset; - accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); + accessor.BufferView = ExportBufferView(byteOffset, byteLength); var id = new AccessorId { @@ -1247,7 +1247,7 @@ private AccessorId ExportAccessor(int[] arr, bool isIndices = false) private AccessorId ExportAccessor(Vector2[] arr) { - var count = arr.Length; + uint count = (uint)arr.Length; if (count == 0) { @@ -1289,7 +1289,7 @@ private AccessorId ExportAccessor(Vector2[] arr) accessor.Min = new List { minX, minY }; accessor.Max = new List { maxX, maxY }; - var byteOffset = _bufferWriter.BaseStream.Position; + uint byteOffset = (uint)_bufferWriter.BaseStream.Position; foreach (var vec in arr) { @@ -1297,9 +1297,9 @@ private AccessorId ExportAccessor(Vector2[] arr) _bufferWriter.Write(vec.y); } - var byteLength = _bufferWriter.BaseStream.Position - byteOffset; + uint byteLength = (uint)_bufferWriter.BaseStream.Position - byteOffset; - accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); + accessor.BufferView = ExportBufferView(byteOffset, byteLength); var id = new AccessorId { @@ -1313,7 +1313,7 @@ private AccessorId ExportAccessor(Vector2[] arr) private AccessorId ExportAccessor(Vector3[] arr) { - var count = arr.Length; + uint count = (uint)arr.Length; if (count == 0) { @@ -1365,7 +1365,7 @@ private AccessorId ExportAccessor(Vector3[] arr) accessor.Min = new List { minX, minY, minZ }; accessor.Max = new List { maxX, maxY, maxZ }; - var byteOffset = _bufferWriter.BaseStream.Position; + uint byteOffset = (uint)_bufferWriter.BaseStream.Position; foreach (var vec in arr) { @@ -1374,9 +1374,9 @@ private AccessorId ExportAccessor(Vector3[] arr) _bufferWriter.Write(vec.z); } - var byteLength = _bufferWriter.BaseStream.Position - byteOffset; + uint byteLength = (uint)_bufferWriter.BaseStream.Position - byteOffset; - accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); + accessor.BufferView = ExportBufferView(byteOffset, byteLength); var id = new AccessorId { @@ -1390,7 +1390,7 @@ private AccessorId ExportAccessor(Vector3[] arr) private AccessorId ExportAccessor(Vector4[] arr) { - var count = arr.Length; + uint count = (uint)arr.Length; if (count == 0) { @@ -1452,7 +1452,7 @@ private AccessorId ExportAccessor(Vector4[] arr) accessor.Min = new List { minX, minY, minZ, minW }; accessor.Max = new List { maxX, maxY, maxZ, maxW }; - var byteOffset = _bufferWriter.BaseStream.Position; + uint byteOffset = (uint)_bufferWriter.BaseStream.Position; foreach (var vec in arr) { @@ -1462,9 +1462,9 @@ private AccessorId ExportAccessor(Vector4[] arr) _bufferWriter.Write(vec.w); } - var byteLength = _bufferWriter.BaseStream.Position - byteOffset; + uint byteLength = (uint)_bufferWriter.BaseStream.Position - byteOffset; - accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); + accessor.BufferView = ExportBufferView(byteOffset, byteLength); var id = new AccessorId { @@ -1478,7 +1478,7 @@ private AccessorId ExportAccessor(Vector4[] arr) private AccessorId ExportAccessor(Color[] arr) { - var count = arr.Length; + uint count = (uint)arr.Length; if (count == 0) { @@ -1540,7 +1540,7 @@ private AccessorId ExportAccessor(Color[] arr) accessor.Min = new List { minR, minG, minB, minA }; accessor.Max = new List { maxR, maxG, maxB, maxA }; - var byteOffset = _bufferWriter.BaseStream.Position; + uint byteOffset = (uint)_bufferWriter.BaseStream.Position; foreach (var color in arr) { @@ -1550,9 +1550,9 @@ private AccessorId ExportAccessor(Color[] arr) _bufferWriter.Write(color.a); } - var byteLength = _bufferWriter.BaseStream.Position - byteOffset; + uint byteLength = (uint)_bufferWriter.BaseStream.Position - byteOffset; - accessor.BufferView = ExportBufferView((int)byteOffset, (int)byteLength); + accessor.BufferView = ExportBufferView(byteOffset, byteLength); var id = new AccessorId { @@ -1564,7 +1564,7 @@ private AccessorId ExportAccessor(Color[] arr) return id; } - private BufferViewId ExportBufferView(int byteOffset, int byteLength) + private BufferViewId ExportBufferView(uint byteOffset, uint byteLength) { var bufferView = new BufferView { diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneImporter.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneImporter.cs index 1d843ab4c..e4e63a13c 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneImporter.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/GLTFSceneImporter.cs @@ -1,1574 +1,1581 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using GLTF; -using GLTF.Schema; -using UnityEngine; -using UnityEngine.Rendering; -using UnityGLTF.Cache; -using UnityGLTF.Extensions; -using UnityGLTF.Loader; -using Matrix4x4 = GLTF.Math.Matrix4x4; -using Object = UnityEngine.Object; -using WrapMode = UnityEngine.WrapMode; -using ThreadPriority = System.Threading.ThreadPriority; - -#if WINDOWS_UWP -using System.Threading.Tasks; -#endif - -namespace UnityGLTF -{ - public struct MeshConstructionData - { - public MeshPrimitive Primitive { get; set; } - public Dictionary MeshAttributes { get; set; } - } - - public class GLTFSceneImporter : IDisposable - { - public enum ColliderType - { - None, - Box, - Mesh, - MeshConvex - } - - /// - /// Maximum LOD - /// - public int MaximumLod = 300; - - /// - /// Timeout for certain threading operations - /// - public int Timeout = 8; - - /// - /// Use Multithreading or not - /// - public bool isMultithreaded = false; - - /// - /// The parent transform for the created GameObject - /// - public Transform SceneParent { get; set; } - - /// - /// The last created object - /// - public GameObject CreatedObject { get; private set; } - - /// - /// Adds colliders to primitive objects when created - /// - public ColliderType Collider { get; set; } - - /// - /// Override for the shader to use on created materials - /// - public string CustomShaderName { get; set; } - - protected struct GLBStream - { - public Stream Stream; - public long StartPosition; - } - - protected GameObject _lastLoadedScene; - protected readonly GLTFMaterial DefaultMaterial = new GLTFMaterial(); - protected MaterialCacheData _defaultLoadedMaterial = null; - - protected string _gltfFileName; - protected GLBStream _gltfStream; - protected GLTFRoot _gltfRoot; - protected AssetCache _assetCache; - protected AsyncAction _asyncAction; - protected ILoader _loader; - private bool _isRunning = false; - - - /// - /// Creates a GLTFSceneBuilder object which will be able to construct a scene based off a url - /// - /// glTF file relative to data loader path - /// - public GLTFSceneImporter(string gltfFileName, ILoader externalDataLoader) : this(externalDataLoader) - { - _gltfFileName = gltfFileName; - } - - public GLTFSceneImporter(GLTFRoot rootNode, ILoader externalDataLoader, Stream glbStream = null) : this(externalDataLoader) - { - _gltfRoot = rootNode; - if (glbStream != null) - { - _gltfStream = new GLBStream { Stream = glbStream, StartPosition = glbStream.Position }; - } - } - - private GLTFSceneImporter(ILoader externalDataLoader) - { - _loader = externalDataLoader; - _asyncAction = new AsyncAction(); - } - - public void Dispose() - { - if (_assetCache != null) - { - Cleanup(); - } - } - - public GameObject LastLoadedScene - { - get { return _lastLoadedScene; } - } - - /// - /// Loads a glTF Scene into the LastLoadedScene field - /// - /// The scene to load, If the index isn't specified, we use the default index in the file. Failing that we load index 0. - /// Callback function for when load is completed - /// - public IEnumerator LoadScene(int sceneIndex = -1, Action onLoadComplete = null) - { - try - { - lock (this) - { - if (_isRunning) - { - throw new GLTFLoadException("Cannot call LoadScene while GLTFSceneImporter is already running"); - } - - _isRunning = true; - } - - if (_gltfRoot == null) - { - yield return LoadJson(_gltfFileName); - } - yield return _LoadScene(sceneIndex); - - Cleanup(); - } - finally - { - lock (this) - { - _isRunning = false; - } - } - - if (onLoadComplete != null) - { - onLoadComplete(LastLoadedScene); - } - } - - /// - /// Loads a node tree from a glTF file into the LastLoadedScene field - /// - /// The node index to load from the glTF - /// - public IEnumerator LoadNode(int nodeIndex) - { - if (_gltfRoot == null) - { - throw new InvalidOperationException("GLTF root must first be loaded and parsed"); - } - - try - { - lock (this) - { - if (_isRunning) - { - throw new GLTFLoadException("Cannot call LoadNode while GLTFSceneImporter is already running"); - } - - _isRunning = true; - } - - if (_assetCache == null) - { - InitializeAssetCache(); - } - - yield return _LoadNode(nodeIndex); - CreatedObject = _assetCache.NodeCache[nodeIndex]; - InitializeGltfTopLevelObject(); - - // todo: optimially the asset cache can be reused between nodes - Cleanup(); - } - finally - { - lock (this) - { - _isRunning = false; - } - } - } - - /// - /// Initializes the top-level created node by adding an instantiated GLTF object component to it, - /// so that it can cleanup after itself properly when destroyed - /// - private void InitializeGltfTopLevelObject() - { - InstantiatedGLTFObject instantiatedGltfObject = CreatedObject.AddComponent(); - instantiatedGltfObject.CachedData = new RefCountedCacheData - { - MaterialCache = _assetCache.MaterialCache, - MeshCache = _assetCache.MeshCache, - TextureCache = _assetCache.TextureCache - }; - } - private IEnumerator ConstructBufferData(Node node) - { - MeshId mesh = node.Mesh; - if (mesh != null) - { - if (mesh.Value.Primitives != null) - { - yield return ConstructMeshAttributes(mesh.Value, mesh); - } - } - - if (node.Children != null) - { - foreach (NodeId child in node.Children) - { - yield return ConstructBufferData(child.Value); - } - } - } - - private IEnumerator ConstructMeshAttributes(GLTFMesh mesh, MeshId meshId) - { - int meshIdIndex = meshId.Id; - - if (_assetCache.MeshCache[meshIdIndex] == null) - { - _assetCache.MeshCache[meshIdIndex] = new MeshCacheData[mesh.Primitives.Count]; - } - - for (int i = 0; i < mesh.Primitives.Count; ++i) - { - MeshPrimitive primitive = mesh.Primitives[i]; - - if (_assetCache.MeshCache[meshIdIndex][i] == null) - { - _assetCache.MeshCache[meshIdIndex][i] = new MeshCacheData(); - } - - if (_assetCache.MeshCache[meshIdIndex][i].MeshAttributes.Count == 0) - { - yield return ConstructMeshAttributes(primitive, meshIdIndex, i); - if (primitive.Material != null) - { - yield return ConstructMaterialImageBuffers(primitive.Material.Value); - } - } - } - } - - protected IEnumerator ConstructImageBuffer(GLTFTexture texture, int textureIndex) - { - int sourceId = GetTextureSourceId(texture); - if (_assetCache.ImageStreamCache[sourceId] == null) - { - GLTFImage image = _gltfRoot.Images[sourceId]; - - // we only load the streams if not a base64 uri, meaning the data is in the uri - if (image.Uri != null && !URIHelper.IsBase64Uri(image.Uri)) - { - yield return _loader.LoadStream(image.Uri); - _assetCache.ImageStreamCache[sourceId] = _loader.LoadedStream; - } - } - - _assetCache.TextureCache[textureIndex] = new TextureCacheData - { - TextureDefinition = texture - }; - } - - private IEnumerator LoadJson(string jsonFilePath) - { - if (isMultithreaded && _loader.HasSyncLoadMethod) - { - Thread loadThread = new Thread(() => _loader.LoadStreamSync(jsonFilePath)); - loadThread.Priority = ThreadPriority.Highest; - loadThread.Start(); - yield return new WaitUntil(() => !loadThread.IsAlive); - } - else - { - yield return _loader.LoadStream(jsonFilePath); - } - - _gltfStream.Stream = _loader.LoadedStream; - _gltfStream.StartPosition = 0; - - if (isMultithreaded) - { - Thread parseJsonThread = new Thread(() => GLTFParser.ParseJson(_gltfStream.Stream, out _gltfRoot, _gltfStream.StartPosition)); - parseJsonThread.Priority = ThreadPriority.Highest; - parseJsonThread.Start(); - yield return new WaitUntil(() => !parseJsonThread.IsAlive); - } - else - { - GLTFParser.ParseJson(_gltfStream.Stream, out _gltfRoot, _gltfStream.StartPosition); - yield return null; - } - } - - private IEnumerator _LoadNode(int nodeIndex) - { - if (nodeIndex >= _gltfRoot.Nodes.Count) - { - throw new ArgumentException("nodeIndex is out of range"); - } - - Node nodeToLoad = _gltfRoot.Nodes[nodeIndex]; - yield return ConstructBufferData(nodeToLoad); - yield return ConstructNode(nodeToLoad, nodeIndex); - } - - protected void InitializeAssetCache() - { - _assetCache = new AssetCache( - _gltfRoot.Images != null ? _gltfRoot.Images.Count : 0, - _gltfRoot.Textures != null ? _gltfRoot.Textures.Count : 0, - _gltfRoot.Materials != null ? _gltfRoot.Materials.Count : 0, - _gltfRoot.Buffers != null ? _gltfRoot.Buffers.Count : 0, - _gltfRoot.Meshes != null ? _gltfRoot.Meshes.Count : 0, - _gltfRoot.Nodes != null ? _gltfRoot.Nodes.Count : 0, - _gltfRoot.Animations != null ? _gltfRoot.Animations.Count : 0 - ); - } - - /// - /// Creates a scene based off loaded JSON. Includes loading in binary and image data to construct the meshes required. - /// - /// The bufferIndex of scene in gltf file to load - /// - protected IEnumerator _LoadScene(int sceneIndex = -1) - { - GLTFScene scene; - InitializeAssetCache(); // asset cache currently needs initialized every time due to cleanup logic - - if (sceneIndex >= 0 && sceneIndex < _gltfRoot.Scenes.Count) - { - scene = _gltfRoot.Scenes[sceneIndex]; - } - else - { - scene = _gltfRoot.GetDefaultScene(); - } - - if (scene == null) - { - throw new GLTFLoadException("No default scene in gltf file."); - } - - if (_lastLoadedScene == null) - { - if (_gltfRoot.Buffers != null) - { - // todo add fuzzing to verify that buffers are before uri - for (int i = 0; i < _gltfRoot.Buffers.Count; ++i) - { - GLTFBuffer buffer = _gltfRoot.Buffers[i]; - if (_assetCache.BufferCache[i] == null) - { - yield return ConstructBuffer(buffer, i); - } - } - } - - if (_gltfRoot.Textures != null) - { - for (int i = 0; i < _gltfRoot.Textures.Count; ++i) - { - if (_assetCache.TextureCache[i] == null) - { - GLTFTexture texture = _gltfRoot.Textures[i]; - yield return ConstructImageBuffer(texture, i); - yield return ConstructImage(texture.Source.Value, texture.Source.Id); - } - } - } - yield return ConstructAttributesForMeshes(); - } - - yield return ConstructScene(scene); - - if (SceneParent != null) - { - CreatedObject.transform.SetParent(SceneParent, false); - } - - _lastLoadedScene = CreatedObject; - } - - protected IEnumerator ConstructBuffer(GLTFBuffer buffer, int bufferIndex) - { - if (buffer.Uri == null) - { - _assetCache.BufferCache[bufferIndex] = ConstructBufferFromGLB(bufferIndex); - } - else - { - Stream bufferDataStream = null; - var uri = buffer.Uri; - - byte[] bufferData; - URIHelper.TryParseBase64(uri, out bufferData); - if (bufferData != null) - { - bufferDataStream = new MemoryStream(bufferData, 0, bufferData.Length, false, true); - } - else - { - yield return _loader.LoadStream(buffer.Uri); - bufferDataStream = _loader.LoadedStream; - } - - _assetCache.BufferCache[bufferIndex] = new BufferCacheData - { - Stream = bufferDataStream - }; - } - } - - protected IEnumerator ConstructImage(GLTFImage image, int imageCacheIndex, bool markGpuOnly = true) - { - if (_assetCache.ImageCache[imageCacheIndex] == null) - { - if (image.BufferView != null) - { - yield return ConstructImageFromGLB(image, imageCacheIndex); - } - else - { - string uri = image.Uri; - - byte[] bufferData; - URIHelper.TryParseBase64(uri, out bufferData); - if (bufferData != null) - { - Texture2D loadedTexture = new Texture2D(0, 0); - loadedTexture.LoadImage(bufferData, true); - - _assetCache.ImageCache[imageCacheIndex] = loadedTexture; - yield return null; - } - else - { - Stream stream = _assetCache.ImageStreamCache[imageCacheIndex]; - yield return ConstructUnityTexture(stream, markGpuOnly, image, imageCacheIndex); - } - } - } - } - - /// - /// Loads texture from a stream. Is responsible for stream clean up - /// - /// - /// Non-readable textures are saved only on the GPU and take up half as much memory. - /// - /// - protected virtual IEnumerator ConstructUnityTexture(Stream stream, bool markGpuOnly, GLTFImage image, int imageCacheIndex) - { - Texture2D texture = new Texture2D(0, 0); - - if (stream is MemoryStream) - { - using (MemoryStream memoryStream = stream as MemoryStream) - { - // NOTE: the second parameter of LoadImage() marks non-readable, but we can't mark it until after we call Apply() - texture.LoadImage(memoryStream.ToArray(), false); - } - - yield return null; - } - else - { - byte[] buffer = new byte[stream.Length]; - - // todo: potential optimization is to split stream read into multiple frames (or put it on a thread?) - using (stream) - { - if (stream.Length > int.MaxValue) - { - throw new Exception("Stream is larger than can be copied into byte array"); - } - - if (isMultithreaded) - { - Thread readThread = new Thread(() => stream.Read(buffer, 0, (int)stream.Length)); - readThread.Priority = ThreadPriority.Highest; - readThread.Start(); - yield return new WaitUntil(() => !readThread.IsAlive); - } - else - { - stream.Read(buffer, 0, (int)stream.Length); - yield return null; - } - } - - // NOTE: the second parameter of LoadImage() marks non-readable, but we can't mark it until after we call Apply() - texture.LoadImage(buffer, false); - yield return null; - } - - // After we conduct the Apply(), then we can make the texture non-readable and never create a CPU copy - texture.Apply(true, markGpuOnly); - - _assetCache.ImageCache[imageCacheIndex] = texture; - yield return null; - } - - protected virtual IEnumerator ConstructAttributesForMeshes() - { - for (int i = 0; i < _gltfRoot.Meshes.Count; ++i) - { - GLTFMesh mesh = _gltfRoot.Meshes[i]; - if (_assetCache.MeshCache[i] == null) - { - _assetCache.MeshCache[i] = new MeshCacheData[mesh.Primitives.Count]; - } - - for (int j = 0; j < mesh.Primitives.Count; ++j) - { - _assetCache.MeshCache[i][j] = new MeshCacheData(); - var primitive = mesh.Primitives[j]; - yield return ConstructMeshAttributes(primitive, i, j); - if (primitive.Material != null) - { - yield return ConstructMaterialImageBuffers(primitive.Material.Value); - } - } - } - } - - protected virtual IEnumerator ConstructMeshAttributes(MeshPrimitive primitive, int meshID, int primitiveIndex) - { - if (_assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes.Count == 0) - { - Dictionary attributeAccessors = new Dictionary(primitive.Attributes.Count + 1); - foreach (var attributePair in primitive.Attributes) - { - BufferId bufferIdPair = attributePair.Value.Value.BufferView.Value.Buffer; - GLTFBuffer buffer = bufferIdPair.Value; - int bufferId = bufferIdPair.Id; - - // on cache miss, load the buffer - if (_assetCache.BufferCache[bufferId] == null) - { - yield return ConstructBuffer(buffer, bufferId); - } - - AttributeAccessor attributeAccessor = new AttributeAccessor - { - AccessorId = attributePair.Value, - Stream = _assetCache.BufferCache[bufferId].Stream, - Offset = _assetCache.BufferCache[bufferId].ChunkOffset - }; - - attributeAccessors[attributePair.Key] = attributeAccessor; - } - - if (primitive.Indices != null) - { - int bufferId = primitive.Indices.Value.BufferView.Value.Buffer.Id; - AttributeAccessor indexBuilder = new AttributeAccessor - { - AccessorId = primitive.Indices, - Stream = _assetCache.BufferCache[bufferId].Stream, - Offset = _assetCache.BufferCache[bufferId].ChunkOffset - }; - - attributeAccessors[SemanticProperties.INDICES] = indexBuilder; - } - - if (isMultithreaded) - { - Thread buildMeshAttributesThread = new Thread(() => GLTFHelpers.BuildMeshAttributes(ref attributeAccessors)); - buildMeshAttributesThread.Priority = ThreadPriority.Highest; - buildMeshAttributesThread.Start(); - while (!buildMeshAttributesThread.Join(Timeout)) - { - yield return null; - } - } - else - { - GLTFHelpers.BuildMeshAttributes(ref attributeAccessors); - } - - TransformAttributes(ref attributeAccessors); - _assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes = attributeAccessors; - } - } - - protected void TransformAttributes(ref Dictionary attributeAccessors) - { - // Flip vectors and triangles to the Unity coordinate system. - if (attributeAccessors.ContainsKey(SemanticProperties.POSITION)) - { - AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.POSITION]; - SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale); - } - if (attributeAccessors.ContainsKey(SemanticProperties.INDICES)) - { - AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.INDICES]; - SchemaExtensions.FlipFaces(ref attributeAccessor); - } - if (attributeAccessors.ContainsKey(SemanticProperties.NORMAL)) - { - AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.NORMAL]; - SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale); - } - // TexCoord goes from 0 to 3 to match GLTFHelpers.BuildMeshAttributes - for (int i = 0; i < 4; i++) - { - if (attributeAccessors.ContainsKey(SemanticProperties.TexCoord(i))) - { - AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.TexCoord(i)]; - SchemaExtensions.FlipTexCoordArrayV(ref attributeAccessor); - } - } - if (attributeAccessors.ContainsKey(SemanticProperties.TANGENT)) - { - AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.TANGENT]; - SchemaExtensions.ConvertVector4CoordinateSpace(ref attributeAccessor, SchemaExtensions.TangentSpaceConversionScale); - } - } - - #region Animation - static string RelativePathFrom(Transform self, Transform root) - { - var path = new List(); - for (var current = self; current != null; current = current.parent) - { - if (current == root) - { - return String.Join("/", path.ToArray()); - } - - path.Insert(0, current.name); - } - - throw new Exception("no RelativePath"); - } - - protected virtual void BuildAnimationSamplers(GLTFAnimation animation, int animationId) - { - // look up expected data types - var typeMap = new Dictionary(); - foreach (var channel in animation.Channels) - { - typeMap[channel.Sampler.Id] = channel.Target.Path.ToString(); - } - - var samplers = _assetCache.AnimationCache[animationId].Samplers; - var samplersByType = new Dictionary> - { - {"time", new List(animation.Samplers.Count)} - }; - - for (var i = 0; i < animation.Samplers.Count; i++) - { - // no sense generating unused samplers - if (!typeMap.ContainsKey(i)) - { - continue; - } - - var samplerDef = animation.Samplers[i]; - - // set up input accessors - BufferCacheData bufferCacheData = _assetCache.BufferCache[samplerDef.Input.Value.BufferView.Value.Buffer.Id]; - AttributeAccessor attributeAccessor = new AttributeAccessor - { - AccessorId = samplerDef.Input, - Stream = bufferCacheData.Stream, - Offset = bufferCacheData.ChunkOffset - }; - - samplers[i].Input = attributeAccessor; - samplersByType["time"].Add(attributeAccessor); - - // set up output accessors - bufferCacheData = _assetCache.BufferCache[samplerDef.Output.Value.BufferView.Value.Buffer.Id]; - attributeAccessor = new AttributeAccessor - { - AccessorId = samplerDef.Output, - Stream = bufferCacheData.Stream, - Offset = bufferCacheData.ChunkOffset - }; - - samplers[i].Output = attributeAccessor; - - if (!samplersByType.ContainsKey(typeMap[i])) - { - samplersByType[typeMap[i]] = new List(); - } - - samplersByType[typeMap[i]].Add(attributeAccessor); - } - - // populate attributeAccessors with buffer data - GLTFHelpers.BuildAnimationSamplers(ref samplersByType); - } - - AnimationClip ConstructClip(Transform root, Transform[] nodes, int animationId) - { - var animation = _gltfRoot.Animations[animationId]; - - var animationCache = _assetCache.AnimationCache[animationId]; - if (animationCache == null) - { - animationCache = new AnimationCacheData(animation.Samplers.Count); - _assetCache.AnimationCache[animationId] = animationCache; - } - else if (animationCache.LoadedAnimationClip != null) - return animationCache.LoadedAnimationClip; - - // unpack accessors - BuildAnimationSamplers(animation, animationId); - - // init clip - var clip = new AnimationClip - { - name = animation.Name ?? String.Format("animation:{0}", animationId) - }; - _assetCache.AnimationCache[animationId].LoadedAnimationClip = clip; - - // needed because Animator component is unavailable at runtime - clip.legacy = true; - - foreach (var channel in animation.Channels) - { - var samplerCache = animationCache.Samplers[channel.Sampler.Id]; - var node = nodes[channel.Target.Node.Id]; - var relativePath = RelativePathFrom(node, root); - AnimationCurve curveX = new AnimationCurve(), - curveY = new AnimationCurve(), - curveZ = new AnimationCurve(), - curveW = new AnimationCurve(); - NumericArray input = samplerCache.Input.AccessorContent, - output = samplerCache.Output.AccessorContent; - - switch (channel.Target.Path) - { - case GLTFAnimationChannelPath.translation: - for (var i = 0; i < input.AsFloats.Length; ++i) - { - var time = input.AsFloats[i]; - Vector3 position = output.AsVec3s[i].ToUnityVector3Convert(); - curveX.AddKey(time, position.x); - curveY.AddKey(time, position.y); - curveZ.AddKey(time, position.z); - } - - clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", curveX); - clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", curveY); - clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", curveZ); - break; - - case GLTFAnimationChannelPath.rotation: - for (int i = 0; i < input.AsFloats.Length; ++i) - { - var time = input.AsFloats[i]; - var rotation = output.AsVec4s[i]; - - Quaternion rot = new GLTF.Math.Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W).ToUnityQuaternionConvert(); - curveX.AddKey(time, rot.x); - curveY.AddKey(time, rot.y); - curveZ.AddKey(time, rot.z); - curveW.AddKey(time, rot.w); - } - - clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", curveX); - clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", curveY); - clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", curveZ); - clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", curveW); - break; - - case GLTFAnimationChannelPath.scale: - for (var i = 0; i < input.AsFloats.Length; ++i) - { - var time = input.AsFloats[i]; - Vector3 scale = output.AsVec3s[i].ToUnityVector3Raw(); - curveX.AddKey(time, scale.x); - curveY.AddKey(time, scale.y); - curveZ.AddKey(time, scale.z); - } - - clip.SetCurve(relativePath, typeof(Transform), "localScale.x", curveX); - clip.SetCurve(relativePath, typeof(Transform), "localScale.y", curveY); - clip.SetCurve(relativePath, typeof(Transform), "localScale.z", curveZ); - break; - - case GLTFAnimationChannelPath.weights: - var primitives = channel.Target.Node.Value.Mesh.Value.Primitives; - var targetCount = primitives[0].Targets.Count; - for (int primitiveIndex = 0; primitiveIndex < primitives.Count; primitiveIndex++) - { - for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) - { - // TODO: add support for blend shapes/morph targets - //clip.SetCurve(primitiveObjPath, typeof(SkinnedMeshRenderer), "blendShape." + targetIndex, curves[targetIndex]); - } - } - break; - - default: - Debug.LogWarning("Cannot read GLTF animation path"); - break; - } // switch target type - } // foreach channel - - clip.EnsureQuaternionContinuity(); - return clip; - } - #endregion - - protected virtual IEnumerator ConstructScene(GLTFScene scene) - { - var sceneObj = new GameObject(string.IsNullOrEmpty(scene.Name) ? ("GLTFScene") : scene.Name); - - Transform[] nodeTransforms = new Transform[scene.Nodes.Count]; - for (int i = 0; i < scene.Nodes.Count; ++i) - { - NodeId node = scene.Nodes[i]; - yield return ConstructNode(node.Value, node.Id); - GameObject nodeObj = _assetCache.NodeCache[node.Id]; - nodeObj.transform.SetParent(sceneObj.transform, false); - nodeTransforms[i] = nodeObj.transform; - } - - if (_gltfRoot.Animations != null && _gltfRoot.Animations.Count > 0) - { - // create the AnimationClip that will contain animation data - Animation animation = sceneObj.AddComponent(); - for (int i = 0; i < _gltfRoot.Animations.Count; ++i) - { - AnimationClip clip = ConstructClip(sceneObj.transform, _assetCache.NodeCache.Select(x => x.transform).ToArray(), i); - - clip.wrapMode = WrapMode.Loop; - - animation.AddClip(clip, clip.name); - if (i == 0) - { - animation.clip = clip; - } - } - } - - CreatedObject = sceneObj; - InitializeGltfTopLevelObject(); - } - - protected virtual IEnumerator ConstructNode(Node node, int nodeIndex) - { - if (_assetCache.NodeCache[nodeIndex] != null) - { - yield break; - } - - var nodeObj = new GameObject(string.IsNullOrEmpty(node.Name) ? ("GLTFNode" + nodeIndex) : node.Name); - // If we're creating a really large node, we need it to not be visible in partial stages. So we hide it while we create it - nodeObj.SetActive(false); - - Vector3 position; - Quaternion rotation; - Vector3 scale; - node.GetUnityTRSProperties(out position, out rotation, out scale); - nodeObj.transform.localPosition = position; - nodeObj.transform.localRotation = rotation; - nodeObj.transform.localScale = scale; - - if (node.Mesh != null) - { - yield return ConstructMesh(node.Mesh.Value, nodeObj.transform, node.Mesh.Id, node.Skin != null ? node.Skin.Value : null); - } - /* TODO: implement camera (probably a flag to disable for VR as well) - if (camera != null) - { - GameObject cameraObj = camera.Value.Create(); - cameraObj.transform.parent = nodeObj.transform; - } - */ - - if (node.Children != null) - { - foreach (var child in node.Children) - { - // todo blgross: replace with an iterartive solution - yield return ConstructNode(child.Value, child.Id); - GameObject childObj = _assetCache.NodeCache[child.Id]; - childObj.transform.SetParent(nodeObj.transform, false); - } - } - - nodeObj.SetActive(true); - _assetCache.NodeCache[nodeIndex] = nodeObj; - } - - private bool NeedsSkinnedMeshRenderer(MeshPrimitive primitive, Skin skin) - { - return HasBones(skin) || HasBlendShapes(primitive); - } - - private bool HasBones(Skin skin) - { - return skin != null; - } - - private bool HasBlendShapes(MeshPrimitive primitive) - { - return primitive.Targets != null; - } - - protected virtual IEnumerator SetupBones(Skin skin, MeshPrimitive primitive, SkinnedMeshRenderer renderer, GameObject primitiveObj, Mesh curMesh) - { - var boneCount = skin.Joints.Count; - Transform[] bones = new Transform[boneCount]; - - int bufferId = skin.InverseBindMatrices.Value.BufferView.Value.Buffer.Id; - AttributeAccessor attributeAccessor = new AttributeAccessor - { - AccessorId = skin.InverseBindMatrices, - Stream = _assetCache.BufferCache[bufferId].Stream, - Offset = _assetCache.BufferCache[bufferId].ChunkOffset - }; - - GLTFHelpers.BuildBindPoseSamplers(ref attributeAccessor); - - Matrix4x4[] gltfBindPoses = attributeAccessor.AccessorContent.AsMatrix4x4s; - UnityEngine.Matrix4x4[] bindPoses = new UnityEngine.Matrix4x4[skin.Joints.Count]; - - for (int i = 0; i < boneCount; i++) - { - if (_assetCache.NodeCache[skin.Joints[i].Id] == null) - { - yield return ConstructNode(_gltfRoot.Nodes[skin.Joints[i].Id], skin.Joints[i].Id); - } - bones[i] = _assetCache.NodeCache[skin.Joints[i].Id].transform; - bindPoses[i] = gltfBindPoses[i].ToUnityMatrix4x4Convert(); - } - - renderer.rootBone = _assetCache.NodeCache[skin.Skeleton.Id].transform; - curMesh.bindposes = bindPoses; - renderer.bones = bones; - - yield return null; - } - - private BoneWeight[] CreateBoneWeightArray(Vector4[] joints, Vector4[] weights, int vertCount) - { - NormalizeBoneWeightArray(weights); - - BoneWeight[] boneWeights = new BoneWeight[vertCount]; - for (int i = 0; i < vertCount; i++) - { - boneWeights[i].boneIndex0 = (int)joints[i].x; - boneWeights[i].boneIndex1 = (int)joints[i].y; - boneWeights[i].boneIndex2 = (int)joints[i].z; - boneWeights[i].boneIndex3 = (int)joints[i].w; - - boneWeights[i].weight0 = weights[i].x; - boneWeights[i].weight1 = weights[i].y; - boneWeights[i].weight2 = weights[i].z; - boneWeights[i].weight3 = weights[i].w; - } - - return boneWeights; - } - - /// - /// Ensures each bone weight influences applied to the vertices add up to 1 - /// - /// Bone weight array - private void NormalizeBoneWeightArray(Vector4[] weights) - { - for (int i = 0; i < weights.Length; i++) - { - var weightSum = (weights[i].x + weights[i].y + weights[i].z + weights[i].w); - - if (!Mathf.Approximately(weightSum, 0)) - { - weights[i] /= weightSum; - } - } - } - - protected virtual IEnumerator ConstructMesh(GLTFMesh mesh, Transform parent, int meshId, Skin skin) - { - if (_assetCache.MeshCache[meshId] == null) - { - _assetCache.MeshCache[meshId] = new MeshCacheData[mesh.Primitives.Count]; - } - - for (int i = 0; i < mesh.Primitives.Count; ++i) - { - var primitive = mesh.Primitives[i]; - int materialIndex = primitive.Material != null ? primitive.Material.Id : -1; - - yield return ConstructMeshPrimitive(primitive, meshId, i, materialIndex); - - var primitiveObj = new GameObject("Primitive"); - - MaterialCacheData materialCacheData = - materialIndex >= 0 ? _assetCache.MaterialCache[materialIndex] : _defaultLoadedMaterial; - - Material material = materialCacheData.GetContents(primitive.Attributes.ContainsKey(SemanticProperties.Color(0))); - - Mesh curMesh = _assetCache.MeshCache[meshId][i].LoadedMesh; - if (NeedsSkinnedMeshRenderer(primitive, skin)) - { - var skinnedMeshRenderer = primitiveObj.AddComponent(); - skinnedMeshRenderer.material = material; - skinnedMeshRenderer.quality = SkinQuality.Auto; - // TODO: add support for blend shapes/morph targets - //if (HasBlendShapes(primitive)) - // SetupBlendShapes(primitive); - if (HasBones(skin)) - { - yield return SetupBones(skin, primitive, skinnedMeshRenderer, primitiveObj, curMesh); - } - - skinnedMeshRenderer.sharedMesh = curMesh; - } - else - { - var meshRenderer = primitiveObj.AddComponent(); - meshRenderer.material = material; - } - - MeshFilter meshFilter = primitiveObj.AddComponent(); - meshFilter.sharedMesh = curMesh; - - switch (Collider) - { - case ColliderType.Box: - var boxCollider = primitiveObj.AddComponent(); - boxCollider.center = curMesh.bounds.center; - boxCollider.size = curMesh.bounds.size; - break; - case ColliderType.Mesh: - var meshCollider = primitiveObj.AddComponent(); - meshCollider.sharedMesh = curMesh; - break; - case ColliderType.MeshConvex: - var meshConvexCollider = primitiveObj.AddComponent(); - meshConvexCollider.sharedMesh = curMesh; - meshConvexCollider.convex = true; - break; - } - - primitiveObj.transform.SetParent(parent, false); - primitiveObj.SetActive(true); - } - } - - protected virtual IEnumerator ConstructMeshPrimitive(MeshPrimitive primitive, int meshID, int primitiveIndex, int materialIndex) - { - if (_assetCache.MeshCache[meshID][primitiveIndex] == null) - { - _assetCache.MeshCache[meshID][primitiveIndex] = new MeshCacheData(); - } - if (_assetCache.MeshCache[meshID][primitiveIndex].LoadedMesh == null) - { - var meshAttributes = _assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes; - var meshConstructionData = new MeshConstructionData - { - Primitive = primitive, - MeshAttributes = meshAttributes - }; - - yield return null; - yield return ConstructUnityMesh(meshConstructionData, meshID, primitiveIndex); - } - - bool shouldUseDefaultMaterial = primitive.Material == null; - - GLTFMaterial materialToLoad = shouldUseDefaultMaterial ? DefaultMaterial : primitive.Material.Value; - if ((shouldUseDefaultMaterial && _defaultLoadedMaterial == null) || - (!shouldUseDefaultMaterial && _assetCache.MaterialCache[materialIndex] == null)) - { - yield return ConstructMaterialTextures(materialToLoad); - ConstructMaterial(materialToLoad, materialIndex); - } - } - - protected virtual IEnumerator ConstructMaterialImageBuffers(GLTFMaterial def) - { - if (def.PbrMetallicRoughness != null) - { - var pbr = def.PbrMetallicRoughness; - - if (pbr.BaseColorTexture != null) - { - var textureId = pbr.BaseColorTexture.Index; - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - if (pbr.MetallicRoughnessTexture != null) - { - var textureId = pbr.MetallicRoughnessTexture.Index; - - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - } - - if (def.CommonConstant != null) - { - if (def.CommonConstant.LightmapTexture != null) - { - var textureId = def.CommonConstant.LightmapTexture.Index; - - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - } - - if (def.NormalTexture != null) - { - var textureId = def.NormalTexture.Index; - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - - if (def.OcclusionTexture != null) - { - var textureId = def.OcclusionTexture.Index; - - if (!(def.PbrMetallicRoughness != null - && def.PbrMetallicRoughness.MetallicRoughnessTexture != null - && def.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id == textureId.Id)) - { - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - } - - if (def.EmissiveTexture != null) - { - var textureId = def.EmissiveTexture.Index; - yield return ConstructImageBuffer(textureId.Value, textureId.Id); - } - } - - protected virtual IEnumerator ConstructMaterialTextures(GLTFMaterial def) - { - for (int i = 0; i < _assetCache.TextureCache.Length; ++i) - { - TextureCacheData textureCacheData = _assetCache.TextureCache[i]; - if (textureCacheData != null && textureCacheData.Texture == null) - { - yield return ConstructTexture(textureCacheData.TextureDefinition, i, true); - } - } - } - - protected IEnumerator ConstructUnityMesh(MeshConstructionData meshConstructionData, int meshId, int primitiveIndex) - { - MeshPrimitive primitive = meshConstructionData.Primitive; - var meshAttributes = meshConstructionData.MeshAttributes; - var vertexCount = primitive.Attributes[SemanticProperties.POSITION].Value.Count; - - // todo optimize: There are multiple copies being performed to turn the buffer data into mesh data. Look into reducing them - Mesh mesh = new Mesh - { -#if UNITY_2017_3_OR_NEWER - indexFormat = vertexCount > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, -#endif - vertices = primitive.Attributes.ContainsKey(SemanticProperties.POSITION) - ? meshAttributes[SemanticProperties.POSITION].AccessorContent.AsVertices.ToUnityVector3Raw() - : null, - normals = primitive.Attributes.ContainsKey(SemanticProperties.NORMAL) - ? meshAttributes[SemanticProperties.NORMAL].AccessorContent.AsNormals.ToUnityVector3Raw() - : null, - - uv = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(0)) - ? meshAttributes[SemanticProperties.TexCoord(0)].AccessorContent.AsTexcoords.ToUnityVector2Raw() - : null, - - uv2 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(1)) - ? meshAttributes[SemanticProperties.TexCoord(1)].AccessorContent.AsTexcoords.ToUnityVector2Raw() - : null, - - uv3 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(2)) - ? meshAttributes[SemanticProperties.TexCoord(2)].AccessorContent.AsTexcoords.ToUnityVector2Raw() - : null, - - uv4 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(3)) - ? meshAttributes[SemanticProperties.TexCoord(3)].AccessorContent.AsTexcoords.ToUnityVector2Raw() - : null, - - colors = primitive.Attributes.ContainsKey(SemanticProperties.Color(0)) - ? meshAttributes[SemanticProperties.Color(0)].AccessorContent.AsColors.ToUnityColorRaw() - : null, - - triangles = primitive.Indices != null - ? meshAttributes[SemanticProperties.INDICES].AccessorContent.AsUInts.ToIntArrayRaw() - : MeshPrimitive.GenerateTriangles(vertexCount), - - tangents = primitive.Attributes.ContainsKey(SemanticProperties.TANGENT) - ? meshAttributes[SemanticProperties.TANGENT].AccessorContent.AsTangents.ToUnityVector4Raw() - : null, - - boneWeights = meshAttributes.ContainsKey(SemanticProperties.Weight(0)) && meshAttributes.ContainsKey(SemanticProperties.Joint(0)) - ? CreateBoneWeightArray(meshAttributes[SemanticProperties.Joint(0)].AccessorContent.AsVec4s.ToUnityVector4Raw(), - meshAttributes[SemanticProperties.Weight(0)].AccessorContent.AsVec4s.ToUnityVector4Raw(), vertexCount) - : null - }; - - _assetCache.MeshCache[meshId][primitiveIndex].LoadedMesh = mesh; - - yield return null; - } - - protected virtual void ConstructMaterial(GLTFMaterial def, int materialIndex) - { - IUniformMap mapper; - const string specGlossExtName = KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME; - if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(specGlossExtName) - && def.Extensions != null && def.Extensions.ContainsKey(specGlossExtName)) - { - if (!string.IsNullOrEmpty(CustomShaderName)) - { - mapper = new SpecGlossMap(CustomShaderName, MaximumLod); - } - else - { - mapper = new SpecGlossMap(MaximumLod); - } - } - else - { - if (!string.IsNullOrEmpty(CustomShaderName)) - { - mapper = new MetalRoughMap(CustomShaderName, MaximumLod); - } - else - { - mapper = new MetalRoughMap(MaximumLod); - } - } - - mapper.AlphaMode = def.AlphaMode; - mapper.DoubleSided = def.DoubleSided; - - var mrMapper = mapper as IMetalRoughUniformMap; - if (def.PbrMetallicRoughness != null && mrMapper != null) - { - var pbr = def.PbrMetallicRoughness; - - mrMapper.BaseColorFactor = pbr.BaseColorFactor.ToUnityColorRaw(); - - if (pbr.BaseColorTexture != null) - { - int textureId = pbr.BaseColorTexture.Index.Id; - mrMapper.BaseColorTexture = _assetCache.TextureCache[textureId].Texture; - mrMapper.BaseColorTexCoord = pbr.BaseColorTexture.TexCoord; - - //ApplyTextureTransform(pbr.BaseColorTexture, material, "_MainTex"); - } - - mrMapper.MetallicFactor = pbr.MetallicFactor; - - if (pbr.MetallicRoughnessTexture != null) - { - int textureId = pbr.MetallicRoughnessTexture.Index.Id; - mrMapper.MetallicRoughnessTexture = _assetCache.TextureCache[textureId].Texture; - mrMapper.MetallicRoughnessTexCoord = pbr.MetallicRoughnessTexture.TexCoord; - - //ApplyTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicRoughnessMap"); - } - - mrMapper.RoughnessFactor = pbr.RoughnessFactor; - } - - var sgMapper = mapper as ISpecGlossUniformMap; - if (sgMapper != null) - { - var specGloss = def.Extensions[specGlossExtName] as KHR_materials_pbrSpecularGlossinessExtension; - - sgMapper.DiffuseFactor = specGloss.DiffuseFactor.ToUnityColorRaw(); - - if (specGloss.DiffuseTexture != null) - { - int textureId = specGloss.DiffuseTexture.Index.Id; - sgMapper.DiffuseTexture = _assetCache.TextureCache[textureId].Texture; - sgMapper.DiffuseTexCoord = specGloss.DiffuseTexture.TexCoord; - - //ApplyTextureTransform(specGloss.DiffuseTexture, material, "_MainTex"); - } - - sgMapper.SpecularFactor = specGloss.SpecularFactor.ToUnityVector3Raw(); - sgMapper.GlossinessFactor = specGloss.GlossinessFactor; - - if (specGloss.SpecularGlossinessTexture != null) - { - int textureId = specGloss.SpecularGlossinessTexture.Index.Id; - sgMapper.SpecularGlossinessTexture = _assetCache.TextureCache[textureId].Texture; - } - } - - if (def.NormalTexture != null) - { - int textureId = def.NormalTexture.Index.Id; - mapper.NormalTexture = _assetCache.TextureCache[textureId].Texture; - mapper.NormalTexCoord = def.NormalTexture.TexCoord; - mapper.NormalTexScale = def.NormalTexture.Scale; - } - - if (def.OcclusionTexture != null) - { - mapper.OcclusionTexStrength = def.OcclusionTexture.Strength; - int textureId = def.OcclusionTexture.Index.Id; - mapper.OcclusionTexture = _assetCache.TextureCache[textureId].Texture; - } - - if (def.EmissiveTexture != null) - { - int textureId = def.EmissiveTexture.Index.Id; - mapper.EmissiveTexture = _assetCache.TextureCache[textureId].Texture; - mapper.EmissiveTexCoord = def.EmissiveTexture.TexCoord; - } - - mapper.EmissiveFactor = def.EmissiveFactor.ToUnityColorRaw(); - - var vertColorMapper = mapper.Clone(); - vertColorMapper.VertexColorsEnabled = true; - - MaterialCacheData materialWrapper = new MaterialCacheData - { - UnityMaterial = mapper.Material, - UnityMaterialWithVertexColor = vertColorMapper.Material, - GLTFMaterial = def - }; - - if (materialIndex >= 0) - { - _assetCache.MaterialCache[materialIndex] = materialWrapper; - } - else - { - _defaultLoadedMaterial = materialWrapper; - } - } - - protected virtual int GetTextureSourceId(GLTFTexture texture) - { - return texture.Source.Id; - } - - /// - /// Creates a texture from a glTF texture - /// - /// The texture to load - /// The loaded unity texture - public virtual IEnumerator LoadTexture(GLTFTexture texture, int textureIndex, bool markGpuOnly = true) - { - try - { - lock (this) - { - if (_isRunning) - { - throw new GLTFLoadException("Cannot CreateTexture while GLTFSceneImporter is already running"); - } - - _isRunning = true; - } - - if (_assetCache == null) - { - InitializeAssetCache(); - } - yield return ConstructImageBuffer(texture, GetTextureSourceId(texture)); - yield return ConstructTexture(texture, textureIndex, markGpuOnly); - } - finally - { - lock (this) - { - _isRunning = false; - } - } - } - - /// - /// Gets texture that has been loaded from CreateTexture - /// - /// The texture to get - /// Created texture - public virtual Texture GetTexture(int textureIndex) - { - if (_assetCache == null) - { - throw new GLTFLoadException("Asset cache needs initialized before calling GetTexture"); - } - - if (_assetCache.TextureCache[textureIndex] == null) - { - return null; - } - - return _assetCache.TextureCache[textureIndex].Texture; - } - - protected virtual IEnumerator ConstructTexture(GLTFTexture texture, int textureIndex, - bool markGpuOnly = true) - { - if (_assetCache.TextureCache[textureIndex].Texture == null) - { - int sourceId = GetTextureSourceId(texture); - GLTFImage image = _gltfRoot.Images[sourceId]; - yield return ConstructImage(image, sourceId, markGpuOnly); - - var source = _assetCache.ImageCache[sourceId]; - var desiredFilterMode = FilterMode.Bilinear; - var desiredWrapMode = TextureWrapMode.Repeat; - - if (texture.Sampler != null) - { - var sampler = texture.Sampler.Value; - switch (sampler.MinFilter) - { - case MinFilterMode.Nearest: - case MinFilterMode.NearestMipmapNearest: - case MinFilterMode.NearestMipmapLinear: - desiredFilterMode = FilterMode.Point; - break; - case MinFilterMode.Linear: - case MinFilterMode.LinearMipmapNearest: - case MinFilterMode.LinearMipmapLinear: - desiredFilterMode = FilterMode.Bilinear; - break; - default: - Debug.LogWarning("Unsupported Sampler.MinFilter: " + sampler.MinFilter); - break; - } - - switch (sampler.WrapS) - { - case GLTF.Schema.WrapMode.ClampToEdge: - desiredWrapMode = TextureWrapMode.Clamp; - break; - case GLTF.Schema.WrapMode.Repeat: - default: - desiredWrapMode = TextureWrapMode.Repeat; - break; - } - } - - if (source.filterMode == desiredFilterMode && source.wrapMode == desiredWrapMode) - { - _assetCache.TextureCache[textureIndex].Texture = source; - } - else - { - var unityTexture = Object.Instantiate(source); - unityTexture.filterMode = desiredFilterMode; - unityTexture.wrapMode = desiredWrapMode; - - _assetCache.TextureCache[textureIndex].Texture = unityTexture; - } - - yield return null; - } - } - - protected virtual IEnumerator ConstructImageFromGLB(GLTFImage image, int imageCacheIndex) - { - var texture = new Texture2D(0, 0); - var bufferView = image.BufferView.Value; - var data = new byte[bufferView.ByteLength]; - - var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id]; - bufferContents.Stream.Position = bufferView.ByteOffset + bufferContents.ChunkOffset; - bufferContents.Stream.Read(data, 0, data.Length); - texture.LoadImage(data); - - _assetCache.ImageCache[imageCacheIndex] = texture; - yield return null; - } - - protected virtual BufferCacheData ConstructBufferFromGLB(int bufferIndex) - { - GLTFParser.SeekToBinaryChunk(_gltfStream.Stream, bufferIndex, _gltfStream.StartPosition); // sets stream to correct start position - return new BufferCacheData - { - Stream = _gltfStream.Stream, - ChunkOffset = _gltfStream.Stream.Position - }; - } - - protected virtual void ApplyTextureTransform(TextureInfo def, Material mat, string texName) - { - IExtension extension; - if (_gltfRoot.ExtensionsUsed != null && - _gltfRoot.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME) && - def.Extensions != null && - def.Extensions.TryGetValue(ExtTextureTransformExtensionFactory.EXTENSION_NAME, out extension)) - { - ExtTextureTransformExtension ext = (ExtTextureTransformExtension)extension; - - Vector2 temp = ext.Offset.ToUnityVector2Raw(); - temp = new Vector2(temp.x, -temp.y); - mat.SetTextureOffset(texName, temp); - - mat.SetTextureScale(texName, ext.Scale.ToUnityVector2Raw()); - } - } - - /// - /// Get the absolute path to a gltf uri reference. - /// - /// The path to the gltf file - /// A path without the filename or extension - protected static string AbsoluteUriPath(string gltfPath) - { - var uri = new Uri(gltfPath); - var partialPath = uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - uri.Query.Length - uri.Segments[uri.Segments.Length - 1].Length); - return partialPath; - } - - /// - /// Get the absolute path a gltf file directory - /// - /// The path to the gltf file - /// A path without the filename or extension - protected static string AbsoluteFilePath(string gltfPath) - { - var fileName = Path.GetFileName(gltfPath); - var lastIndex = gltfPath.IndexOf(fileName); - var partialPath = gltfPath.Substring(0, lastIndex); - return partialPath; - } - - /// - /// Cleans up any undisposed streams after loading a scene or a node. - /// - private void Cleanup() - { - _assetCache.Dispose(); - _assetCache = null; - } - } -} +using GLTF; +using GLTF.Schema; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using UnityEngine; +using UnityGLTF.Cache; +using UnityGLTF.Extensions; +using UnityGLTF.Loader; +using Matrix4x4 = GLTF.Math.Matrix4x4; +using Object = UnityEngine.Object; +using ThreadPriority = System.Threading.ThreadPriority; +using WrapMode = UnityEngine.WrapMode; + +#if WINDOWS_UWP +using System.Threading.Tasks; +#endif + +namespace UnityGLTF +{ + public struct MeshConstructionData + { + public MeshPrimitive Primitive { get; set; } + public Dictionary MeshAttributes { get; set; } + } + + public class GLTFSceneImporter : IDisposable + { + public enum ColliderType + { + None, + Box, + Mesh, + MeshConvex + } + + /// + /// Maximum LOD + /// + public int MaximumLod = 300; + + /// + /// Timeout for certain threading operations + /// + public int Timeout = 8; + + /// + /// Use Multithreading or not + /// + public bool isMultithreaded = false; + + /// + /// The parent transform for the created GameObject + /// + public Transform SceneParent { get; set; } + + /// + /// The last created object + /// + public GameObject CreatedObject { get; private set; } + + /// + /// Adds colliders to primitive objects when created + /// + public ColliderType Collider { get; set; } + + /// + /// Override for the shader to use on created materials + /// + public string CustomShaderName { get; set; } + + protected struct GLBStream + { + public Stream Stream; + public long StartPosition; + } + + protected GameObject _lastLoadedScene; + protected readonly GLTFMaterial DefaultMaterial = new GLTFMaterial(); + protected MaterialCacheData _defaultLoadedMaterial = null; + + protected string _gltfFileName; + protected GLBStream _gltfStream; + protected GLTFRoot _gltfRoot; + protected AssetCache _assetCache; + protected ILoader _loader; + private bool _isRunning = false; + + + /// + /// Creates a GLTFSceneBuilder object which will be able to construct a scene based off a url + /// + /// glTF file relative to data loader path + /// + public GLTFSceneImporter(string gltfFileName, ILoader externalDataLoader) : this(externalDataLoader) + { + _gltfFileName = gltfFileName; + } + + public GLTFSceneImporter(GLTFRoot rootNode, ILoader externalDataLoader, Stream gltfStream = null) : this(externalDataLoader) + { + _gltfRoot = rootNode; + _loader = externalDataLoader; + if (gltfStream != null) _gltfStream = new GLBStream {Stream = gltfStream, StartPosition = gltfStream.Position}; + } + + private GLTFSceneImporter(ILoader externalDataLoader) + { + _loader = externalDataLoader; + } + + public void Dispose() + { + if (_assetCache != null) + { + Cleanup(); + } + } + + public GameObject LastLoadedScene + { + get { return _lastLoadedScene; } + } + + /// + /// Loads a glTF Scene into the LastLoadedScene field + /// + /// The scene to load, If the index isn't specified, we use the default index in the file. Failing that we load index 0. + /// Callback function for when load is completed + /// + public IEnumerator LoadScene(int sceneIndex = -1, Action onLoadComplete = null) + { + try + { + lock (this) + { + if (_isRunning) + { + throw new GLTFLoadException("Cannot call LoadScene while GLTFSceneImporter is already running"); + } + + _isRunning = true; + } + + if (_gltfRoot == null) + { + yield return LoadJson(_gltfFileName); + } + yield return _LoadScene(sceneIndex); + + Cleanup(); + } + finally + { + lock (this) + { + _isRunning = false; + } + } + + if (onLoadComplete != null) + { + onLoadComplete(LastLoadedScene); + } + } + + /// + /// Loads a node tree from a glTF file into the LastLoadedScene field + /// + /// The node index to load from the glTF + /// + public IEnumerator LoadNode(int nodeIndex) + { + if (_gltfRoot == null) + { + throw new InvalidOperationException("GLTF root must first be loaded and parsed"); + } + + try + { + lock (this) + { + if (_isRunning) + { + throw new GLTFLoadException("Cannot call LoadNode while GLTFSceneImporter is already running"); + } + + _isRunning = true; + } + + if (_assetCache == null) + { + InitializeAssetCache(); + } + + yield return _LoadNode(nodeIndex); + CreatedObject = _assetCache.NodeCache[nodeIndex]; + InitializeGltfTopLevelObject(); + + // todo: optimially the asset cache can be reused between nodes + Cleanup(); + } + finally + { + lock (this) + { + _isRunning = false; + } + } + } + + /// + /// Initializes the top-level created node by adding an instantiated GLTF object component to it, + /// so that it can cleanup after itself properly when destroyed + /// + private void InitializeGltfTopLevelObject() + { + InstantiatedGLTFObject instantiatedGltfObject = CreatedObject.AddComponent(); + instantiatedGltfObject.CachedData = new RefCountedCacheData + { + MaterialCache = _assetCache.MaterialCache, + MeshCache = _assetCache.MeshCache, + TextureCache = _assetCache.TextureCache + }; + } + + private IEnumerator ConstructBufferData(Node node) + { + MeshId mesh = node.Mesh; + if (mesh != null) + { + if (mesh.Value.Primitives != null) + { + yield return ConstructMeshAttributes(mesh.Value, mesh); + } + } + + if (node.Children != null) + { + foreach (NodeId child in node.Children) + { + yield return ConstructBufferData(child.Value); + } + } + } + + private IEnumerator ConstructMeshAttributes(GLTFMesh mesh, MeshId meshId) + { + int meshIdIndex = meshId.Id; + + if (_assetCache.MeshCache[meshIdIndex] == null) + { + _assetCache.MeshCache[meshIdIndex] = new MeshCacheData[mesh.Primitives.Count]; + } + + for (int i = 0; i < mesh.Primitives.Count; ++i) + { + MeshPrimitive primitive = mesh.Primitives[i]; + + if (_assetCache.MeshCache[meshIdIndex][i] == null) + { + _assetCache.MeshCache[meshIdIndex][i] = new MeshCacheData(); + } + + if (_assetCache.MeshCache[meshIdIndex][i].MeshAttributes.Count == 0) + { + yield return ConstructMeshAttributes(primitive, meshIdIndex, i); + if (primitive.Material != null) + { + yield return ConstructMaterialImageBuffers(primitive.Material.Value); + } + } + } + } + + protected IEnumerator ConstructImageBuffer(GLTFTexture texture, int textureIndex) + { + int sourceId = GetTextureSourceId(texture); + if (_assetCache.ImageStreamCache[sourceId] == null) + { + GLTFImage image = _gltfRoot.Images[sourceId]; + + // we only load the streams if not a base64 uri, meaning the data is in the uri + if (image.Uri != null && !URIHelper.IsBase64Uri(image.Uri)) + { + yield return _loader.LoadStream(image.Uri); + _assetCache.ImageStreamCache[sourceId] = _loader.LoadedStream; + } + } + + _assetCache.TextureCache[textureIndex] = new TextureCacheData + { + TextureDefinition = texture + }; + } + + private IEnumerator LoadJson(string jsonFilePath) + { + if (isMultithreaded && _loader.HasSyncLoadMethod) + { + Thread loadThread = new Thread(() => _loader.LoadStreamSync(jsonFilePath)); + loadThread.Priority = ThreadPriority.Highest; + loadThread.Start(); + yield return new WaitUntil(() => !loadThread.IsAlive); + } + else + { + yield return _loader.LoadStream(jsonFilePath); + } + + _gltfStream.Stream = _loader.LoadedStream; + _gltfStream.StartPosition = 0; + + if (isMultithreaded) + { + Thread parseJsonThread = new Thread(() => GLTFParser.ParseJson(_gltfStream.Stream, out _gltfRoot, _gltfStream.StartPosition)); + parseJsonThread.Priority = ThreadPriority.Highest; + parseJsonThread.Start(); + yield return new WaitUntil(() => !parseJsonThread.IsAlive); + } + else + { + GLTFParser.ParseJson(_gltfStream.Stream, out _gltfRoot, _gltfStream.StartPosition); + yield return null; + } + } + + private IEnumerator _LoadNode(int nodeIndex) + { + if (nodeIndex >= _gltfRoot.Nodes.Count) + { + throw new ArgumentException("nodeIndex is out of range"); + } + + Node nodeToLoad = _gltfRoot.Nodes[nodeIndex]; + yield return ConstructBufferData(nodeToLoad); + yield return ConstructNode(nodeToLoad, nodeIndex); + } + + + protected void InitializeAssetCache() + { + _assetCache = new AssetCache( + _gltfRoot.Images != null ? _gltfRoot.Images.Count : 0, + _gltfRoot.Textures != null ? _gltfRoot.Textures.Count : 0, + _gltfRoot.Materials != null ? _gltfRoot.Materials.Count : 0, + _gltfRoot.Buffers != null ? _gltfRoot.Buffers.Count : 0, + _gltfRoot.Meshes != null ? _gltfRoot.Meshes.Count : 0, + _gltfRoot.Nodes != null ? _gltfRoot.Nodes.Count : 0, + _gltfRoot.Animations != null ? _gltfRoot.Animations.Count : 0 + ); + } + + /// + /// Creates a scene based off loaded JSON. Includes loading in binary and image data to construct the meshes required. + /// + /// The bufferIndex of scene in gltf file to load + /// + protected IEnumerator _LoadScene(int sceneIndex = -1) + { + GLTFScene scene; + InitializeAssetCache(); // asset cache currently needs initialized every time due to cleanup logic + + if (sceneIndex >= 0 && sceneIndex < _gltfRoot.Scenes.Count) + { + scene = _gltfRoot.Scenes[sceneIndex]; + } + else + { + scene = _gltfRoot.GetDefaultScene(); + } + + if (scene == null) + { + throw new GLTFLoadException("No default scene in gltf file."); + } + + if (_lastLoadedScene == null) + { + if (_gltfRoot.Buffers != null) + { + // todo add fuzzing to verify that buffers are before uri + for (int i = 0; i < _gltfRoot.Buffers.Count; ++i) + { + GLTFBuffer buffer = _gltfRoot.Buffers[i]; + if (_assetCache.BufferCache[i] == null) + { + yield return ConstructBuffer(buffer, i); + } + } + } + + if (_gltfRoot.Textures != null) + { + for (int i = 0; i < _gltfRoot.Textures.Count; ++i) + { + if (_assetCache.TextureCache[i] == null) + { + GLTFTexture texture = _gltfRoot.Textures[i]; + yield return ConstructImageBuffer(texture, i); + yield return ConstructImage(texture.Source.Value, texture.Source.Id); + } + } + } + yield return ConstructAttributesForMeshes(); + } + + yield return ConstructScene(scene); + + if (SceneParent != null) + { + CreatedObject.transform.SetParent(SceneParent, false); + } + + _lastLoadedScene = CreatedObject; + } + + protected IEnumerator ConstructBuffer(GLTFBuffer buffer, int bufferIndex) + { + if (buffer.Uri == null) + { + _assetCache.BufferCache[bufferIndex] = ConstructBufferFromGLB(bufferIndex); + } + else + { + Stream bufferDataStream = null; + var uri = buffer.Uri; + + byte[] bufferData; + URIHelper.TryParseBase64(uri, out bufferData); + if (bufferData != null) + { + bufferDataStream = new MemoryStream(bufferData, 0, bufferData.Length, false, true); + } + else + { + yield return _loader.LoadStream(buffer.Uri); + bufferDataStream = _loader.LoadedStream; + } + + _assetCache.BufferCache[bufferIndex] = new BufferCacheData + { + Stream = bufferDataStream + }; + } + } + + protected IEnumerator ConstructImage(GLTFImage image, int imageCacheIndex, bool markGpuOnly = false, bool linear = true) + { + if (_assetCache.ImageCache[imageCacheIndex] == null) + { + if (image.BufferView != null) + { + yield return ConstructImageFromGLB(image, imageCacheIndex); + } + else + { + string uri = image.Uri; + + byte[] bufferData; + URIHelper.TryParseBase64(uri, out bufferData); + if (bufferData != null) + { + Texture2D loadedTexture = new Texture2D(0, 0); + loadedTexture.LoadImage(bufferData, true); + + _assetCache.ImageCache[imageCacheIndex] = loadedTexture; + yield return null; + } + else + { + Stream stream = _assetCache.ImageStreamCache[imageCacheIndex]; + yield return ConstructUnityTexture(stream, markGpuOnly, linear, image, imageCacheIndex); + } + } + } + } + + protected virtual IEnumerator ConstructUnityTexture(Stream stream, bool markGpuOnly, bool linear, GLTFImage image, int imageCacheIndex) + { + Texture2D texture = new Texture2D(0, 0, TextureFormat.RGBA32, true, linear); + + if (stream is MemoryStream) + { + using (MemoryStream memoryStream = stream as MemoryStream) + { + // NOTE: the second parameter of LoadImage() marks non-readable, but we can't mark it until after we call Apply() + texture.LoadImage(memoryStream.ToArray(), false); + } + + yield return null; + } + else + { + byte[] buffer = new byte[stream.Length]; + + // todo: potential optimization is to split stream read into multiple frames (or put it on a thread?) + using (stream) + { + if (stream.Length > int.MaxValue) + { + throw new Exception("Stream is larger than can be copied into byte array"); + } + + if (isMultithreaded) + { + Thread readThread = new Thread(() => stream.Read(buffer, 0, (int)stream.Length)); + readThread.Priority = ThreadPriority.Highest; + readThread.Start(); + yield return new WaitUntil(() => !readThread.IsAlive); + } + else + { + stream.Read(buffer, 0, (int)stream.Length); + yield return null; + } + } + + // NOTE: the second parameter of LoadImage() marks non-readable, but we can't mark it until after we call Apply() + texture.LoadImage(buffer, false); + yield return null; + } + + // After we conduct the Apply(), then we can make the texture non-readable and never create a CPU copy + texture.Apply(true, markGpuOnly); + + _assetCache.ImageCache[imageCacheIndex] = texture; + yield return null; + } + + protected virtual IEnumerator ConstructAttributesForMeshes() + { + for (int i = 0; i < _gltfRoot.Meshes.Count; ++i) + { + GLTFMesh mesh = _gltfRoot.Meshes[i]; + if (_assetCache.MeshCache[i] == null) + { + _assetCache.MeshCache[i] = new MeshCacheData[mesh.Primitives.Count]; + } + + for (int j = 0; j < mesh.Primitives.Count; ++j) + { + _assetCache.MeshCache[i][j] = new MeshCacheData(); + var primitive = mesh.Primitives[j]; + yield return ConstructMeshAttributes(primitive, i, j); + if (primitive.Material != null) + { + yield return ConstructMaterialImageBuffers(primitive.Material.Value); + } + } + } + } + + protected virtual IEnumerator ConstructMeshAttributes(MeshPrimitive primitive, int meshID, int primitiveIndex) + { + if (_assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes.Count == 0) + { + Dictionary attributeAccessors = new Dictionary(primitive.Attributes.Count + 1); + foreach (var attributePair in primitive.Attributes) + { + BufferId bufferIdPair = attributePair.Value.Value.BufferView.Value.Buffer; + GLTFBuffer buffer = bufferIdPair.Value; + int bufferId = bufferIdPair.Id; + + // on cache miss, load the buffer + if (_assetCache.BufferCache[bufferId] == null) + { + yield return ConstructBuffer(buffer, bufferId); + } + + AttributeAccessor attributeAccessor = new AttributeAccessor + { + AccessorId = attributePair.Value, + Stream = _assetCache.BufferCache[bufferId].Stream, + Offset = (uint)_assetCache.BufferCache[bufferId].ChunkOffset + }; + + attributeAccessors[attributePair.Key] = attributeAccessor; + } + + if (primitive.Indices != null) + { + int bufferId = primitive.Indices.Value.BufferView.Value.Buffer.Id; + AttributeAccessor indexBuilder = new AttributeAccessor + { + AccessorId = primitive.Indices, + Stream = _assetCache.BufferCache[bufferId].Stream, + Offset = (uint)_assetCache.BufferCache[bufferId].ChunkOffset + }; + + attributeAccessors[SemanticProperties.INDICES] = indexBuilder; + } + + if (isMultithreaded) + { + Thread buildMeshAttributesThread = new Thread(() => GLTFHelpers.BuildMeshAttributes(ref attributeAccessors)); + buildMeshAttributesThread.Priority = ThreadPriority.Highest; + buildMeshAttributesThread.Start(); + while (!buildMeshAttributesThread.Join(Timeout)) + { + yield return null; + } + } + else + { + GLTFHelpers.BuildMeshAttributes(ref attributeAccessors); + } + + TransformAttributes(ref attributeAccessors); + _assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes = attributeAccessors; + } + } + + + protected void TransformAttributes(ref Dictionary attributeAccessors) + { + // Flip vectors and triangles to the Unity coordinate system. + if (attributeAccessors.ContainsKey(SemanticProperties.POSITION)) + { + AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.POSITION]; + SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale); + } + if (attributeAccessors.ContainsKey(SemanticProperties.INDICES)) + { + AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.INDICES]; + SchemaExtensions.FlipFaces(ref attributeAccessor); + } + if (attributeAccessors.ContainsKey(SemanticProperties.NORMAL)) + { + AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.NORMAL]; + SchemaExtensions.ConvertVector3CoordinateSpace(ref attributeAccessor, SchemaExtensions.CoordinateSpaceConversionScale); + } + // TexCoord goes from 0 to 3 to match GLTFHelpers.BuildMeshAttributes + for (int i = 0; i < 4; i++) + { + if (attributeAccessors.ContainsKey(SemanticProperties.TexCoord(i))) + { + AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.TexCoord(i)]; + SchemaExtensions.FlipTexCoordArrayV(ref attributeAccessor); + } + } + if (attributeAccessors.ContainsKey(SemanticProperties.TANGENT)) + { + AttributeAccessor attributeAccessor = attributeAccessors[SemanticProperties.TANGENT]; + SchemaExtensions.ConvertVector4CoordinateSpace(ref attributeAccessor, SchemaExtensions.TangentSpaceConversionScale); + } + } + + #region Animation + static string RelativePathFrom(Transform self, Transform root) + { + var path = new List(); + for (var current = self; current != null; current = current.parent) + { + if (current == root) + { + return String.Join("/", path.ToArray()); + } + + path.Insert(0, current.name); + } + + throw new Exception("no RelativePath"); + } + + protected virtual void BuildAnimationSamplers(GLTFAnimation animation, int animationId) + { + // look up expected data types + var typeMap = new Dictionary(); + foreach (var channel in animation.Channels) + { + typeMap[channel.Sampler.Id] = channel.Target.Path.ToString(); + } + + var samplers = _assetCache.AnimationCache[animationId].Samplers; + var samplersByType = new Dictionary> + { + {"time", new List(animation.Samplers.Count)} + }; + + for (var i = 0; i < animation.Samplers.Count; i++) + { + // no sense generating unused samplers + if (!typeMap.ContainsKey(i)) + { + continue; + } + + var samplerDef = animation.Samplers[i]; + + // set up input accessors + BufferCacheData bufferCacheData = _assetCache.BufferCache[samplerDef.Input.Value.BufferView.Value.Buffer.Id]; + AttributeAccessor attributeAccessor = new AttributeAccessor + { + AccessorId = samplerDef.Input, + Stream = bufferCacheData.Stream, + Offset = bufferCacheData.ChunkOffset + }; + + samplers[i].Input = attributeAccessor; + samplersByType["time"].Add(attributeAccessor); + + // set up output accessors + bufferCacheData = _assetCache.BufferCache[samplerDef.Output.Value.BufferView.Value.Buffer.Id]; + attributeAccessor = new AttributeAccessor + { + AccessorId = samplerDef.Output, + Stream = bufferCacheData.Stream, + Offset = bufferCacheData.ChunkOffset + }; + + samplers[i].Output = attributeAccessor; + + if (!samplersByType.ContainsKey(typeMap[i])) + { + samplersByType[typeMap[i]] = new List(); + } + + samplersByType[typeMap[i]].Add(attributeAccessor); + } + + // populate attributeAccessors with buffer data + GLTFHelpers.BuildAnimationSamplers(ref samplersByType); + } + + AnimationClip ConstructClip(Transform root, Transform[] nodes, int animationId) + { + var animation = _gltfRoot.Animations[animationId]; + + var animationCache = _assetCache.AnimationCache[animationId]; + if (animationCache == null) + { + animationCache = new AnimationCacheData(animation.Samplers.Count); + _assetCache.AnimationCache[animationId] = animationCache; + } + else if (animationCache.LoadedAnimationClip != null) + return animationCache.LoadedAnimationClip; + + // unpack accessors + BuildAnimationSamplers(animation, animationId); + + // init clip + var clip = new AnimationClip + { + name = animation.Name ?? String.Format("animation:{0}", animationId) + }; + _assetCache.AnimationCache[animationId].LoadedAnimationClip = clip; + + // needed because Animator component is unavailable at runtime + clip.legacy = true; + + foreach (var channel in animation.Channels) + { + var samplerCache = animationCache.Samplers[channel.Sampler.Id]; + var node = nodes[channel.Target.Node.Id]; + var relativePath = RelativePathFrom(node, root); + AnimationCurve curveX = new AnimationCurve(), + curveY = new AnimationCurve(), + curveZ = new AnimationCurve(), + curveW = new AnimationCurve(); + NumericArray input = samplerCache.Input.AccessorContent, + output = samplerCache.Output.AccessorContent; + + switch (channel.Target.Path) + { + case GLTFAnimationChannelPath.translation: + for (var i = 0; i < input.AsFloats.Length; ++i) + { + var time = input.AsFloats[i]; + Vector3 position = output.AsVec3s[i].ToUnityVector3Convert(); + curveX.AddKey(time, position.x); + curveY.AddKey(time, position.y); + curveZ.AddKey(time, position.z); + } + + clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", curveX); + clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", curveY); + clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", curveZ); + break; + + case GLTFAnimationChannelPath.rotation: + for (int i = 0; i < input.AsFloats.Length; ++i) + { + var time = input.AsFloats[i]; + var rotation = output.AsVec4s[i]; + + Quaternion rot = new GLTF.Math.Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W).ToUnityQuaternionConvert(); + curveX.AddKey(time, rot.x); + curveY.AddKey(time, rot.y); + curveZ.AddKey(time, rot.z); + curveW.AddKey(time, rot.w); + } + + clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", curveX); + clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", curveY); + clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", curveZ); + clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", curveW); + break; + + case GLTFAnimationChannelPath.scale: + for (var i = 0; i < input.AsFloats.Length; ++i) + { + var time = input.AsFloats[i]; + Vector3 scale = output.AsVec3s[i].ToUnityVector3Raw(); + curveX.AddKey(time, scale.x); + curveY.AddKey(time, scale.y); + curveZ.AddKey(time, scale.z); + } + + clip.SetCurve(relativePath, typeof(Transform), "localScale.x", curveX); + clip.SetCurve(relativePath, typeof(Transform), "localScale.y", curveY); + clip.SetCurve(relativePath, typeof(Transform), "localScale.z", curveZ); + break; + + case GLTFAnimationChannelPath.weights: + var primitives = channel.Target.Node.Value.Mesh.Value.Primitives; + var targetCount = primitives[0].Targets.Count; + for (int primitiveIndex = 0; primitiveIndex < primitives.Count; primitiveIndex++) + { + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) + { + // TODO: add support for blend shapes/morph targets + //clip.SetCurve(primitiveObjPath, typeof(SkinnedMeshRenderer), "blendShape." + targetIndex, curves[targetIndex]); + } + } + break; + + default: + Debug.LogWarning("Cannot read GLTF animation path"); + break; + } // switch target type + } // foreach channel + + clip.EnsureQuaternionContinuity(); + return clip; + } + #endregion + + protected virtual IEnumerator ConstructScene(GLTFScene scene) + { + var sceneObj = new GameObject(string.IsNullOrEmpty(scene.Name) ? ("GLTFScene") : scene.Name); + + Transform[] nodeTransforms = new Transform[scene.Nodes.Count]; + for (int i = 0; i < scene.Nodes.Count; ++i) + { + NodeId node = scene.Nodes[i]; + yield return ConstructNode(node.Value, node.Id); + GameObject nodeObj = _assetCache.NodeCache[node.Id]; + nodeObj.transform.SetParent(sceneObj.transform, false); + nodeTransforms[i] = nodeObj.transform; + } + + if (_gltfRoot.Animations != null && _gltfRoot.Animations.Count > 0) + { + // create the AnimationClip that will contain animation data + Animation animation = sceneObj.AddComponent(); + for (int i = 0; i < _gltfRoot.Animations.Count; ++i) + { + AnimationClip clip = ConstructClip(sceneObj.transform, _assetCache.NodeCache.Select(x => x.transform).ToArray(), i); + + clip.wrapMode = WrapMode.Loop; + + animation.AddClip(clip, clip.name); + if (i == 0) + { + animation.clip = clip; + } + } + } + + CreatedObject = sceneObj; + InitializeGltfTopLevelObject(); + } + + + protected virtual IEnumerator ConstructNode(Node node, int nodeIndex) + { + if (_assetCache.NodeCache[nodeIndex] != null) + { + yield break; + } + + var nodeObj = new GameObject(string.IsNullOrEmpty(node.Name) ? ("GLTFNode" + nodeIndex) : node.Name); + // If we're creating a really large node, we need it to not be visible in partial stages. So we hide it while we create it + nodeObj.SetActive(false); + + Vector3 position; + Quaternion rotation; + Vector3 scale; + node.GetUnityTRSProperties(out position, out rotation, out scale); + nodeObj.transform.localPosition = position; + nodeObj.transform.localRotation = rotation; + nodeObj.transform.localScale = scale; + + if (node.Mesh != null) + { + yield return ConstructMesh(node.Mesh.Value, nodeObj.transform, node.Mesh.Id, node.Skin != null ? node.Skin.Value : null); + } + /* TODO: implement camera (probably a flag to disable for VR as well) + if (camera != null) + { + GameObject cameraObj = camera.Value.Create(); + cameraObj.transform.parent = nodeObj.transform; + } + */ + + if (node.Children != null) + { + foreach (var child in node.Children) + { + // todo blgross: replace with an iterartive solution + yield return ConstructNode(child.Value, child.Id); + GameObject childObj = _assetCache.NodeCache[child.Id]; + childObj.transform.SetParent(nodeObj.transform, false); + } + } + + nodeObj.SetActive(true); + _assetCache.NodeCache[nodeIndex] = nodeObj; + } + + private bool NeedsSkinnedMeshRenderer(MeshPrimitive primitive, Skin skin) + { + return HasBones(skin) || HasBlendShapes(primitive); + } + + private bool HasBones(Skin skin) + { + return skin != null; + } + + private bool HasBlendShapes(MeshPrimitive primitive) + { + return primitive.Targets != null; + } + + protected virtual IEnumerator SetupBones(Skin skin, MeshPrimitive primitive, SkinnedMeshRenderer renderer, GameObject primitiveObj, Mesh curMesh) + { + var boneCount = skin.Joints.Count; + Transform[] bones = new Transform[boneCount]; + + int bufferId = skin.InverseBindMatrices.Value.BufferView.Value.Buffer.Id; + AttributeAccessor attributeAccessor = new AttributeAccessor + { + AccessorId = skin.InverseBindMatrices, + Stream = _assetCache.BufferCache[bufferId].Stream, + Offset = _assetCache.BufferCache[bufferId].ChunkOffset + }; + + GLTFHelpers.BuildBindPoseSamplers(ref attributeAccessor); + + Matrix4x4[] gltfBindPoses = attributeAccessor.AccessorContent.AsMatrix4x4s; + UnityEngine.Matrix4x4[] bindPoses = new UnityEngine.Matrix4x4[skin.Joints.Count]; + + for (int i = 0; i < boneCount; i++) + { + if (_assetCache.NodeCache[skin.Joints[i].Id] == null) + { + yield return ConstructNode(_gltfRoot.Nodes[skin.Joints[i].Id], skin.Joints[i].Id); + } + bones[i] = _assetCache.NodeCache[skin.Joints[i].Id].transform; + bindPoses[i] = gltfBindPoses[i].ToUnityMatrix4x4Convert(); + } + + renderer.rootBone = _assetCache.NodeCache[skin.Skeleton.Id].transform; + curMesh.bindposes = bindPoses; + renderer.bones = bones; + + yield return null; + } + + private BoneWeight[] CreateBoneWeightArray(Vector4[] joints, Vector4[] weights, int vertCount) + { + NormalizeBoneWeightArray(weights); + + BoneWeight[] boneWeights = new BoneWeight[vertCount]; + for (int i = 0; i < vertCount; i++) + { + boneWeights[i].boneIndex0 = (int)joints[i].x; + boneWeights[i].boneIndex1 = (int)joints[i].y; + boneWeights[i].boneIndex2 = (int)joints[i].z; + boneWeights[i].boneIndex3 = (int)joints[i].w; + + boneWeights[i].weight0 = weights[i].x; + boneWeights[i].weight1 = weights[i].y; + boneWeights[i].weight2 = weights[i].z; + boneWeights[i].weight3 = weights[i].w; + } + + return boneWeights; + } + + /// + /// Ensures each bone weight influences applied to the vertices add up to 1 + /// + /// Bone weight array + private void NormalizeBoneWeightArray(Vector4[] weights) + { + for (int i = 0; i < weights.Length; i++) + { + var weightSum = (weights[i].x + weights[i].y + weights[i].z + weights[i].w); + + if (!Mathf.Approximately(weightSum, 0)) + { + weights[i] /= weightSum; + } + } + } + + protected virtual IEnumerator ConstructMesh(GLTFMesh mesh, Transform parent, int meshId, Skin skin) + { + if (_assetCache.MeshCache[meshId] == null) + { + _assetCache.MeshCache[meshId] = new MeshCacheData[mesh.Primitives.Count]; + } + + for (int i = 0; i < mesh.Primitives.Count; ++i) + { + var primitive = mesh.Primitives[i]; + int materialIndex = primitive.Material != null ? primitive.Material.Id : -1; + + yield return ConstructMeshPrimitive(primitive, meshId, i, materialIndex); + + var primitiveObj = new GameObject("Primitive"); + + MaterialCacheData materialCacheData = + materialIndex >= 0 ? _assetCache.MaterialCache[materialIndex] : _defaultLoadedMaterial; + + Material material = materialCacheData.GetContents(primitive.Attributes.ContainsKey(SemanticProperties.Color(0))); + + Mesh curMesh = _assetCache.MeshCache[meshId][i].LoadedMesh; + if (NeedsSkinnedMeshRenderer(primitive, skin)) + { + var skinnedMeshRenderer = primitiveObj.AddComponent(); + skinnedMeshRenderer.material = material; + skinnedMeshRenderer.quality = SkinQuality.Auto; + // TODO: add support for blend shapes/morph targets + //if (HasBlendShapes(primitive)) + // SetupBlendShapes(primitive); + if (HasBones(skin)) + { + yield return SetupBones(skin, primitive, skinnedMeshRenderer, primitiveObj, curMesh); + } + + skinnedMeshRenderer.sharedMesh = curMesh; + } + else + { + var meshRenderer = primitiveObj.AddComponent(); + meshRenderer.material = material; + } + + MeshFilter meshFilter = primitiveObj.AddComponent(); + meshFilter.sharedMesh = curMesh; + + switch (Collider) + { + case ColliderType.Box: + var boxCollider = primitiveObj.AddComponent(); + boxCollider.center = curMesh.bounds.center; + boxCollider.size = curMesh.bounds.size; + break; + case ColliderType.Mesh: + var meshCollider = primitiveObj.AddComponent(); + meshCollider.sharedMesh = curMesh; + break; + case ColliderType.MeshConvex: + var meshConvexCollider = primitiveObj.AddComponent(); + meshConvexCollider.sharedMesh = curMesh; + meshConvexCollider.convex = true; + break; + } + + primitiveObj.transform.SetParent(parent, false); + primitiveObj.SetActive(true); + } + } + + + protected virtual IEnumerator ConstructMeshPrimitive(MeshPrimitive primitive, int meshID, int primitiveIndex, int materialIndex) + { + if (_assetCache.MeshCache[meshID][primitiveIndex] == null) + { + _assetCache.MeshCache[meshID][primitiveIndex] = new MeshCacheData(); + } + if (_assetCache.MeshCache[meshID][primitiveIndex].LoadedMesh == null) + { + var meshAttributes = _assetCache.MeshCache[meshID][primitiveIndex].MeshAttributes; + var meshConstructionData = new MeshConstructionData + { + Primitive = primitive, + MeshAttributes = meshAttributes + }; + + yield return null; + yield return ConstructUnityMesh(meshConstructionData, meshID, primitiveIndex); + } + + bool shouldUseDefaultMaterial = primitive.Material == null; + + GLTFMaterial materialToLoad = shouldUseDefaultMaterial ? DefaultMaterial : primitive.Material.Value; + if ((shouldUseDefaultMaterial && _defaultLoadedMaterial == null) || + (!shouldUseDefaultMaterial && _assetCache.MaterialCache[materialIndex] == null)) + { + yield return ConstructMaterialTextures(materialToLoad); + ConstructMaterial(materialToLoad, materialIndex); + } + } + + + protected virtual IEnumerator ConstructMaterialImageBuffers(GLTFMaterial def) + { + if (def.PbrMetallicRoughness != null) + { + var pbr = def.PbrMetallicRoughness; + + if (pbr.BaseColorTexture != null) + { + var textureId = pbr.BaseColorTexture.Index; + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + if (pbr.MetallicRoughnessTexture != null) + { + var textureId = pbr.MetallicRoughnessTexture.Index; + + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + } + + if (def.CommonConstant != null) + { + if (def.CommonConstant.LightmapTexture != null) + { + var textureId = def.CommonConstant.LightmapTexture.Index; + + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + } + + if (def.NormalTexture != null) + { + var textureId = def.NormalTexture.Index; + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + + if (def.OcclusionTexture != null) + { + var textureId = def.OcclusionTexture.Index; + + if (!(def.PbrMetallicRoughness != null + && def.PbrMetallicRoughness.MetallicRoughnessTexture != null + && def.PbrMetallicRoughness.MetallicRoughnessTexture.Index.Id == textureId.Id)) + { + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + } + + if (def.EmissiveTexture != null) + { + var textureId = def.EmissiveTexture.Index; + yield return ConstructImageBuffer(textureId.Value, textureId.Id); + } + } + + protected virtual IEnumerator ConstructMaterialTextures(GLTFMaterial def) + { + for (int i = 0; i < _assetCache.TextureCache.Length; ++i) + { + TextureCacheData textureCacheData = _assetCache.TextureCache[i]; + if (textureCacheData != null && textureCacheData.Texture == null) + { + yield return ConstructTexture(textureCacheData.TextureDefinition, i); + } + } + } + + protected IEnumerator ConstructUnityMesh(MeshConstructionData meshConstructionData, int meshId, int primitiveIndex) + { + MeshPrimitive primitive = meshConstructionData.Primitive; + var meshAttributes = meshConstructionData.MeshAttributes; + int vertexCount = (int)primitive.Attributes[SemanticProperties.POSITION].Value.Count; + + bool hasNormals = primitive.Attributes.ContainsKey(SemanticProperties.NORMAL); + // todo optimize: There are multiple copies being performed to turn the buffer data into mesh data. Look into reducing them + Mesh mesh = new Mesh + { +#if UNITY_2017_3_OR_NEWER + indexFormat = vertexCount > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, +#endif + vertices = primitive.Attributes.ContainsKey(SemanticProperties.POSITION) + ? meshAttributes[SemanticProperties.POSITION].AccessorContent.AsVertices.ToUnityVector3Raw() + : null, + normals = primitive.Attributes.ContainsKey(SemanticProperties.NORMAL) + ? meshAttributes[SemanticProperties.NORMAL].AccessorContent.AsNormals.ToUnityVector3Raw() + : null, + + uv = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(0)) + ? meshAttributes[SemanticProperties.TexCoord(0)].AccessorContent.AsTexcoords.ToUnityVector2Raw() + : null, + + uv2 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(1)) + ? meshAttributes[SemanticProperties.TexCoord(1)].AccessorContent.AsTexcoords.ToUnityVector2Raw() + : null, + + uv3 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(2)) + ? meshAttributes[SemanticProperties.TexCoord(2)].AccessorContent.AsTexcoords.ToUnityVector2Raw() + : null, + + uv4 = primitive.Attributes.ContainsKey(SemanticProperties.TexCoord(3)) + ? meshAttributes[SemanticProperties.TexCoord(3)].AccessorContent.AsTexcoords.ToUnityVector2Raw() + : null, + + colors = primitive.Attributes.ContainsKey(SemanticProperties.Color(0)) + ? meshAttributes[SemanticProperties.Color(0)].AccessorContent.AsColors.ToUnityColorRaw() + : null, + + triangles = primitive.Indices != null + ? meshAttributes[SemanticProperties.INDICES].AccessorContent.AsUInts.ToIntArrayRaw() + : MeshPrimitive.GenerateTriangles(vertexCount), + + tangents = primitive.Attributes.ContainsKey(SemanticProperties.TANGENT) + ? meshAttributes[SemanticProperties.TANGENT].AccessorContent.AsTangents.ToUnityVector4Raw() + : null, + + boneWeights = meshAttributes.ContainsKey(SemanticProperties.Weight(0)) && meshAttributes.ContainsKey(SemanticProperties.Joint(0)) + ? CreateBoneWeightArray(meshAttributes[SemanticProperties.Joint(0)].AccessorContent.AsVec4s.ToUnityVector4Raw(), + meshAttributes[SemanticProperties.Weight(0)].AccessorContent.AsVec4s.ToUnityVector4Raw(), vertexCount) + : null + }; + + yield return null; + + if (!hasNormals) + { + mesh.RecalculateNormals(); + yield return null; + } + + mesh.RecalculateTangents(); + yield return null; + + _assetCache.MeshCache[meshId][primitiveIndex].LoadedMesh = mesh; + } + + + protected virtual void ConstructMaterial(GLTFMaterial def, int materialIndex) + { + IUniformMap mapper; + const string specGlossExtName = KHR_materials_pbrSpecularGlossinessExtensionFactory.EXTENSION_NAME; + if (_gltfRoot.ExtensionsUsed != null && _gltfRoot.ExtensionsUsed.Contains(specGlossExtName) + && def.Extensions != null && def.Extensions.ContainsKey(specGlossExtName)) + { + if (!string.IsNullOrEmpty(CustomShaderName)) + { + mapper = new SpecGlossMap(CustomShaderName, MaximumLod); + } + else + { + mapper = new SpecGlossMap(MaximumLod); + } + } + else + { + if (!string.IsNullOrEmpty(CustomShaderName)) + { + mapper = new MetalRoughMap(CustomShaderName, MaximumLod); + } + else + { + mapper = new MetalRoughMap(MaximumLod); + } + } + + mapper.AlphaMode = def.AlphaMode; + mapper.DoubleSided = def.DoubleSided; + + var mrMapper = mapper as IMetalRoughUniformMap; + if (def.PbrMetallicRoughness != null && mrMapper != null) + { + var pbr = def.PbrMetallicRoughness; + + mrMapper.BaseColorFactor = pbr.BaseColorFactor.ToUnityColorRaw(); + + if (pbr.BaseColorTexture != null) + { + int textureId = pbr.BaseColorTexture.Index.Id; + mrMapper.BaseColorTexture = _assetCache.TextureCache[textureId].Texture; + mrMapper.BaseColorTexCoord = pbr.BaseColorTexture.TexCoord; + + //ApplyTextureTransform(pbr.BaseColorTexture, material, "_MainTex"); + } + + mrMapper.MetallicFactor = pbr.MetallicFactor; + + if (pbr.MetallicRoughnessTexture != null) + { + int textureId = pbr.MetallicRoughnessTexture.Index.Id; + mrMapper.MetallicRoughnessTexture = _assetCache.TextureCache[textureId].Texture; + mrMapper.MetallicRoughnessTexCoord = pbr.MetallicRoughnessTexture.TexCoord; + + //ApplyTextureTransform(pbr.MetallicRoughnessTexture, material, "_MetallicRoughnessMap"); + } + + mrMapper.RoughnessFactor = pbr.RoughnessFactor; + } + + var sgMapper = mapper as ISpecGlossUniformMap; + if (sgMapper != null) + { + var specGloss = def.Extensions[specGlossExtName] as KHR_materials_pbrSpecularGlossinessExtension; + + sgMapper.DiffuseFactor = specGloss.DiffuseFactor.ToUnityColorRaw(); + + if (specGloss.DiffuseTexture != null) + { + int textureId = specGloss.DiffuseTexture.Index.Id; + sgMapper.DiffuseTexture = _assetCache.TextureCache[textureId].Texture; + sgMapper.DiffuseTexCoord = specGloss.DiffuseTexture.TexCoord; + + //ApplyTextureTransform(specGloss.DiffuseTexture, material, "_MainTex"); + } + + sgMapper.SpecularFactor = specGloss.SpecularFactor.ToUnityVector3Raw(); + sgMapper.GlossinessFactor = specGloss.GlossinessFactor; + + if (specGloss.SpecularGlossinessTexture != null) + { + int textureId = specGloss.SpecularGlossinessTexture.Index.Id; + sgMapper.SpecularGlossinessTexture = _assetCache.TextureCache[textureId].Texture; + } + } + + if (def.NormalTexture != null) + { + int textureId = def.NormalTexture.Index.Id; + mapper.NormalTexture = _assetCache.TextureCache[textureId].Texture; + mapper.NormalTexCoord = def.NormalTexture.TexCoord; + mapper.NormalTexScale = def.NormalTexture.Scale; + } + + if (def.OcclusionTexture != null) + { + mapper.OcclusionTexStrength = def.OcclusionTexture.Strength; + int textureId = def.OcclusionTexture.Index.Id; + mapper.OcclusionTexture = _assetCache.TextureCache[textureId].Texture; + } + + if (def.EmissiveTexture != null) + { + int textureId = def.EmissiveTexture.Index.Id; + mapper.EmissiveTexture = _assetCache.TextureCache[textureId].Texture; + mapper.EmissiveTexCoord = def.EmissiveTexture.TexCoord; + } + + mapper.EmissiveFactor = def.EmissiveFactor.ToUnityColorRaw(); + + var vertColorMapper = mapper.Clone(); + vertColorMapper.VertexColorsEnabled = true; + + MaterialCacheData materialWrapper = new MaterialCacheData + { + UnityMaterial = mapper.Material, + UnityMaterialWithVertexColor = vertColorMapper.Material, + GLTFMaterial = def + }; + + if (materialIndex >= 0) + { + _assetCache.MaterialCache[materialIndex] = materialWrapper; + } + else + { + _defaultLoadedMaterial = materialWrapper; + } + } + + + protected virtual int GetTextureSourceId(GLTFTexture texture) + { + return texture.Source.Id; + } + + /// + /// Creates a texture from a glTF texture + /// + /// The texture to load + /// The loaded unity texture + public virtual IEnumerator LoadTexture(GLTFTexture texture, int textureIndex, bool markGpuOnly = true) + { + try + { + lock (this) + { + if (_isRunning) + { + throw new GLTFLoadException("Cannot CreateTexture while GLTFSceneImporter is already running"); + } + + _isRunning = true; + } + + if (_assetCache == null) + { + InitializeAssetCache(); + } + yield return ConstructImageBuffer(texture, GetTextureSourceId(texture)); + yield return ConstructTexture(texture, textureIndex, markGpuOnly); + } + finally + { + lock (this) + { + _isRunning = false; + } + } + } + + /// + /// Gets texture that has been loaded from CreateTexture + /// + /// The texture to get + /// Created texture + public virtual Texture GetTexture(int textureIndex) + { + if (_assetCache == null) + { + throw new GLTFLoadException("Asset cache needs initialized before calling GetTexture"); + } + + if (_assetCache.TextureCache[textureIndex] == null) + { + return null; + } + + return _assetCache.TextureCache[textureIndex].Texture; + } + + protected virtual IEnumerator ConstructTexture(GLTFTexture texture, int textureIndex, + bool markGpuOnly = false) + { + if (_assetCache.TextureCache[textureIndex].Texture == null) + { + int sourceId = GetTextureSourceId(texture); + GLTFImage image = _gltfRoot.Images[sourceId]; + yield return ConstructImage(image, sourceId, markGpuOnly); + + var source = _assetCache.ImageCache[sourceId]; + var desiredFilterMode = FilterMode.Bilinear; + var desiredWrapMode = TextureWrapMode.Repeat; + + if (texture.Sampler != null) + { + var sampler = texture.Sampler.Value; + switch (sampler.MinFilter) + { + case MinFilterMode.Nearest: + case MinFilterMode.NearestMipmapNearest: + case MinFilterMode.NearestMipmapLinear: + desiredFilterMode = FilterMode.Point; + break; + case MinFilterMode.Linear: + case MinFilterMode.LinearMipmapNearest: + case MinFilterMode.LinearMipmapLinear: + desiredFilterMode = FilterMode.Bilinear; + break; + default: + Debug.LogWarning("Unsupported Sampler.MinFilter: " + sampler.MinFilter); + break; + } + + switch (sampler.WrapS) + { + case GLTF.Schema.WrapMode.ClampToEdge: + desiredWrapMode = TextureWrapMode.Clamp; + break; + case GLTF.Schema.WrapMode.Repeat: + default: + desiredWrapMode = TextureWrapMode.Repeat; + break; + } + } + + if (source.filterMode == desiredFilterMode && source.wrapMode == desiredWrapMode) + { + _assetCache.TextureCache[textureIndex].Texture = source; + } + else + { + var unityTexture = Object.Instantiate(source); + unityTexture.filterMode = desiredFilterMode; + unityTexture.wrapMode = desiredWrapMode; + + _assetCache.TextureCache[textureIndex].Texture = unityTexture; + } + + yield return null; + } + } + + protected virtual IEnumerator ConstructImageFromGLB(GLTFImage image, int imageCacheIndex) + { + var texture = new Texture2D(0, 0); + var bufferView = image.BufferView.Value; + var data = new byte[bufferView.ByteLength]; + + var bufferContents = _assetCache.BufferCache[bufferView.Buffer.Id]; + bufferContents.Stream.Position = bufferView.ByteOffset + bufferContents.ChunkOffset; + bufferContents.Stream.Read(data, 0, data.Length); + texture.LoadImage(data); + + _assetCache.ImageCache[imageCacheIndex] = texture; + yield return null; + } + + protected virtual BufferCacheData ConstructBufferFromGLB(int bufferIndex) + { + GLTFParser.SeekToBinaryChunk(_gltfStream.Stream, bufferIndex, _gltfStream.StartPosition); // sets stream to correct start position + return new BufferCacheData + { + Stream = _gltfStream.Stream, + ChunkOffset = (uint)_gltfStream.Stream.Position + }; + } + + protected virtual void ApplyTextureTransform(TextureInfo def, Material mat, string texName) + { + IExtension extension; + if (_gltfRoot.ExtensionsUsed != null && + _gltfRoot.ExtensionsUsed.Contains(ExtTextureTransformExtensionFactory.EXTENSION_NAME) && + def.Extensions != null && + def.Extensions.TryGetValue(ExtTextureTransformExtensionFactory.EXTENSION_NAME, out extension)) + { + ExtTextureTransformExtension ext = (ExtTextureTransformExtension)extension; + + Vector2 temp = ext.Offset.ToUnityVector2Raw(); + temp = new Vector2(temp.x, -temp.y); + mat.SetTextureOffset(texName, temp); + + mat.SetTextureScale(texName, ext.Scale.ToUnityVector2Raw()); + } + } + + + /// + /// Get the absolute path to a gltf uri reference. + /// + /// The path to the gltf file + /// A path without the filename or extension + protected static string AbsoluteUriPath(string gltfPath) + { + var uri = new Uri(gltfPath); + var partialPath = uri.AbsoluteUri.Remove(uri.AbsoluteUri.Length - uri.Query.Length - uri.Segments[uri.Segments.Length - 1].Length); + return partialPath; + } + + /// + /// Get the absolute path a gltf file directory + /// + /// The path to the gltf file + /// A path without the filename or extension + protected static string AbsoluteFilePath(string gltfPath) + { + var fileName = Path.GetFileName(gltfPath); + var lastIndex = gltfPath.IndexOf(fileName); + var partialPath = gltfPath.Substring(0, lastIndex); + return partialPath; + } + + /// + /// Cleans up any undisposed streams after loading a scene or a node. + /// + private void Cleanup() + { + _assetCache.Dispose(); + _assetCache = null; + } + } +} diff --git a/UnityGLTF/Assets/UnityGLTF/Scripts/InstantiatedGLTFObject.cs b/UnityGLTF/Assets/UnityGLTF/Scripts/InstantiatedGLTFObject.cs index cf92f31ab..6b4faf97d 100644 --- a/UnityGLTF/Assets/UnityGLTF/Scripts/InstantiatedGLTFObject.cs +++ b/UnityGLTF/Assets/UnityGLTF/Scripts/InstantiatedGLTFObject.cs @@ -53,6 +53,7 @@ public InstantiatedGLTFObject Duplicate() InstantiatedGLTFObject newGltfObjectComponent = duplicatedObject.GetComponent(); newGltfObjectComponent.CachedData = CachedData; + CachedData.IncreaseRefCount(); return newGltfObjectComponent; } diff --git a/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardInput.cginc b/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardInput.cginc index 09f354de0..166b70543 100644 --- a/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardInput.cginc +++ b/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardInput.cginc @@ -1,3 +1,5 @@ +// Upgrade NOTE: replaced 'UNITY_INSTANCE_ID' with 'UNITY_VERTEX_INPUT_INSTANCE_ID' + // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) #ifndef UNITY_STANDARD_INPUT_INCLUDED @@ -68,7 +70,7 @@ struct VertexInput #if defined(UNITY_VERTEX_INPUT_INSTANCE_ID) UNITY_VERTEX_INPUT_INSTANCE_ID #else - UNITY_INSTANCE_ID + UNITY_VERTEX_INPUT_INSTANCE_ID #endif }; diff --git a/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardShadow.cginc b/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardShadow.cginc index 3c7b89d71..23772cd11 100644 --- a/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardShadow.cginc +++ b/UnityGLTF/Assets/UnityGLTF/Shaders/UnityStandardShadow.cginc @@ -1,3 +1,5 @@ +// Upgrade NOTE: replaced 'UNITY_INSTANCE_ID' with 'UNITY_VERTEX_INPUT_INSTANCE_ID' + // Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) #ifndef UNITY_STANDARD_SHADOW_INCLUDED @@ -87,7 +89,7 @@ struct VertexInput #if defined(UNITY_VERTEX_INPUT_INSTANCE_ID) UNITY_VERTEX_INPUT_INSTANCE_ID #else - UNITY_INSTANCE_ID + UNITY_VERTEX_INPUT_INSTANCE_ID #endif }; diff --git a/UnityGLTF/Assets/UnityTestTools.meta b/UnityGLTF/Assets/UnityTestTools.meta index 777c141dc..c718fe24e 100644 --- a/UnityGLTF/Assets/UnityTestTools.meta +++ b/UnityGLTF/Assets/UnityTestTools.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: 522dc3305856fca4f9b0278a89851a3f +guid: d19cdec650da7ac41b5c6ea73dea4676 folderAsset: yes -timeCreated: 1493833516 +timeCreated: 1537993541 licenseType: Pro DefaultImporter: userData: diff --git a/UnityGLTF/ProjectSettings/ProjectSettings.asset b/UnityGLTF/ProjectSettings/ProjectSettings.asset index fb9bd94a5..3b1db3a9c 100644 --- a/UnityGLTF/ProjectSettings/ProjectSettings.asset +++ b/UnityGLTF/ProjectSettings/ProjectSettings.asset @@ -3,9 +3,10 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 14 productGUID: 3f4dc7395d1f2ff42b654788bc1f374e AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 @@ -14,7 +15,7 @@ PlayerSettings: productName: GLTFLoader defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} - m_SplashScreenBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21176471, a: 1} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 @@ -38,8 +39,6 @@ PlayerSettings: width: 1 height: 1 m_SplashScreenLogos: [] - m_SplashScreenBackgroundLandscape: {fileID: 0} - m_SplashScreenBackgroundPortrait: {fileID: 0} m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 @@ -49,7 +48,6 @@ PlayerSettings: m_StereoRenderingPath: 0 m_ActiveColorSpace: 1 m_MTRendering: 1 - m_MobileMTRendering: 0 m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 @@ -63,13 +61,19 @@ PlayerSettings: allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 + androidBlitType: 0 defaultIsFullScreen: 1 defaultIsNativeResolution: 1 + macRetinaSupport: 1 runInBackground: 0 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 bakeCollisionMeshes: 0 @@ -88,19 +92,22 @@ PlayerSettings: allowFullscreenSwitch: 1 graphicsJobMode: 0 macFullscreenMode: 2 - d3d9FullscreenMode: 1 d3d11FullscreenMode: 1 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 n3dsDisableStereoscopicView: 0 n3dsEnableSharedListOpt: 1 n3dsEnableVSync: 0 - ignoreAlphaClear: 0 xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOnePresentImmediateThreshold: 0 videoMemoryForVertexBuffers: 0 psp2PowerMode: 0 psp2AcquireBGM: 1 @@ -122,6 +129,7 @@ PlayerSettings: bundleVersion: 1.0 preloadedAssets: [] metroInputSource: 0 + wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 0 xboxOneEnable7thCore: 0 @@ -132,10 +140,23 @@ PlayerSettings: daydream: depthFormat: 0 useSustainedPerformanceMode: 0 + enableVideoLayer: 0 + useProtectedVideoMemory: 0 + minimumSupportedHeadTracking: 0 + maximumSupportedHeadTracking: 1 hololens: depthFormat: 1 + depthBufferSharingEnabled: 0 + oculus: + sharedDepthBuffer: 0 + dashSupport: 0 protectGraphicsMemory: 0 useHDRDisplay: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 applicationIdentifier: Android: com.Company.ProductName Standalone: unity.DefaultCompany.GLTFLoader @@ -178,15 +199,22 @@ PlayerSettings: iPhone47inSplashScreen: {fileID: 0} iPhone55inPortraitSplashScreen: {fileID: 0} iPhone55inLandscapeSplashScreen: {fileID: 0} + iPhone58inPortraitSplashScreen: {fileID: 0} + iPhone58inLandscapeSplashScreen: {fileID: 0} iPadPortraitSplashScreen: {fileID: 0} iPadHighResPortraitSplashScreen: {fileID: 0} iPadLandscapeSplashScreen: {fileID: 0} iPadHighResLandscapeSplashScreen: {fileID: 0} appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} @@ -204,6 +232,8 @@ PlayerSettings: iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: + iOSUseLaunchScreenStoryboard: 0 + iOSLaunchScreenCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] iOSBackgroundModes: 0 @@ -215,6 +245,7 @@ PlayerSettings: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: appleEnableAutomaticSigning: 0 + clonedFromGUID: 00000000000000000000000000000000 AndroidTargetDevice: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} @@ -222,7 +253,9 @@ PlayerSettings: AndroidKeyaliasName: AndroidTVCompatibility: 1 AndroidIsGame: 1 + AndroidEnableTango: 0 androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 m_AndroidBanners: - width: 320 height: 180 @@ -236,6 +269,7 @@ PlayerSettings: m_Icon: {fileID: 0} m_Width: 128 m_Height: 128 + m_Kind: 39095 m_BuildTargetBatching: [] m_BuildTargetGraphicsAPIs: [] m_BuildTargetVRSettings: @@ -293,10 +327,20 @@ PlayerSettings: - m_BuildTarget: tvOS m_Enabled: 0 m_Devices: [] + m_BuildTargetEnableVuforiaSettings: [] openGLRequireES31: 0 openGLRequireES31AEP: 0 - webPlayerTemplate: APPLICATION:Default m_TemplateCustomTags: {} + mobileMTRendering: + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: + - m_BuildTarget: Standalone + m_EncodingQuality: 1 + - m_BuildTarget: XboxOne + m_EncodingQuality: 1 + - m_BuildTarget: PS4 + m_EncodingQuality: 1 wiiUTitleID: 0005000011000000 wiiUGroupID: 00010000 wiiUCommonSaveSize: 4096 @@ -327,6 +371,7 @@ PlayerSettings: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchApplicationID: 0x0005000C10000001 switchNSODependencies: @@ -405,7 +450,7 @@ PlayerSettings: switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 - switchAttribute: 0 + switchApplicationAttribute: 0 switchCardSpecSize: 4 switchCardSpecClock: 25 switchRatingsMask: 0 @@ -431,7 +476,21 @@ PlayerSettings: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 + switchSupportedNpadStyles: 3 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchPlayerConnectionEnabled: 1 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: @@ -450,6 +509,8 @@ PlayerSettings: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: @@ -469,7 +530,6 @@ PlayerSettings: ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ - ps4UseDebugIl2cppLibs: 0 ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 @@ -544,7 +604,7 @@ PlayerSettings: psp2UseLibLocation: 0 psp2InfoBarOnStartup: 0 psp2InfoBarColor: 0 - psp2UseDebugIl2cppLibs: 0 + psp2ScriptOptimizationLevel: 0 psmSplashimage: {fileID: 0} splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} @@ -575,11 +635,12 @@ PlayerSettings: Standalone: 0 incrementalIl2cppBuild: {} additionalIl2CppArgs: + scriptingRuntimeVersion: 1 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: GLTFLoader - metroPackageVersion: + metroPackageVersion: 1.0.0.0 metroCertificatePath: metroCertificatePassword: metroCertificateSubject: @@ -599,7 +660,33 @@ PlayerSettings: metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 - platformCapabilities: {} + platformCapabilities: + WindowsStoreApps: + AllJoyn: False + BlockedChatMessages: False + Bluetooth: False + Chat: False + CodeGeneration: False + EnterpriseAuthentication: False + HumanInterfaceDevice: False + InputInjectionBrokered: False + InternetClient: False + InternetClientServer: False + Location: False + Microphone: False + MusicLibrary: False + Objects3D: False + PhoneCall: False + PicturesLibrary: False + PrivateNetworkClientServer: False + Proximity: False + RemovableStorage: False + SharedUserCertificates: False + SpatialPerception: False + UserAccountInformation: False + VideosLibrary: False + VoipCall: False + WebCam: False metroFTAName: metroFTAFileTypes: [] metroProtocolName: @@ -623,12 +710,6 @@ PlayerSettings: n3dsTitle: GameName n3dsProductCode: n3dsApplicationId: 0xFF3FF - stvDeviceAddress: - stvProductDescription: - stvProductAuthor: - stvProductAuthorEmail: - stvProductLink: - stvProductCategory: 0 XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: @@ -672,4 +753,5 @@ PlayerSettings: projectName: organizationId: cloudEnabled: 0 - enableNewInputSystem: 0 + enableNativePlatformBackendsForNewInputSystem: 0 + disableOldInputManagerSupport: 0