diff --git a/CHANGELOG.md b/CHANGELOG.md index 9857e2a..d78ecd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [2.5.17] - 2023-08-27 + +- Features + - Enabled to use Stream argument for Load method of VgoImporter. +- Fixes + - [Editor] Fixed a bug that occurred in BlendShape of Scripted Importer. + - [Editor] Fixed a bug in v2.5.16 that caused error when declaring `UNIVGO_USE_UNITASK`. + ## [2.5.16] - 2023-08-24 - Experiments diff --git a/Documentation~/UniVGO/Installation.ja.md b/Documentation~/UniVGO/Installation.ja.md index 8cb53a2..3ed94ce 100644 --- a/Documentation~/UniVGO/Installation.ja.md +++ b/Documentation~/UniVGO/Installation.ja.md @@ -44,7 +44,7 @@ Unity 2021.1 以下のバージョンを使用する場合 |com.izayoi.liltoon.shader.utility|IzayoiJiichan|GitHub||1.4.0|2023年5月30日| |com.izayoi.unishaders|IzayoiJiichan|GitHub||1.6.1|2023年8月1日| |com.izayoi.vgospringbone|IzayoiJiichan|GitHub||1.1.2|2022年8月24日| -|com.izayoi.univgo|IzayoiJiichan|GitHub|VGO 2.5|2.5.16|2023年8月24日| +|com.izayoi.univgo|IzayoiJiichan|GitHub|VGO 2.5|2.5.17|2023年8月27日| #### 追加パッケージ @@ -143,7 +143,7 @@ UniVGOを使用するために、以下の設定を追加してください。 "dependencies": { "com.izayoi.liltoon.shader.utility": "https://github.com/izayoijiichan/lilToonShaderUtility.git#v1.4.0", "com.izayoi.unishaders": "https://github.com/izayoijiichan/UniShaders.git#v1.6.1", - "com.izayoi.univgo": "https://github.com/izayoijiican/VGO.git#v2.5.16", + "com.izayoi.univgo": "https://github.com/izayoijiican/VGO.git#v2.5.17", "com.izayoi.vgospringbone": "https://github.com/izayoijiichan/VgoSpringBone.git#v1.1.2", "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.ugui": "1.0.0", @@ -273,20 +273,20 @@ ___ |UniVRM|UniVGO|min Unity| |:---:|:---:|:---:| -|0.100.0|2.5.16|2020.3| -|0.101.0|2.5.16|2020.3| -|0.102.0|2.5.16|2020.3| -|0.103.2|2.5.16|2020.3| -|0.104.2|2.5.16|2020.3| -|0.105.0|2.5.16|2020.3| -|0.106.0|2.5.16|2020.3| -|0.107.2|2.5.16|2020.3| -|0.108.0|2.5.16|2020.3| -|0.109.0|2.5.16|2020.3| -|0.110.0|2.5.16|2020.3| -|0.111.0|2.5.16|2020.3| -|0.112.0|2.5.16|2021.3| -|0.113.0|2.5.16|2021.3| +|0.100.0|2.5.17|2020.3| +|0.101.0|2.5.17|2020.3| +|0.102.0|2.5.17|2020.3| +|0.103.2|2.5.17|2020.3| +|0.104.2|2.5.17|2020.3| +|0.105.0|2.5.17|2020.3| +|0.106.0|2.5.17|2020.3| +|0.107.2|2.5.17|2020.3| +|0.108.0|2.5.17|2020.3| +|0.109.0|2.5.17|2020.3| +|0.110.0|2.5.17|2020.3| +|0.111.0|2.5.17|2020.3| +|0.112.0|2.5.17|2021.3| +|0.113.0|2.5.17|2021.3| `/Packages/package.json` に以下の記述を行います。 @@ -317,7 +317,7 @@ https://github.com/izayoijiichan/VGO/wiki/How-to-use-UniVRM-and-UniVGO-together |2022.3.0f1|BRP|UniVGO + UniVRM|[Link](https://github.com/izayoijiichan/univgo2.sample.unity.project/tree/unity2022.3.brp.univrm)| ___ -最終更新日:2023年8月24日 +最終更新日:2023年8月27日 編集者:十六夜おじいちゃん *Copyright (C) 2020 Izayoi Jiichan. All Rights Reserved.* diff --git a/Documentation~/UniVGO/Installation.md b/Documentation~/UniVGO/Installation.md index e803b35..c3b8b10 100644 --- a/Documentation~/UniVGO/Installation.md +++ b/Documentation~/UniVGO/Installation.md @@ -44,7 +44,7 @@ This package is required for any Unity version. |com.izayoi.liltoon.shader.utility|IzayoiJiichan|GitHub||1.4.0|30 May, 2023| |com.izayoi.unishaders|IzayoiJiichan|GitHub||1.6.1|1 Aug, 2023| |com.izayoi.vgospringbone|IzayoiJiichan|GitHub||1.1.2|24 Aug, 2022| -|com.izayoi.univgo|IzayoiJiichan|GitHub|VGO 2.5|2.5.16|24 Aug, 2023| +|com.izayoi.univgo|IzayoiJiichan|GitHub|VGO 2.5|2.5.17|27 Aug, 2023| #### Additional Packages @@ -142,7 +142,7 @@ To use UniVGO, add the following settings. "dependencies": { "com.izayoi.liltoon.shader.utility": "https://github.com/izayoijiichan/lilToonShaderUtility.git#v1.4.0", "com.izayoi.unishaders": "https://github.com/izayoijiichan/UniShaders.git#v1.6.1", - "com.izayoi.univgo": "https://github.com/izayoijiican/VGO.git#v2.5.16", + "com.izayoi.univgo": "https://github.com/izayoijiican/VGO.git#v2.5.17", "com.izayoi.vgospringbone": "https://github.com/izayoijiichan/VgoSpringBone.git#v1.1.2", "com.unity.nuget.newtonsoft-json": "3.2.1", "com.unity.ugui": "1.0.0", @@ -270,20 +270,20 @@ The version combinations are as follows. |UniVRM|UniVGO|min Unity| |:---:|:---:|:---:| -|0.100.0|2.5.16|2020.3| -|0.101.0|2.5.16|2020.3| -|0.102.0|2.5.16|2020.3| -|0.103.2|2.5.16|2020.3| -|0.104.2|2.5.16|2020.3| -|0.105.0|2.5.16|2020.3| -|0.106.0|2.5.16|2020.3| -|0.107.2|2.5.16|2020.3| -|0.108.0|2.5.16|2020.3| -|0.109.0|2.5.16|2020.3| -|0.110.0|2.5.16|2020.3| -|0.111.0|2.5.16|2020.3| -|0.112.0|2.5.16|2021.3| -|0.113.0|2.5.16|2021.3| +|0.100.0|2.5.17|2020.3| +|0.101.0|2.5.17|2020.3| +|0.102.0|2.5.17|2020.3| +|0.103.2|2.5.17|2020.3| +|0.104.2|2.5.17|2020.3| +|0.105.0|2.5.17|2020.3| +|0.106.0|2.5.17|2020.3| +|0.107.2|2.5.17|2020.3| +|0.108.0|2.5.17|2020.3| +|0.109.0|2.5.17|2020.3| +|0.110.0|2.5.17|2020.3| +|0.111.0|2.5.17|2020.3| +|0.112.0|2.5.17|2021.3| +|0.113.0|2.5.17|2021.3| Write the following in ` /Packages/package.json`. @@ -313,7 +313,7 @@ https://github.com/izayoijiichan/VGO/wiki/How-to-use-UniVRM-and-UniVGO-together |2022.3.0f1|BRP|UniVGO + UniVRM|[Link](https://github.com/izayoijiichan/univgo2.sample.unity.project/tree/unity2022.3.brp.univrm)| ___ -Last updated: 24 August, 2023 +Last updated: 27 August, 2023 Editor: Izayoi Jiichan *Copyright (C) 2020 Izayoi Jiichan. All Rights Reserved.* diff --git a/NewtonVgo/Runtime/Buffers/ReadOnlyArraySegmentByteBuffer.cs b/NewtonVgo/Runtime/Buffers/ReadOnlyArraySegmentByteBuffer.cs index 9b1fef6..b023e28 100644 --- a/NewtonVgo/Runtime/Buffers/ReadOnlyArraySegmentByteBuffer.cs +++ b/NewtonVgo/Runtime/Buffers/ReadOnlyArraySegmentByteBuffer.cs @@ -25,12 +25,21 @@ public class ReadOnlyArraySegmentByteBuffer : IByteBuffer public int Capacity => _Bytes.Count; /// Gets or sets the current length of the buffer in bytes. - public int Length { get; private set; } + public int Length => _Bytes.Count; #endregion #region Constructors + /// + /// Create a new instance of ReadOnlyArraySegmentByteBuffer with bytes. + /// + /// The data to store in the buffer. + public ReadOnlyArraySegmentByteBuffer(byte[] bytes) + { + _Bytes = new ArraySegment(bytes); + } + /// /// Create a new instance of ReadOnlyArraySegmentByteBuffer with bytes. /// @@ -38,8 +47,6 @@ public class ReadOnlyArraySegmentByteBuffer : IByteBuffer public ReadOnlyArraySegmentByteBuffer(ArraySegment bytes) { _Bytes = bytes; - - Length = bytes.Count; } #endregion diff --git a/NewtonVgo/Runtime/Chunks/VgoReadChunk.cs b/NewtonVgo/Runtime/Chunks/VgoReadChunk.cs new file mode 100644 index 0000000..0cf29f5 --- /dev/null +++ b/NewtonVgo/Runtime/Chunks/VgoReadChunk.cs @@ -0,0 +1,85 @@ +// ---------------------------------------------------------------------- +// @Namespace : NewtonVgo +// @Class : VgoReadChunk +// ---------------------------------------------------------------------- +#nullable enable +namespace NewtonVgo +{ + /// + /// VGO Read Chunk + /// + public class VgoReadChunk + { + #region Fields + + /// The chunk type ID. + public readonly VgoChunkTypeID _TypeId; + + /// Length of the chunk data. + public readonly uint _DataLength; + + /// The chunk data. + public readonly byte[] _ChunkData; + + #endregion + + #region Properties + + /// The chunk type ID. + public VgoChunkTypeID TypeId => _TypeId; + + /// Length of the chunk data. + public uint DataLength => _DataLength; + + /// The chunk data. + public byte[] ChunkData => _ChunkData; + + #endregion + + #region Constructors + + /// + /// Create a new instance of VgoReadChunk. + /// + /// The chunk type ID. + /// The chunk data. + public VgoReadChunk(VgoChunkTypeID chunkTypeId, byte[] chunkData) + { + _TypeId = chunkTypeId; + + _DataLength = (uint)chunkData.Length; + + _ChunkData = chunkData; + } + + /// + /// Create a new instance of VgoReadChunk. + /// + /// The chunk type ID. + /// Length of the chunk data. + /// The chunk data. + public VgoReadChunk(VgoChunkTypeID chunkTypeId, uint chunkDataLength, byte[] chunkData) + { + _TypeId = chunkTypeId; + + _DataLength = chunkDataLength; + + _ChunkData = chunkData; + } + + #endregion + + #region Public Methods + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + public override string ToString() + { + return TypeId.ToString(); + } + + #endregion + } +} diff --git a/UniVgo2/Runtime/Porters/VgoImporter.Async.cs.meta b/NewtonVgo/Runtime/Chunks/VgoReadChunk.cs.meta similarity index 83% rename from UniVgo2/Runtime/Porters/VgoImporter.Async.cs.meta rename to NewtonVgo/Runtime/Chunks/VgoReadChunk.cs.meta index 3c12380..d9c80d7 100644 --- a/UniVgo2/Runtime/Porters/VgoImporter.Async.cs.meta +++ b/NewtonVgo/Runtime/Chunks/VgoReadChunk.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 1bbdfb136f628ac41a3689aa46d69d49 +guid: 251babd0d32b1c84686907ebf3a68243 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/NewtonVgo/Runtime/Extensions/StreamExtensions.cs b/NewtonVgo/Runtime/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..549013c --- /dev/null +++ b/NewtonVgo/Runtime/Extensions/StreamExtensions.cs @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------- +// @Namespace : NewtonVgo +// @Class : StreamExtensions +// ---------------------------------------------------------------------- +#nullable enable +namespace NewtonVgo +{ + using System; + using System.Threading.Tasks; + using System.Threading; + using System.IO; + + /// + /// Stream Extensions + /// + public static class StreamExtensions + { + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The stream. + /// The number of bytes to read. + /// An array of bytes. + public static byte[] ReadBytes(this Stream stream, int count) + { + if (count < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0) + { + return Array.Empty(); + } + + byte[] buffer = new byte[count]; + + int numBytesToRead = count; + + int numBytesRead = 0; + + do + { + int n = stream.Read(buffer, offset: numBytesRead, count: numBytesToRead); + + numBytesRead += n; + + numBytesToRead -= n; + } + while (numBytesToRead > 0); + + if (numBytesRead != count) + { + ThrowHelper.ThrowIOException(); + } + + return buffer; + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The stream. + /// The number of bytes to read. + /// The token to monitor for cancellation requests. + /// An array of bytes. + public static async Task ReadBytesAsync(this Stream stream, int count, CancellationToken cancellationToken) + { + if (count < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0) + { + return Array.Empty(); + } + + byte[] buffer = new byte[count]; + + int numBytesToRead = count; + + int numBytesRead = 0; + + do + { + int n = await stream.ReadAsync(buffer, offset: numBytesRead, count: numBytesToRead, cancellationToken); + + numBytesRead += n; + + numBytesToRead -= n; + } + while (numBytesToRead > 0); + + if (numBytesRead != count) + { + ThrowHelper.ThrowIOException(); + } + + return buffer; + } + } +} diff --git a/NewtonVgo/Runtime/Extensions/StreamExtensions.cs.meta b/NewtonVgo/Runtime/Extensions/StreamExtensions.cs.meta new file mode 100644 index 0000000..8556fa4 --- /dev/null +++ b/NewtonVgo/Runtime/Extensions/StreamExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d71051851feb62459075f31361ba9ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NewtonVgo/Runtime/Storage/IVgoStorage.cs b/NewtonVgo/Runtime/Storage/IVgoStorage.cs index 8ad5586..7f78782 100644 --- a/NewtonVgo/Runtime/Storage/IVgoStorage.cs +++ b/NewtonVgo/Runtime/Storage/IVgoStorage.cs @@ -1,6 +1,6 @@ // ---------------------------------------------------------------------- // @Namespace : NewtonVgo -// @Class : IVgoStorage +// @Interface : IVgoStorage // ---------------------------------------------------------------------- #nullable enable namespace NewtonVgo @@ -8,6 +8,9 @@ namespace NewtonVgo using NewtonVgo.Buffers; using System; using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using System.Threading; /// /// VGO Storage Interface @@ -34,12 +37,6 @@ public interface IVgoStorage /// The resource. IByteBuffer? Resource { get; } - /// The directory path. - string? DirectoryPath { get; } - - /// The timeout seconds of http request. - int HttpTimeoutSeconds { get; set; } - /// Whether spec version is 2.4 or lower. bool IsSpecVersion_2_4_orLower { get; } @@ -57,6 +54,74 @@ public interface IVgoStorage #endregion + #region Methods (Export) + + /// + /// Parse vgo. + /// + /// The file path of the vgo. + /// The file path of the crypt key. + void ParseVgo(in string vgoFilePath, in string? vgkFilePath); + + /// + /// Parse vgo. + /// + /// The vgo bytes. + /// The vgk bytes. + void ParseVgo(in byte[] vgoBytes, in byte[]? vgkBytes); + + /// + /// Parse vgo. + /// + /// The vgo stream. + /// The vgk bytes. + void ParseVgo(in Stream vgoStream, in byte[]? vgkBytes); + + /// + /// Parse vgo. + /// + /// The vgo stream. + /// The vgk stream. + void ParseVgo(in Stream vgoStream, in Stream? vgkStream); + + /// + /// Parse vgo. + /// + /// The file path of the vgo. + /// The file path of the crypt key. + /// The token to monitor for cancellation requests. + /// + Task ParseVgoAsync(string vgoFilePath, string? vgkFilePath, CancellationToken cancellationToken); + + /// + /// Parse vgo. + /// + /// The vgo bytes. + /// The vgk bytes. + /// The token to monitor for cancellation requests. + /// + Task ParseVgoAsync(byte[] vgoBytes, byte[]? vgkBytes, CancellationToken cancellationToken); + + /// + /// Parse vgo. + /// + /// The vgo stream. + /// The vgk bytes. + /// The token to monitor for cancellation requests. + /// + Task ParseVgoAsync(Stream vgoStream, byte[]? vgkBytes, CancellationToken cancellationToken); + + /// + /// Parse vgo. + /// + /// The vgo stream. + /// The vgk stream. + /// The token to monitor for cancellation requests. + /// + Task ParseVgoAsync(Stream vgoStream, Stream? vgkStream, CancellationToken cancellationToken); + + #endregion + #region Methods (Accessor) /// diff --git a/NewtonVgo/Runtime/Storage/VgoStorage.Import.cs b/NewtonVgo/Runtime/Storage/VgoStorage.Import.cs index 73b4f86..bef76b4 100644 --- a/NewtonVgo/Runtime/Storage/VgoStorage.Import.cs +++ b/NewtonVgo/Runtime/Storage/VgoStorage.Import.cs @@ -5,795 +5,267 @@ #nullable enable namespace NewtonVgo { - using Newtonsoft.Json; using NewtonVgo.Buffers; - using NewtonVgo.Security.Cryptography; using System; - using System.Collections.Generic; using System.IO; - using System.Linq; - using System.Net.Http; - using System.Runtime.InteropServices; - using System.Text; + using System.Threading; + using System.Threading.Tasks; /// /// VGO Storage /// public partial class VgoStorage : IVgoStorage { - #region Protected Methods (Import) + #region Public Methods (Sync) /// /// Parse vgo. /// - /// The vgo bytes. - /// The vgk bytes. - /// The vgo layout. - protected virtual void ParseVgo(in byte[] vgoBytes, in byte[]? vgkBytes, out VgoLayout layout) + /// The file path of the vgo. + /// The file path of the crypt key. + public virtual void ParseVgo(in string vgoFilePath, in string? vgkFilePath) { - if (vgoBytes == null) + if (vgoFilePath == null) { #if NET_STANDARD_2_1 - ThrowHelper.ThrowArgumentNullException(nameof(vgoBytes)); + ThrowHelper.ThrowArgumentNullException(nameof(vgoFilePath)); #else - throw new ArgumentNullException(nameof(vgoBytes)); + throw new ArgumentNullException(nameof(vgoFilePath)); #endif } - if (vgoBytes.Any() == false) - { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(vgoBytes), vgoBytes.Length); - } + var vgoFileInfo = new FileInfo(vgoFilePath); - if (vgoBytes.Length < 16) + if (vgoFileInfo.Exists == false) { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(vgoBytes), vgoBytes.Length, min: 16); + ThrowHelper.ThrowFileNotFoundException(vgoFilePath); } - ArraySegment allSegmentBytes = new ArraySegment(vgoBytes); - - // Header - Header = GetHeader(allSegmentBytes); + using var vgoStream = new FileStream(vgoFilePath, FileMode.Open, FileAccess.Read); - if ((Header.IsRequireResourceAccessorExternalCryptKey == 1) && (vgkBytes == null)) + if (vgkFilePath == null) { - ThrowHelper.ThrowException("cryptKey is required."); - } - - // Index chunk - ChunkIndexMap = GetChunkIndexMap(allSegmentBytes, indexByteOffset: 16); - - // Composer chunk - ArraySegment composerChunkDataBytes = ExtractChunkData(VgoChunkTypeID.COMP, ChunkIndexMap, allSegmentBytes); + ParseVgo(vgoStream, vgkStream: null); - VgoComposerChunkData composerChunkData = composerChunkDataBytes - .ToArray() - .ConvertToStructure(); - - // Asset Info chunk - try - { - AssetInfo = ExtractAssetInfo(composerChunkData, ChunkIndexMap, allSegmentBytes); - } - catch - { - throw; + return; } - // Layout chunk - try - { - layout = ExtractLayout(composerChunkData, ChunkIndexMap, allSegmentBytes); - } - catch - { - throw; - } + var vgkFileInfo = new FileInfo(vgkFilePath); - // Resource Accessor chunk - try - { - ResourceAccessors = ExtractResourceAccessor(composerChunkData, ChunkIndexMap, allSegmentBytes, vgkBytes); - } - catch + if (vgkFileInfo.Exists == false) { - throw; + ThrowHelper.ThrowFileNotFoundException(vgkFilePath); } - // Resource chunk - try - { - ArraySegment resourceBytes = ExtractResource(composerChunkData, ChunkIndexMap, allSegmentBytes); + using var vgkStream = new FileStream(vgkFilePath, FileMode.Open, FileAccess.Read); - Resource = new ReadOnlyArraySegmentByteBuffer(resourceBytes); - } - catch - { - throw; - } + ParseVgo(vgoStream, vgkStream); } - #endregion - - #region Protected Methods (Import) Header chunk - /// - /// Get header chunk. + /// Parse vgo. /// - /// The all segment bytes. - /// The header chunk. - protected virtual VgoHeader GetHeader(in ArraySegment allSegmentBytes) + /// The vgo bytes. + /// The vgk bytes. + public virtual void ParseVgo(in byte[] vgoBytes, in byte[]? vgkBytes) { - try - { - int headerSize = Marshal.SizeOf(typeof(VgoHeader)); - - ArraySegment headerSegment = allSegmentBytes.Slice(offset: 0, count: headerSize); + using var vgoStream = new MemoryStream(vgoBytes); - VgoHeader header = headerSegment.ToArray().ConvertToStructure(); - - if (header.Magic != (uint)VgoChunkTypeID.Vgo) - { - ThrowHelper.ThrowFormatException($"Header.Magic: {header.Magic}"); - } - - if (header.DataLength != 8) - { - ThrowHelper.ThrowFormatException($"Header.DataLength: {header.DataLength}"); - } - - if (header.MajorVersion == 0) - { - ThrowHelper.ThrowFormatException($"Header.MajorVersion: {header.MajorVersion}"); - } - - if ((header.GeometryCoordinate != VgoGeometryCoordinate.RightHanded) && - (header.GeometryCoordinate != VgoGeometryCoordinate.LeftHanded)) - { - ThrowHelper.ThrowFormatException($"Header.GeometryCoordinate: {header.GeometryCoordinate}"); - } - - if ((header.UVCoordinate != VgoUVCoordinate.TopLeft) && - (header.UVCoordinate != VgoUVCoordinate.BottomLeft)) - { - ThrowHelper.ThrowFormatException($"Header.UVCoordinate: {header.UVCoordinate}"); - } - - return header; - } - catch - { - throw; - } + ParseVgo(vgoStream, vgkBytes); } - #endregion - - #region Protected Methods (Import) Index chunk - /// - /// Get chunk index map. + /// Parse vgo. /// - /// The all segment bytes. - /// The start position of the index chunk. - /// The chunk index map. - /// - /// Index chunk (8 + 16 * n byte) - /// - protected virtual VgoIndexChunkDataElement[] GetChunkIndexMap(in ArraySegment allSegmentBytes, in int indexByteOffset) + /// The vgo stream. + /// The vgk bytes. + public virtual void ParseVgo(in Stream vgoStream, in byte[]? vgkBytes) { - if (allSegmentBytes.Array is null) - { - ThrowHelper.ThrowArgumentException(nameof(allSegmentBytes)); - - return Array.Empty(); - } - - if (allSegmentBytes.Count == 0) - { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(allSegmentBytes)); - } - - try - { - uint indexChunkTypeId = BitConverter.ToUInt32(allSegmentBytes.Array, indexByteOffset); - - if (indexChunkTypeId != (uint)VgoChunkTypeID.Idx) - { - ThrowHelper.ThrowFormatException($"[IDX] Chunk.ChunkTypeId: {indexChunkTypeId}"); - } - - uint indexChunkDataLength = BitConverter.ToUInt32(allSegmentBytes.Array, indexByteOffset + 4); - - if (indexChunkDataLength == 0) - { - ThrowHelper.ThrowFormatException($"[IDX] Chunk.DataLength: {indexChunkDataLength}"); - } - - int elementSize = Marshal.SizeOf(typeof(VgoIndexChunkDataElement)); + using var vgoStreamReader = new VgoStreamReader(vgoStream, vgkBytes); - if ((indexChunkDataLength % elementSize) != 0) - { - ThrowHelper.ThrowFormatException($"[IDX] Chunk.DataLength: {indexChunkDataLength}"); - } - - int elementCount = (int)indexChunkDataLength / elementSize; - - VgoIndexChunkDataElement[] chunkIndexMap = new VgoIndexChunkDataElement[elementCount]; - - allSegmentBytes - .Slice(offset: indexByteOffset + 8, count: elementSize * elementCount) - .MarshalCopyTo(chunkIndexMap); - - return chunkIndexMap; - } - catch - { - throw; - } + ParseVgoInternal(vgoStreamReader, hasVgk: vgkBytes != null); } /// - /// Get the index chunk data of the specified chunk type ID from the chunk index map. + /// Parse vgo. /// - /// The chunk type ID. - /// The chunk index map. - /// - protected virtual VgoIndexChunkDataElement GetIndexChunkDataElement(VgoChunkTypeID chunkTypeId, in VgoIndexChunkDataElement[] chunkIndexMap) + /// The vgo stream. + /// The vgk stream. + public virtual void ParseVgo(in Stream vgoStream, in Stream? vgkStream) { - try - { - int count = chunkIndexMap.Count(x => x.ChunkTypeId == chunkTypeId); - - if (count == 0) - { - ThrowHelper.ThrowFormatException($"{chunkTypeId} is not define in IDX chunk."); - } - - if (count >= 2) - { - ThrowHelper.ThrowFormatException($"{chunkTypeId} is defined more than once in IDX chunk."); - } - - VgoIndexChunkDataElement chunkInfo = chunkIndexMap.First(x => x.ChunkTypeId == chunkTypeId); + using var vgoStreamReader = new VgoStreamReader(vgoStream, vgkStream); - if (chunkInfo.ByteOffset == 0) - { - ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteOffset: {chunkInfo.ByteOffset}"); - } - - if (chunkInfo.ByteLength == 0) - { - //ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteLength: {chunkInfo.ByteLength}"); - } + ParseVgoInternal(vgoStreamReader, hasVgk: vgkStream != null); + } - //if ((chunkInfo.ByteOffset + chunkInfo.ByteLength) > allBytes.Length) - //{ - // ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteLength: {chunkInfo.ByteLength} is out of the range."); - //} + #endregion - return chunkInfo; - } - catch - { - throw; - } - } + #region Public Methods (Async) /// - /// Extract chunk data from asset info chunk. + /// Parse vgo. /// - /// The chunk type ID. - /// The chunk index map. - /// The all segment bytes. - /// The chunk data bytes. - protected virtual ArraySegment ExtractChunkData(in VgoChunkTypeID chunkTypeId, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes) + /// The file path of the vgo. + /// The file path of the crypt key. + /// The token to monitor for cancellation requests. + /// + public virtual async Task ParseVgoAsync(string vgoFilePath, string? vgkFilePath, CancellationToken cancellationToken) { - if (allSegmentBytes.Array is null) + if (vgoFilePath == null) { - ThrowHelper.ThrowArgumentException(nameof(allSegmentBytes)); - - return new ArraySegment(Array.Empty()); +#if NET_STANDARD_2_1 + ThrowHelper.ThrowArgumentNullException(nameof(vgoFilePath)); +#else + throw new ArgumentNullException(nameof(vgoFilePath)); +#endif } - if (allSegmentBytes.Count == 0) + var vgoFileInfo = new FileInfo(vgoFilePath); + + if (vgoFileInfo.Exists == false) { - ThrowHelper.ThrowArgumentOutOfRangeException(nameof(allSegmentBytes)); + ThrowHelper.ThrowFileNotFoundException(vgoFilePath); } - VgoIndexChunkDataElement chunkIndexInfo = GetIndexChunkDataElement(chunkTypeId, chunkIndexMap); - - uint chunkChunkTypeId = BitConverter.ToUInt32(allSegmentBytes.Array, (int)chunkIndexInfo.ByteOffset); + using var vgoStream = new FileStream(vgoFilePath, FileMode.Open, FileAccess.Read); - if (chunkChunkTypeId != (uint)chunkIndexInfo.ChunkTypeId) + if (vgkFilePath == null) { - ThrowHelper.ThrowFormatException($"[{chunkIndexInfo.ChunkTypeId}] Chunk.ChunkTypeId: {chunkChunkTypeId}"); + await ParseVgoAsync(vgoStream, vgkStream: null, cancellationToken); + + return; } - uint chunkDataLength = BitConverter.ToUInt32(allSegmentBytes.Array, (int)chunkIndexInfo.ByteOffset + 4); + var vgkFileInfo = new FileInfo(vgkFilePath); - if (chunkDataLength == 0) + if (vgkFileInfo.Exists == false) { - ThrowHelper.ThrowFormatException($"[{chunkIndexInfo.ChunkTypeId}] Chunk.DataLength: {chunkDataLength}"); + ThrowHelper.ThrowFileNotFoundException(vgkFilePath); } - ArraySegment chunkData = allSegmentBytes.Slice( - offset: (int)chunkIndexInfo.ByteOffset + 8, - count: (int)chunkDataLength - chunkIndexInfo.BytePadding - ); + using var vgkStream = new FileStream(vgkFilePath, FileMode.Open, FileAccess.Read); - return chunkData; + await ParseVgoAsync(vgoStream, vgkStream, cancellationToken); } - #endregion - - #region Protected Methods (Import) Asset Info chunk - /// - /// Extract asset info from asset info chunk. + /// Parse vgo. /// - /// The composer chunk data. - /// The chunk index map. - /// The all segment bytes. - /// The asset info. - protected virtual VgoAssetInfo? ExtractAssetInfo(in VgoComposerChunkData composerChunkData, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes) + /// The vgo bytes. + /// The vgk bytes. + /// The token to monitor for cancellation requests. + /// + public virtual async Task ParseVgoAsync(byte[] vgoBytes, byte[]? vgkBytes, CancellationToken cancellationToken) { - try - { - ArraySegment assetInfoChunkData = ExtractChunkData(composerChunkData.AssetInfoChunkTypeId, chunkIndexMap, allSegmentBytes); - - VgoAssetInfo? vgoAssetInfo; - - if (composerChunkData.AssetInfoChunkTypeId == VgoChunkTypeID.AIPJ) - { - // JSON - vgoAssetInfo = DeserializeObject(assetInfoChunkData.ToArray(), isBson: false); - } - else if (composerChunkData.AssetInfoChunkTypeId == VgoChunkTypeID.AIPB) - { - // BSON - vgoAssetInfo = DeserializeObject(assetInfoChunkData.ToArray(), isBson: true); - } - else - { - ThrowHelper.ThrowFormatException($"[COMP] AssetInfoChunkTypeId: {composerChunkData.AssetInfoChunkTypeId}"); - - return default; - } - - return vgoAssetInfo; - } - catch (JsonSerializationException) - { - throw; - } - catch - { - throw; - } - } + using var vgoStream = new MemoryStream(vgoBytes); - #endregion - - #region Protected Methods (Import) Layout chunk + await ParseVgoAsync(vgoStream, vgkBytes, cancellationToken); + } /// - /// Extract layout from layout chunk. + /// Parse vgo. /// - /// The composer chunk data. - /// The chunk index map. - /// The all segment bytes. - /// The layout. - protected virtual VgoLayout ExtractLayout(in VgoComposerChunkData composerChunkData, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes) + /// The vgo stream. + /// The vgk bytes. + /// The token to monitor for cancellation requests. + /// + public virtual async Task ParseVgoAsync(Stream vgoStream, byte[]? vgkBytes, CancellationToken cancellationToken) { - try - { - ArraySegment layoutChunkData = ExtractChunkData(composerChunkData.LayoutChunkTypeId, chunkIndexMap, allSegmentBytes); - - VgoLayout? vgoLayout; - - if (composerChunkData.LayoutChunkTypeId == VgoChunkTypeID.LAPJ) - { - // JSON - vgoLayout = DeserializeObject(layoutChunkData.ToArray(), isBson: false); - } - else if (composerChunkData.LayoutChunkTypeId == VgoChunkTypeID.LAPB) - { - // BSON - vgoLayout = DeserializeObject(layoutChunkData.ToArray(), isBson: true); - } - else - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowFormatException($"[COMP] LayoutChunkTypeId: {composerChunkData.LayoutChunkTypeId}"); - - return default; -#else - throw new FormatException($"[COMP] LayoutChunkTypeId: {composerChunkData.LayoutChunkTypeId}"); -#endif - } - - if (vgoLayout is null) - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowFormatException(); - - return default; -#else - throw new FormatException(); -#endif - } + using var vgoStreamReader = new VgoStreamReader(vgoStream, vgkBytes); - return vgoLayout; - } - catch (JsonSerializationException) - { - throw; - } - catch - { - throw; - } + await ParseVgoInternalAsync(vgoStreamReader, hasVgk: vgkBytes != null, cancellationToken); } - #endregion - - #region Protected Methods (Import) Resource Accessor chunk - /// - /// Extract resource accessor from resource accessor chunk. + /// Parse vgo. /// - /// The composer chunk data. - /// The chunk index map. - /// The all segment bytes. - /// The crypt key. - /// List of the resource accessor. - protected virtual List? ExtractResourceAccessor(in VgoComposerChunkData composerChunkData, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes, in byte[]? cryptKey = null) + /// The vgo stream. + /// The vgk stream. + /// The token to monitor for cancellation requests. + /// + public virtual async Task ParseVgoAsync(Stream vgoStream, Stream? vgkStream, CancellationToken cancellationToken) { - VgoChunkTypeID raChunkTypeId = composerChunkData.ResourceAccessorChunkTypeId; - - switch (raChunkTypeId) - { - case VgoChunkTypeID.RAPJ: - case VgoChunkTypeID.RAPB: - case VgoChunkTypeID.RACJ: - case VgoChunkTypeID.RACB: - break; - default: - ThrowHelper.ThrowFormatException($"[COMP] ResourceAccessorChunkTypeId: {raChunkTypeId}"); - break; - } - - ArraySegment raChunkData = ExtractChunkData(raChunkTypeId, chunkIndexMap, allSegmentBytes); - - byte[]? plainJsonOrBson; - - if (raChunkTypeId == VgoChunkTypeID.RAPJ) - { - plainJsonOrBson = raChunkData.ToArray(); - } - else if (raChunkTypeId == VgoChunkTypeID.RAPB) - { - plainJsonOrBson = raChunkData.ToArray(); - } - else if ( - (raChunkTypeId == VgoChunkTypeID.RACJ) || - (raChunkTypeId == VgoChunkTypeID.RACB)) - { - VgoCryptV0? vgoCrypt = GetVgoCrypt(composerChunkData.ResourceAccessorCryptChunkTypeId, chunkIndexMap, allSegmentBytes); - - if (vgoCrypt is null) - { - ThrowHelper.ThrowFormatException($" ResourceAccessorChunkTypeId: {raChunkTypeId}"); - - return null; - } - - try - { - byte[] encryptedJsonOrBson = raChunkData.ToArray(); - - if (vgoCrypt.algorithms == VgoCryptographyAlgorithms.AES) - { - // AES - AesCrypter aesCrypter = new AesCrypter - { - CipherMode = vgoCrypt.cipherMode, - PaddingMode = vgoCrypt.paddingMode, - }; - - byte[] key; - - if (cryptKey == null) - { - if (string.IsNullOrEmpty(vgoCrypt.key)) - { - ThrowHelper.ThrowException("crypt key is unknown."); - - return default; - } - else - { - key = Convert.FromBase64String(vgoCrypt.key); - } - } - else - { - key = cryptKey; - } - - if (key == null) - { - ThrowHelper.ThrowException("crypt key is unknown."); - - return default; - } - - if (string.IsNullOrEmpty(vgoCrypt.iv)) - { - ThrowHelper.ThrowException("iv is unknown."); - - return default; - } - - byte[] iv = Convert.FromBase64String(vgoCrypt.iv); - - plainJsonOrBson = aesCrypter.Decrypt(encryptedJsonOrBson, key, iv); - } - else if (vgoCrypt.algorithms == VgoCryptographyAlgorithms.Base64) - { - // Base64 - string base64String = Encoding.UTF8.GetString(encryptedJsonOrBson); - - plainJsonOrBson = Convert.FromBase64String(base64String); - } - else - { - ThrowHelper.ThrowNotSupportedException($"CryptographyAlgorithms: {vgoCrypt.algorithms}"); - - return default; - } - } - catch (JsonSerializationException) - { - throw; - } - catch - { - throw; - } - } - else - { - ThrowHelper.ThrowFormatException($"[COMP] ResourceAccessorChunkTypeId: {raChunkTypeId}"); - - return default; - } - - List? vgoResourceAccessors = null; - - try - { - if ((raChunkTypeId == VgoChunkTypeID.RAPJ) || - (raChunkTypeId == VgoChunkTypeID.RACJ)) - { - // JSON - vgoResourceAccessors = DeserializeObject>(plainJsonOrBson, isBson: false); - } - else if ( - (raChunkTypeId == VgoChunkTypeID.RAPB) || - (raChunkTypeId == VgoChunkTypeID.RACB)) - { - // BSON - vgoResourceAccessors = DeserializeObject>(plainJsonOrBson, isBson: true, rootValueAsArray: true); - } - } - catch (JsonSerializationException) - { - throw; - } - catch - { - throw; - } + using var vgoStreamReader = new VgoStreamReader(vgoStream, vgkStream); - return vgoResourceAccessors; + await ParseVgoInternalAsync(vgoStreamReader, hasVgk: vgkStream != null, cancellationToken); } #endregion - #region Protected Methods (Import) Resource chunk + #region Protected Methods (Import) /// - /// Extract resource bytes from resource chunk. + /// Parse vgo. /// - /// The composer chunk data. - /// The chunk index map. - /// The all segment bytes. - /// The resource bytes. - protected virtual ArraySegment ExtractResource(in VgoComposerChunkData composerChunkData, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes) + /// The vgo stream reader. + /// Whether or not you have vgk. + protected virtual void ParseVgoInternal(in IVgoStreamReader vgoStreamReader, in bool hasVgk) { - VgoChunkTypeID resourceChunkTypeId = composerChunkData.ResourceChunkTypeId; - - VgoResource? vgoResource; + // Header + Header = vgoStreamReader.ReadHeader(); - try - { - ArraySegment resouceChunkData = ExtractChunkData(resourceChunkTypeId, chunkIndexMap, allSegmentBytes); - - if (resourceChunkTypeId == VgoChunkTypeID.REPb) - { - return resouceChunkData; - } - else if (resourceChunkTypeId == VgoChunkTypeID.REPJ) - { - // JSON - vgoResource = DeserializeObject(resouceChunkData.ToArray(), isBson: false); - } - else if (resourceChunkTypeId == VgoChunkTypeID.REPB) - { - // BSON - vgoResource = DeserializeObject(resouceChunkData.ToArray(), isBson: true); - } - else - { - ThrowHelper.ThrowFormatException($"[COMP] ResourceChunkTypeId: {resourceChunkTypeId}"); - - return default; - } - - if (vgoResource is null) - { - ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] resource is null."); - - return default; - } - } - catch (JsonSerializationException) + if ((Header.IsRequireResourceAccessorExternalCryptKey == 1) && (hasVgk == false)) { - throw; - } - catch - { - throw; + ThrowHelper.ThrowException("cryptKey is required."); } - if (vgoResource.uri is null || vgoResource.uri == string.Empty) - { - ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] uri is null or empty."); - - return default; - } + // Index chunk + ChunkIndexMap = vgoStreamReader.ReadIndexChunk(); - if (vgoResource.byteLength == 0) - { - //ThrowHelper.ThrowFormatException($"[{chunkTypeId}] byteLength: {vgoResource.byteLength}"); - } + // Composer chunk + _ = vgoStreamReader.ReadComposerChunk(); - byte[] resourceBytes = GetUriData(vgoResource.uri); + // Asset Info chunk + AssetInfo = vgoStreamReader.ReadAssetInfo(); - return new ArraySegment(resourceBytes); - } + // Layout chunk + Layout = vgoStreamReader.ReadLayout()!; - #endregion + // Resource Accessor chunk + ResourceAccessors = vgoStreamReader.ReadResourceAccessor(); - #region Protected Methods (Import) Crypt chunk + // Resource chunk + byte[] resourceBytes = vgoStreamReader.ReadResource(); - /// - /// Get the vgo crypt. - /// - /// The crypt chunk type ID. - /// The chunk index map. - /// The all segment bytes. - /// The vgo crypt. - protected virtual VgoCryptV0? GetVgoCrypt(in VgoChunkTypeID cryptChunkTypeId, in VgoIndexChunkDataElement[] chunkIndexMap, in ArraySegment allSegmentBytes) - { - try - { - ArraySegment cryptChunkData = ExtractChunkData(cryptChunkTypeId, chunkIndexMap, allSegmentBytes); - - VgoCryptV0? vgoCrypt; - - if (cryptChunkTypeId == VgoChunkTypeID.CRAJ) - { - // JSON - vgoCrypt = DeserializeObject(cryptChunkData.ToArray(), isBson: false); - } - else if (cryptChunkTypeId == VgoChunkTypeID.CRAB) - { - // BSON - vgoCrypt = DeserializeObject(cryptChunkData.ToArray(), isBson: true); - } - else - { - ThrowHelper.ThrowFormatException($"{nameof(cryptChunkTypeId)}: {cryptChunkTypeId}"); - - return default; - } - - return vgoCrypt; - } - catch (JsonSerializationException) - { - throw; - } - catch - { - throw; - } + Resource = new ReadOnlyArraySegmentByteBuffer(resourceBytes); } - #endregion - - #region Protected Methods (Import) Helper - /// - /// Get URI data. + /// Parse vgo. /// - /// The uri. - /// An byte array of uri data. - protected virtual byte[] GetUriData(in string uri) + /// The vgo stream reader. + /// Whether or not you have vgk. + /// The token to monitor for cancellation requests. + /// + protected virtual async Task ParseVgoInternalAsync(IVgoStreamReader vgoStreamReader, bool hasVgk, CancellationToken cancellationToken) { - ThrowHelper.ThrowExceptionIfArgumentIsNull(nameof(uri), uri); - - if (uri.StartsWith("data:")) - { - ThrowHelper.ThrowNotSupportedException(uri); + // Header + Header = vgoStreamReader.ReadHeader(); - return Array.Empty(); - } - else if ( - uri.StartsWith("http://") || - uri.StartsWith("https://")) + if ((Header.IsRequireResourceAccessorExternalCryptKey == 1) && (hasVgk == false)) { - using HttpClient httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(HttpTimeoutSeconds) }; - using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri); - using HttpResponseMessage response = httpClient.SendAsync(request).GetAwaiter().GetResult(); - - if (response.IsSuccessStatusCode) - { - byte[] byteArray = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); - - return byteArray; - } - else - { - ThrowHelper.ThrowHttpRequestException(response.StatusCode.ToString()); - - return Array.Empty(); - } + ThrowHelper.ThrowException("cryptKey is required."); } - else if (uri.StartsWith("file://")) - { - ThrowHelper.ThrowNotSupportedException(uri); - return Array.Empty(); - } - else - { - if (DirectoryPath == null) - { - ThrowHelper.ThrowException(); + // Index chunk + ChunkIndexMap = vgoStreamReader.ReadIndexChunk(); - return Array.Empty(); - } + // Composer chunk + _ = vgoStreamReader.ReadComposerChunk(); - if (Directory.Exists(DirectoryPath) == false) - { - ThrowHelper.ThrowDirectoryNotFoundException(DirectoryPath); - } + // Asset Info chunk + AssetInfo = vgoStreamReader.ReadAssetInfo(); - string binFilePath = Path.Combine(DirectoryPath, uri); + // Layout chunk + Layout = vgoStreamReader.ReadLayout()!; - if (File.Exists(binFilePath) == false) - { - ThrowHelper.ThrowFileNotFoundException(binFilePath); - } + // Resource Accessor chunk + ResourceAccessors = vgoStreamReader.ReadResourceAccessor(); - byte[] binData = File.ReadAllBytes(binFilePath); + // Resource chunk + byte[] resourceBytes = await vgoStreamReader.ReadResourceAsync(cancellationToken); - return binData; - } + Resource = new ReadOnlyArraySegmentByteBuffer(resourceBytes); } #endregion diff --git a/NewtonVgo/Runtime/Storage/VgoStorage.cs b/NewtonVgo/Runtime/Storage/VgoStorage.cs index 0560d95..8e6ef90 100644 --- a/NewtonVgo/Runtime/Storage/VgoStorage.cs +++ b/NewtonVgo/Runtime/Storage/VgoStorage.cs @@ -8,9 +8,7 @@ namespace NewtonVgo using Newtonsoft.Json; using NewtonVgo.Buffers; using NewtonVgo.Serialization; - using System; using System.Collections.Generic; - using System.IO; using System.Text; /// @@ -64,12 +62,6 @@ public partial class VgoStorage : IVgoStorage /// The resource. public IByteBuffer? Resource { get; protected set; } - /// The directory path. - public string? DirectoryPath { get; protected set; } - - /// The timeout seconds of http request. - public int HttpTimeoutSeconds { get; set; } = 30; - /// Whether spec version is 2.4 or lower. public bool IsSpecVersion_2_4_orLower => (Header.MajorVersion == 2) && (Header.MinorVersion <= 4); @@ -78,63 +70,12 @@ public partial class VgoStorage : IVgoStorage #region Constructors /// - /// Create a new instance of VgoStorage with filePath. - /// - /// The file path of the vgo. - /// The file path of the crypt key. - /// for Import - public VgoStorage(string vgoFilePath, string? vgkFilePath = null) - { - if (vgoFilePath == null) - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowArgumentNullException(nameof(vgoFilePath)); -#else - throw new ArgumentNullException(nameof(vgoFilePath)); -#endif - } - - FileInfo vgoFileInfo = new FileInfo(vgoFilePath); - - if (vgoFileInfo.Exists == false) - { - ThrowHelper.ThrowFileNotFoundException(vgoFilePath); - } - - DirectoryPath = vgoFileInfo.DirectoryName; - - byte[] vgoBytes = File.ReadAllBytes(vgoFilePath); - - byte[]? vgkBytes = null; - - if (vgkFilePath != null) - { - FileInfo vgkFileInfo = new FileInfo(vgkFilePath); - - if (vgkFileInfo.Exists == false) - { - ThrowHelper.ThrowFileNotFoundException(vgkFilePath); - } - - vgkBytes = File.ReadAllBytes(vgkFilePath); - } - - ParseVgo(vgoBytes, vgkBytes, out var layout); - - Layout = layout; - } - - /// - /// Create a new instance of VgoStorage with bytes. + /// Create a new instance of VgoStorage. /// - /// The vgo bytes. - /// The vgk bytes. /// for Import - public VgoStorage(byte[] vgoBytes, byte[]? vgkBytes = null) + public VgoStorage() { - ParseVgo(vgoBytes, vgkBytes, out var layout); - - Layout = layout; + Layout = new VgoLayout(); } /// @@ -207,6 +148,7 @@ protected virtual byte[] SerializeObject(T? value, bool isBson) where T : cla } } +#if false /// /// Deserialize JSON or BSON to a object. /// @@ -243,6 +185,7 @@ protected virtual byte[] SerializeObject(T? value, bool isBson) where T : cla throw; } } +#endif #endregion } diff --git a/NewtonVgo/Runtime/Stream.meta b/NewtonVgo/Runtime/Stream.meta new file mode 100644 index 0000000..b964df7 --- /dev/null +++ b/NewtonVgo/Runtime/Stream.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 14b024cb26402464e9ec29e519002e0f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs b/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs new file mode 100644 index 0000000..c390358 --- /dev/null +++ b/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs @@ -0,0 +1,76 @@ +// ---------------------------------------------------------------------- +// @Namespace : NewtonVgo +// @Interface : IVgoStreamReader +// ---------------------------------------------------------------------- +#nullable enable +namespace NewtonVgo +{ + using System; + using System.Collections.Generic; + using System.Threading; + using System.Threading.Tasks; + + /// + /// VGO Stream Reader Interface + /// + public interface IVgoStreamReader : IDisposable + { + #region Methods + + ///// + ///// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + ///// + //void Dispose(); + + /// + /// Read header chunk. + /// + /// The header chunk. + VgoHeader ReadHeader(); + + /// + /// Read index chunk data. + /// + /// The index chunk. + VgoIndexChunkDataElement[] ReadIndexChunk(); + + /// + /// Read composer chunk. + /// + /// The composer chunk data. + VgoComposerChunkData ReadComposerChunk(); + + /// + /// Read asset info chunk. + /// + /// The vgo asset info. + VgoAssetInfo? ReadAssetInfo(); + + /// + /// Read layout chunk. + /// + /// The vgo layout. + VgoLayout? ReadLayout(); + + /// + /// Read resource accessor chunk. + /// + /// List of resource accessor. + List? ReadResourceAccessor(); + + /// + /// Read resource chunk. + /// + /// The resource chunk data. + byte[] ReadResource(); + + /// + /// Read resource chunk. + /// + /// The token to monitor for cancellation requests. + /// The resource chunk data. + Task ReadResourceAsync(CancellationToken cancellationToken); + + #endregion + } +} diff --git a/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs.meta b/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs.meta new file mode 100644 index 0000000..56a8dcc --- /dev/null +++ b/NewtonVgo/Runtime/Stream/IVgoStreamReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 362a47eb1aa1e184e8ed99940bf5f734 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/NewtonVgo/Runtime/Stream/VgoStreamReader.cs b/NewtonVgo/Runtime/Stream/VgoStreamReader.cs new file mode 100644 index 0000000..b6e1e04 --- /dev/null +++ b/NewtonVgo/Runtime/Stream/VgoStreamReader.cs @@ -0,0 +1,1244 @@ +// ---------------------------------------------------------------------- +// @Namespace : NewtonVgo +// @Class : VgoStreamReader +// ---------------------------------------------------------------------- +#nullable enable +namespace NewtonVgo +{ + using Newtonsoft.Json; + using NewtonVgo.Security.Cryptography; + using NewtonVgo.Serialization; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Runtime.InteropServices; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + /// + /// VGO Stream Reader + /// + public class VgoStreamReader : IVgoStreamReader + { + #region Fields + + /// The vgo stream. + protected Stream _VgoStream; + + /// The vgk bytes. + protected byte[] _VgkBytes; + + /// The vgo stream lock object. + protected readonly object _VgoStreamLockObject = new object(); + + /// The vgo binary reader. + protected BinaryReader _VgoBinaryReader; + + /// Vgo BSON Serializer. + protected readonly VgoBsonSerializer _VgoBsonSerializer = new VgoBsonSerializer(); + + /// Vgo JSON Serializer settings. + protected readonly VgoJsonSerializerSettings _VgoJsonSerializerSettings = new VgoJsonSerializerSettings(); + + /// The file header. + protected VgoHeader? _Header = null; + + /// The index map of chunks. + protected VgoIndexChunkDataElement[]? _ChunkIndexMap = null; + + /// The composer chunk data. + protected VgoComposerChunkData? _ComposerChunkData = null; + + /// + protected bool _Disposed; + + #endregion + + #region Constructors + + /// + /// Create a new instance of VgoStreamReader with vgo stream. + /// + /// The vgo stream. + public VgoStreamReader(in Stream vgoStream) + { + _VgoStream = vgoStream; + + _VgkBytes = Array.Empty(); + + _VgoBinaryReader = new BinaryReader(_VgoStream); + } + + /// + /// Create a new instance of VgoStreamReader with vgo stream and vgk stream. + /// + /// The vgo stream. + /// The vgk stream. + public VgoStreamReader(in Stream vgoStream, in Stream? vgkStream) + { + _VgoStream = vgoStream; + + if (vgkStream == null) + { + _VgkBytes = Array.Empty(); + } + else + { + if (vgkStream.Length != 32) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(vgkStream), (int)vgkStream.Length, min: 32, max: 32); + } + + byte[] vgkBytes = vgkStream.ReadBytes(32); + + if (vgkBytes.Length != 32) + { + ThrowHelper.ThrowIOException(); + } + + _VgkBytes = vgkBytes; + } + + _VgoBinaryReader = new BinaryReader(_VgoStream); + } + + /// + /// Create a new instance of VgoStreamReader with vgo stream and vgk bytes. + /// + /// The vgo stream. + /// The vgk bytes. + public VgoStreamReader(in Stream vgoStream, in byte[]? vgkBytes) + { + _VgoStream = vgoStream; + + if (vgkBytes == null) + { + _VgkBytes = Array.Empty(); + } + else + { + if (vgkBytes.Length != 32) + { + ThrowHelper.ThrowArgumentOutOfRangeException(nameof(vgkBytes), vgkBytes.Length, min: 32, max: 32); + } + + _VgkBytes = vgkBytes; + } + + _VgoBinaryReader = new BinaryReader(_VgoStream); + } + + #endregion + + #region IDisposable + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + Dispose(disposing: true); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// + protected virtual void Dispose(bool disposing) + { + if (_Disposed) + { + return; + } + + if (disposing) + { + _VgoBinaryReader.Dispose(); + + //_VgoStream.Dispose(); + } + + _Disposed = true; + } + + #endregion + + #region Protected Methods (Common chunk) + + /// + /// Read chunk. + /// + /// The chunk type ID. + /// The vgo read chunk. + /// This method is not available for header and index chunks. + protected virtual VgoReadChunk ReadChunk(in VgoChunkTypeID chunkTypeId) + { + if (_ChunkIndexMap == null) + { + _ = ReadIndexChunk(); + + if (_ChunkIndexMap == null) + { +#if NET_STANDARD_2_1 + ThrowHelper.ThrowFormatException(); +#else + throw new FormatException(); +#endif + } + } + + VgoIndexChunkDataElement chunkInfo = GetIndexChunkDataElement(chunkTypeId, _ChunkIndexMap); + + return ReadChunk(chunkTypeId, chunkInfo.ByteOffset, chunkInfo.BytePadding); + } + + /// + /// Read chunk. + /// + /// The chunk type ID. + /// The token to monitor for cancellation requests. + /// The vgo read chunk. + /// This method is not available for header and index chunks. + protected virtual async Task ReadChunkAsync(VgoChunkTypeID chunkTypeId, CancellationToken cancellationToken) + { + if (_ChunkIndexMap == null) + { + _ = ReadIndexChunk(); + + if (_ChunkIndexMap == null) + { +#if NET_STANDARD_2_1 + ThrowHelper.ThrowFormatException(); +#else + throw new FormatException(); +#endif + } + } + + VgoIndexChunkDataElement chunkInfo = GetIndexChunkDataElement(chunkTypeId, _ChunkIndexMap); + + return await ReadChunkAsync(chunkTypeId, chunkInfo.ByteOffset, chunkInfo.BytePadding, cancellationToken); + } + + /// + /// Read chunk. + /// + /// The chunk type ID. + /// The byte offset. + /// The byte padding. + /// The vgo read chunk. + protected virtual VgoReadChunk ReadChunk(in VgoChunkTypeID chunkTypeId, in uint byteOffset, in byte bytePadding) + { + uint typeId; + uint chunkDataLength; + byte[] chunkData; + + lock (_VgoStreamLockObject) + { + _VgoStream.Seek(byteOffset, SeekOrigin.Begin); + + typeId = _VgoBinaryReader.ReadUInt32(); + + if (typeId != (uint)chunkTypeId) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.TypeId: {chunkTypeId}"); + } + + chunkDataLength = _VgoBinaryReader.ReadUInt32(); + + if (chunkDataLength == 0) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.DataLength: {chunkDataLength}"); + } + + int effectiveDataLength = (int)chunkDataLength - bytePadding; + + chunkData = _VgoBinaryReader.ReadBytes(effectiveDataLength); + + if (chunkData.Length != effectiveDataLength) + { + ThrowHelper.ThrowIOException($"[{chunkTypeId}] readSize: {effectiveDataLength}"); + } + } + + return new VgoReadChunk(chunkTypeId, chunkDataLength, chunkData); + } + + /// + /// Read chunk. + /// + /// The chunk type ID. + /// The byte offset. + /// The byte padding. + /// The token to monitor for cancellation requests. + /// The vgo read chunk. + protected virtual async Task ReadChunkAsync( + VgoChunkTypeID chunkTypeId, uint byteOffset, byte bytePadding, CancellationToken cancellationToken) + { + uint typeId; + uint chunkDataLength; + byte[] chunkData; + + //lock (_VgoStreamLockObject) + { + _VgoStream.Seek(byteOffset, SeekOrigin.Begin); + + typeId = _VgoBinaryReader.ReadUInt32(); + + if (typeId != (uint)chunkTypeId) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.TypeId: {chunkTypeId}"); + } + + chunkDataLength = _VgoBinaryReader.ReadUInt32(); + + if (chunkDataLength == 0) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.DataLength: {chunkDataLength}"); + } + + int effectiveDataLength = (int)chunkDataLength - bytePadding; + + chunkData = await _VgoStream.ReadBytesAsync(effectiveDataLength, cancellationToken); + + if (chunkData.Length != effectiveDataLength) + { + ThrowHelper.ThrowIOException($"[{chunkTypeId}] readSize: {effectiveDataLength}"); + } + } + + return new VgoReadChunk(chunkTypeId, chunkDataLength, chunkData); + } + + #endregion + + #region Public Methods (Header chunk) + + /// + /// Read header chunk. + /// + /// The header chunk. + public virtual VgoHeader ReadHeader() + { + if (_Header.HasValue) + { + return _Header.Value; + } + + try + { + int headerSize = Marshal.SizeOf(typeof(VgoHeader)); + + byte[] headerBytes; + + lock (_VgoStreamLockObject) + { + _VgoStream.Seek(0, SeekOrigin.Begin); + + headerBytes = _VgoStream.ReadBytes(headerSize); + } + + if (headerBytes.Length != 16) + { + ThrowHelper.ThrowIOException($"[Header] readSize: {headerBytes.Length}"); + } + + VgoHeader header = headerBytes.ConvertToStructure(); + + if (header.Magic != (uint)VgoChunkTypeID.Vgo) + { + ThrowHelper.ThrowFormatException($"[Header] Magic: {header.Magic}"); + } + + if (header.DataLength != 8) + { + ThrowHelper.ThrowFormatException($"[Header] DataLength: {header.DataLength}"); + } + + if (header.MajorVersion == 0) + { + ThrowHelper.ThrowFormatException($"[Header] MajorVersion: {header.MajorVersion}"); + } + + if (header.GeometryCoordinate != VgoGeometryCoordinate.RightHanded && + header.GeometryCoordinate != VgoGeometryCoordinate.LeftHanded) + { + ThrowHelper.ThrowFormatException($"[Header] GeometryCoordinate: {header.GeometryCoordinate}"); + } + + if (header.UVCoordinate != VgoUVCoordinate.TopLeft && + header.UVCoordinate != VgoUVCoordinate.BottomLeft) + { + ThrowHelper.ThrowFormatException($"[Header] UVCoordinate: {header.UVCoordinate}"); + } + + _Header = header; + + return header; + } + catch + { + throw; + } + } + + #endregion + + #region Public Methods (Index chunk) + + /// + /// Read index chunk data. + /// + /// The index chunk. + public virtual VgoIndexChunkDataElement[] ReadIndexChunk() + { + if (_ChunkIndexMap != null) + { + return _ChunkIndexMap; + } + + if (_Header == null) + { + _ = ReadHeader(); + } + + try + { + VgoReadChunk indexChunk = ReadChunk(VgoChunkTypeID.Idx, byteOffset: 16, bytePadding: 0); + + int elementSize = Marshal.SizeOf(typeof(VgoIndexChunkDataElement)); + + if ((indexChunk.DataLength % elementSize) != 0) + { + ThrowHelper.ThrowFormatException($"[IDX] Chunk.DataLength: {indexChunk.DataLength}"); + } + + Span elementSpan = MemoryMarshal.Cast(indexChunk.ChunkData); + + _ChunkIndexMap = elementSpan.ToArray(); + + return _ChunkIndexMap; + } + catch + { + throw; + } + } + + #endregion + + #region Protected Methods (Index chunk) + + /// + /// Get the index chunk data of the specified chunk type ID from the chunk index map. + /// + /// The chunk type ID. + /// The chunk index map. + /// An index chunk data element. + protected virtual VgoIndexChunkDataElement GetIndexChunkDataElement(VgoChunkTypeID chunkTypeId, VgoIndexChunkDataElement[] chunkIndexMap) + { + int count = chunkIndexMap.Count(x => x.ChunkTypeId == chunkTypeId); + + if (count == 0) + { + ThrowHelper.ThrowFormatException($"{chunkTypeId} is not define in IDX chunk."); + } + + if (count >= 2) + { + ThrowHelper.ThrowFormatException($"{chunkTypeId} is defined more than once in IDX chunk."); + } + + VgoIndexChunkDataElement chunkInfo = chunkIndexMap.First(x => x.ChunkTypeId == chunkTypeId); + + if (chunkInfo.ByteOffset == 0) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteOffset: {chunkInfo.ByteOffset}"); + } + + if (chunkInfo.ByteLength == 0) + { + //ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteLength: {chunkInfo.ByteLength}"); + } + + if ((chunkInfo.ByteOffset + chunkInfo.ByteLength) > _VgoStream.Length) + { + ThrowHelper.ThrowFormatException($"[{chunkTypeId}] Chunk.ByteLength: {chunkInfo.ByteLength} is out of the range."); + } + + return chunkInfo; + } + + #endregion + + #region Public Methods (Composer chunk) + + /// + /// Read composer chunk. + /// + /// The composer chunk data. + public virtual VgoComposerChunkData ReadComposerChunk() + { + if (_ComposerChunkData.HasValue) + { + return _ComposerChunkData.Value; + } + + try + { + VgoReadChunk composerChunk = ReadChunk(VgoChunkTypeID.COMP); + + VgoComposerChunkData composerChunkData = composerChunk.ChunkData.ConvertToStructure(); + + _ComposerChunkData = composerChunkData; + + return composerChunkData; + } + catch + { + throw; + } + } + + #endregion + + #region Public Methods (Asset Info chunk) + + /// + /// Read asset info chunk. + /// + /// The vgo asset info. + public virtual VgoAssetInfo? ReadAssetInfo() + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return null; + } + } + + try + { + VgoChunkTypeID chunkTypeId = _ComposerChunkData.Value.AssetInfoChunkTypeId; + + VgoReadChunk assetInfoChunk = ReadChunk(chunkTypeId); + + VgoAssetInfo? assetInfo = null; + + if (chunkTypeId is VgoChunkTypeID.AIPJ) + { + // JSON + assetInfo = DeserializeObject(assetInfoChunk.ChunkData, isBson: false); + } + else if (chunkTypeId is VgoChunkTypeID.AIPB) + { + // BSON + assetInfo = DeserializeObject(assetInfoChunk.ChunkData, isBson: true); + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] AssetInfoChunkTypeId: {chunkTypeId}"); + } + + return assetInfo; + } + catch + { + throw; + } + } + + #endregion + + #region Public Methods (Layout chunk) + + /// + /// Read layout chunk. + /// + /// The vgo layout. + public virtual VgoLayout? ReadLayout() + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return null; + } + } + + try + { + VgoChunkTypeID chunkTypeId = _ComposerChunkData.Value.LayoutChunkTypeId; + + VgoReadChunk layoutChunk = ReadChunk(chunkTypeId); + + VgoLayout? layout = null; + + if (chunkTypeId is VgoChunkTypeID.LAPJ) + { + // JSON + layout = DeserializeObject(layoutChunk.ChunkData, isBson: false); + } + else if (chunkTypeId is VgoChunkTypeID.LAPB) + { + // BSON + layout = DeserializeObject(layoutChunk.ChunkData, isBson: true); + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] LayoutChunkTypeId: {chunkTypeId}"); + } + + return layout; + } + catch + { + throw; + } + } + + #endregion + + #region Public Methods (Resource Accessor chunk) + + /// + /// Read resource accessor chunk. + /// + /// List of resource accessor. + public virtual List? ReadResourceAccessor() + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return null; + } + } + + try + { + VgoChunkTypeID raChunkTypeId = _ComposerChunkData.Value.ResourceAccessorChunkTypeId; + + switch (raChunkTypeId) + { + case VgoChunkTypeID.RAPJ: + case VgoChunkTypeID.RAPB: + case VgoChunkTypeID.RACJ: + case VgoChunkTypeID.RACB: + break; + default: + throw new FormatException($"[COMP] ResourceAccessorChunkTypeId: {raChunkTypeId}"); + } + + VgoReadChunk resourceAccessorChunk = ReadChunk(raChunkTypeId); + + byte[]? plainJsonOrBson; + + if (raChunkTypeId is VgoChunkTypeID.RAPJ) + { + plainJsonOrBson = resourceAccessorChunk.ChunkData; + } + else if (raChunkTypeId is VgoChunkTypeID.RAPB) + { + plainJsonOrBson = resourceAccessorChunk.ChunkData; + } + else if ( + (raChunkTypeId == VgoChunkTypeID.RACJ) || + (raChunkTypeId == VgoChunkTypeID.RACB)) + { + VgoCryptV0? vgoCrypt = ReadCryptChunk(); + + if (vgoCrypt is null) + { + ThrowHelper.ThrowFormatException($" ResourceAccessorChunkTypeId: {raChunkTypeId}"); + + return null; + } + + try + { + byte[] encryptedJsonOrBson = resourceAccessorChunk.ChunkData; + + if (vgoCrypt.algorithms == VgoCryptographyAlgorithms.AES) + { + // AES + var aesCrypter = new AesCrypter() + { + CipherMode = vgoCrypt.cipherMode, + PaddingMode = vgoCrypt.paddingMode, + }; + + byte[] key; + + if (_VgkBytes is null || _VgkBytes.Any() == false) + { + if (string.IsNullOrEmpty(vgoCrypt.key)) + { + ThrowHelper.ThrowException("crypt key is unknown."); + + return default; + } + else + { + key = Convert.FromBase64String(vgoCrypt.key); + } + } + else + { + key = _VgkBytes; + } + + if (key is null || key.Any() == false) + { + ThrowHelper.ThrowException("crypt key is unknown."); + + return default; + } + + if (string.IsNullOrEmpty(vgoCrypt.iv)) + { + ThrowHelper.ThrowException("iv is unknown."); + + return default; + } + + byte[] iv = Convert.FromBase64String(vgoCrypt.iv); + + plainJsonOrBson = aesCrypter.Decrypt(encryptedJsonOrBson, key, iv); + } + else if (vgoCrypt.algorithms == VgoCryptographyAlgorithms.Base64) + { + // Base64 + string base64String = Encoding.UTF8.GetString(encryptedJsonOrBson); + + plainJsonOrBson = Convert.FromBase64String(base64String); + } + else + { + ThrowHelper.ThrowNotSupportedException($"CryptographyAlgorithms: {vgoCrypt.algorithms}"); + + return default; + } + } + catch (JsonSerializationException) + { + throw; + } + catch + { + throw; + } + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] ResourceAccessorChunkTypeId: {raChunkTypeId}"); + + return default; + } + + List? vgoResourceAccessors = null; + + try + { + if (plainJsonOrBson is null) + { + ThrowHelper.ThrowException("plainJsonOrBson is null."); + + return default; + } + + if (raChunkTypeId == VgoChunkTypeID.RAPJ || + raChunkTypeId == VgoChunkTypeID.RACJ) + { + // JSON + vgoResourceAccessors = DeserializeObject>(plainJsonOrBson, isBson: false); + } + else if ( + raChunkTypeId == VgoChunkTypeID.RAPB || + raChunkTypeId == VgoChunkTypeID.RACB) + { + // BSON + vgoResourceAccessors = DeserializeObject>(plainJsonOrBson, isBson: true, rootValueAsArray: true); + } + } + catch (JsonSerializationException) + { + throw; + } + catch + { + throw; + } + + return vgoResourceAccessors; + } + catch + { + throw; + } + } + + #endregion + + #region Public Methods (Resource chunk) + + /// + /// Read resource chunk. + /// + /// The resource chunk data. + public virtual byte[] ReadResource() + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return Array.Empty(); + } + } + + try + { + VgoChunkTypeID resourceChunkTypeId = _ComposerChunkData.Value.ResourceChunkTypeId; + + switch (resourceChunkTypeId) + { + case VgoChunkTypeID.REPb: + case VgoChunkTypeID.REPJ: + case VgoChunkTypeID.REPB: + break; + default: + throw new FormatException($"[COMP] ResourceChunkTypeId: {resourceChunkTypeId}"); + } + + VgoReadChunk resourceChunk = ReadChunk(resourceChunkTypeId); + + if (resourceChunkTypeId is VgoChunkTypeID.REPb) + { + return resourceChunk.ChunkData; + } + + VgoResource? vgoResource; + + if (resourceChunkTypeId == VgoChunkTypeID.REPJ) + { + // JSON + vgoResource = DeserializeObject(resourceChunk.ChunkData, isBson: false); + } + else if (resourceChunkTypeId == VgoChunkTypeID.REPB) + { + // BSON + vgoResource = DeserializeObject(resourceChunk.ChunkData, isBson: true); + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] ResourceChunkTypeId: {resourceChunkTypeId}"); + + return Array.Empty(); + } + + if (vgoResource is null) + { + ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] resource is null."); + + return Array.Empty(); + } + + if (vgoResource.uri is null || vgoResource.uri == string.Empty) + { + ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] uri is null or empty."); + + return Array.Empty(); + } + + if (vgoResource.byteLength == 0) + { + //ThrowHelper.ThrowFormatException($"[{chunkTypeId}] byteLength: {vgoResource.byteLength}"); + } + + if (_VgoStream is FileStream fileStream) + { + var fileInfo = new FileInfo(fileStream.Name); + + return GetUriData(vgoResource.uri, directory: fileInfo.Directory.FullName); + } + else + { + return GetUriData(vgoResource.uri); + } + } + catch + { + throw; + } + } + + /// + /// Read resource chunk. + /// + /// The token to monitor for cancellation requests. + /// The resource chunk data. + public virtual async Task ReadResourceAsync(CancellationToken cancellationToken) + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return Array.Empty(); + } + } + + try + { + VgoChunkTypeID resourceChunkTypeId = _ComposerChunkData.Value.ResourceChunkTypeId; + + switch (resourceChunkTypeId) + { + case VgoChunkTypeID.REPb: + case VgoChunkTypeID.REPJ: + case VgoChunkTypeID.REPB: + break; + default: + throw new FormatException($"[COMP] ResourceChunkTypeId: {resourceChunkTypeId}"); + } + + VgoReadChunk resourceChunk = await ReadChunkAsync(resourceChunkTypeId, cancellationToken); + + if (resourceChunkTypeId is VgoChunkTypeID.REPb) + { + return resourceChunk.ChunkData; + } + + VgoResource? vgoResource; + + if (resourceChunkTypeId == VgoChunkTypeID.REPJ) + { + // JSON + vgoResource = DeserializeObject(resourceChunk.ChunkData, isBson: false); + } + else if (resourceChunkTypeId == VgoChunkTypeID.REPB) + { + // BSON + vgoResource = DeserializeObject(resourceChunk.ChunkData, isBson: true); + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] ResourceChunkTypeId: {resourceChunkTypeId}"); + + return Array.Empty(); + } + + if (vgoResource is null) + { + ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] resource is null."); + + return Array.Empty(); + } + + if (vgoResource.uri is null || vgoResource.uri == string.Empty) + { + ThrowHelper.ThrowFormatException($"[{resourceChunkTypeId}] uri is null or empty."); + + return Array.Empty(); + } + + if (vgoResource.byteLength == 0) + { + //ThrowHelper.ThrowFormatException($"[{chunkTypeId}] byteLength: {vgoResource.byteLength}"); + } + + if (_VgoStream is FileStream fileStream) + { + var fileInfo = new FileInfo(fileStream.Name); + + return await GetUriDataAsync(vgoResource.uri, directory: fileInfo.Directory.FullName); + } + else + { + return await GetUriDataAsync(vgoResource.uri); + } + } + catch + { + throw; + } + } + + #endregion + + #region Protected Methods (Crypt chunk) + + /// + /// Read crypt chunk. + /// + /// The crypt chunk data. + protected virtual VgoCryptV0? ReadCryptChunk() + { + if (_ComposerChunkData == null) + { + _ = ReadComposerChunk(); + + if (_ComposerChunkData == null) + { + ThrowHelper.ThrowFormatException(); + + return null; + } + } + + try + { + VgoChunkTypeID chunkTypeId = _ComposerChunkData.Value.ResourceAccessorCryptChunkTypeId; + + VgoReadChunk cryptChunk = ReadChunk(chunkTypeId); + + VgoCryptV0? crypt = null; + + if (chunkTypeId is VgoChunkTypeID.CRAJ) + { + // JSON + crypt = DeserializeObject(cryptChunk.ChunkData, isBson: false); + } + else if (chunkTypeId is VgoChunkTypeID.CRAB) + { + // BSON + crypt = DeserializeObject(cryptChunk.ChunkData, isBson: true); + } + else + { + ThrowHelper.ThrowFormatException($"[COMP] ResourceAccessorCryptChunkTypeId: {chunkTypeId}"); + } + + return crypt; + } + catch + { + throw; + } + } + + #endregion + + #region Protected Methods (JSON or BSON) + + /// + /// Deserialize JSON or BSON to a object. + /// + /// + /// The JSON or BSON. + /// Specify true if the data is BSON. + /// Specify true if the root value is an array. + /// A object. + protected virtual T? DeserializeObject(in byte[] jsonOrBson, in bool isBson, bool rootValueAsArray = false) where T : class + { + try + { + T? data; + + if (isBson) + { + data = _VgoBsonSerializer.DeserializeObject(jsonOrBson, rootValueAsArray); + } + else + { + string jsonString = Encoding.UTF8.GetString(jsonOrBson); + + data = JsonConvert.DeserializeObject(jsonString, _VgoJsonSerializerSettings); + } + + return data; + } + catch (JsonSerializationException) + { + throw; + } + catch + { + throw; + } + } + + #endregion + + #region Protected Methods (Helper) + + /// + /// Get URI data. + /// + /// The uri. + /// The directory. + /// An byte array of uri data. + protected virtual byte[] GetUriData(in string uri, string? directory = null) + { + ThrowHelper.ThrowExceptionIfArgumentIsNull(nameof(uri), uri); + + if (uri.StartsWith("data:")) + { + ThrowHelper.ThrowNotSupportedException(uri); + + return Array.Empty(); + } + else if ( + uri.StartsWith("http://") || + uri.StartsWith("https://")) + { + using var httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(30) }; + + using var request = new HttpRequestMessage(HttpMethod.Get, uri); + + using var response = httpClient.SendAsync(request).GetAwaiter().GetResult(); + + if (response.IsSuccessStatusCode) + { + byte[] byteArray = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult(); + + return byteArray; + } + else + { + ThrowHelper.ThrowHttpRequestException(response.StatusCode.ToString()); + + return Array.Empty(); + } + } + else if (uri.StartsWith("file://")) + { + ThrowHelper.ThrowNotSupportedException(uri); + + return Array.Empty(); + } + else + { + if (directory == null) + { + ThrowHelper.ThrowException(); + + return Array.Empty(); + } + + if (Directory.Exists(directory) == false) + { + ThrowHelper.ThrowDirectoryNotFoundException(directory); + } + + string binFilePath = Path.Combine(directory, uri); + + if (File.Exists(binFilePath) == false) + { + ThrowHelper.ThrowFileNotFoundException(binFilePath); + } + + //using var binFileStream = new FileStream(binFilePath, FileMode.Open, FileAccess.Read); + + byte[] binData = File.ReadAllBytes(binFilePath); + + return binData; + } + } + + /// + /// Get URI data. + /// + /// The uri. + /// The directory. + /// The token to monitor for cancellation requests. + /// An byte array of uri data. + protected virtual async Task GetUriDataAsync(string uri, string? directory = null, CancellationToken cancellationToken = default) + { + ThrowHelper.ThrowExceptionIfArgumentIsNull(nameof(uri), uri); + + cancellationToken.ThrowIfCancellationRequested(); + + if (uri.StartsWith("data:")) + { + ThrowHelper.ThrowNotSupportedException(uri); + + return Array.Empty(); + } + else if ( + uri.StartsWith("http://") || + uri.StartsWith("https://")) + { + using var httpClient = new HttpClient() { Timeout = TimeSpan.FromSeconds(30) }; + + using var request = new HttpRequestMessage(HttpMethod.Get, uri); + + using var response = await httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + byte[] byteArray = await response.Content.ReadAsByteArrayAsync(); + + return byteArray; + } + else + { + ThrowHelper.ThrowHttpRequestException(response.StatusCode.ToString()); + + return Array.Empty(); + } + } + else if (uri.StartsWith("file://")) + { + ThrowHelper.ThrowNotSupportedException(uri); + + return Array.Empty(); + } + else + { + if (directory == null) + { + ThrowHelper.ThrowException(); + + return Array.Empty(); + } + + if (Directory.Exists(directory) == false) + { + ThrowHelper.ThrowDirectoryNotFoundException(directory); + } + + string binFilePath = Path.Combine(directory, uri); + + if (File.Exists(binFilePath) == false) + { + ThrowHelper.ThrowFileNotFoundException(binFilePath); + } + + //using var binFileStream = new FileStream(binFilePath, FileMode.Open, FileAccess.Read); + +#if UNITY_2021_2_OR_NEWER + byte[] binData = await File.ReadAllBytesAsync(binFilePath, cancellationToken); +#else + byte[] binData = File.ReadAllBytes(binFilePath); +#endif + return binData; + } + } + + #endregion + } +} diff --git a/NewtonVgo/Runtime/Stream/VgoStreamReader.cs.meta b/NewtonVgo/Runtime/Stream/VgoStreamReader.cs.meta new file mode 100644 index 0000000..d200ab4 --- /dev/null +++ b/NewtonVgo/Runtime/Stream/VgoStreamReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 666f141af9dba1f44ba350a47d043971 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniVgo2/Editor/ScriptedImporters/VgoScriptedImporter.cs b/UniVgo2/Editor/ScriptedImporters/VgoScriptedImporter.cs index 9182487..f3f0021 100644 --- a/UniVgo2/Editor/ScriptedImporters/VgoScriptedImporter.cs +++ b/UniVgo2/Editor/ScriptedImporters/VgoScriptedImporter.cs @@ -106,7 +106,7 @@ public override void OnImportAsset(AssetImportContext ctx) // Animation if (modelAsset.AnimationClipList != null) { - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach (AnimationClip? animationClip in modelAsset.AnimationClipList) { @@ -134,7 +134,7 @@ public override void OnImportAsset(AssetImportContext ctx) // Avatar if (modelAsset.Avatar != null) { - var externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); if (externalObjects.ContainsValue(modelAsset.Avatar) == false) { @@ -152,7 +152,7 @@ public override void OnImportAsset(AssetImportContext ctx) // Material if (modelAsset.MaterialList != null) { - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach (Material? material in modelAsset.MaterialList) { @@ -180,7 +180,7 @@ public override void OnImportAsset(AssetImportContext ctx) // Mesh if (modelAsset.MeshAssetList != null) { - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach (MeshAsset meshAsset in modelAsset.MeshAssetList) { @@ -208,7 +208,7 @@ public override void OnImportAsset(AssetImportContext ctx) // Texture if (modelAsset.Texture2dList != null) { - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach (Texture2D? texture in modelAsset.Texture2dList) { @@ -243,7 +243,8 @@ public override void OnImportAsset(AssetImportContext ctx) .Select(x => x as BlendShapeConfiguration) .ToList(); - var externals = GetExternalUnityObjects(); + Dictionary externals + = GetExternalUnityObjects(); foreach (BlendShapeConfiguration? blendShapeConfiguration in blendShapeConfigurationList) { @@ -260,6 +261,7 @@ public override void OnImportAsset(AssetImportContext ctx) try { blendShapeConfiguration.name = blendShapeConfiguration.Kind + "BlendShapeConfiguration"; + ctx.AddObjectToAsset(blendShapeConfiguration.name, blendShapeConfiguration); } catch (Exception ex) @@ -362,14 +364,7 @@ protected virtual async Task ExtractModelAsync(string vgoFilePath VgoModelAsset vgoModelAsset; - if (vgkFilePath is null || vgkFilePath == string.Empty) - { - vgoModelAsset = await _VgoImporter.ExtractAsync(vgoFilePath, cancellationToken); - } - else - { - vgoModelAsset = await _VgoImporter.ExtractAsync(vgoFilePath, vgkFilePath, cancellationToken); - } + vgoModelAsset = await _VgoImporter.ExtractAsync(vgoFilePath, vgkFilePath, cancellationToken); return vgoModelAsset; } @@ -408,7 +403,7 @@ public virtual void ExtractAnimationClips() { ExtractAssets(AnimationDirectoryName, ".anim"); - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach ((_, AnimationClip animationClip) in externalObjects) { @@ -417,6 +412,7 @@ public virtual void ExtractAnimationClips() if (string.IsNullOrEmpty(assetPath) == false) { EditorUtility.SetDirty(animationClip); + AssetDatabase.WriteImportSettingsIfDirty(assetPath); } } @@ -431,7 +427,7 @@ public void ExtractAvatars() { ExtractAssets(AvatarDirectoryName, ".asset"); - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach ((_, Avatar avatar) in externalObjects) { @@ -440,6 +436,7 @@ public void ExtractAvatars() if (string.IsNullOrEmpty(assetPath) == false) { EditorUtility.SetDirty(avatar); + AssetDatabase.WriteImportSettingsIfDirty(assetPath); } } @@ -454,7 +451,7 @@ public void ExtractBlendShapes() { ExtractAssets(BlendShapeDirectoryName, ".asset"); - Dictionary externalObjects = GetExternalUnityObjects(); + Dictionary externalObjects = GetExternalUnityObjects(); foreach ((_, BlendShapeConfiguration blendShapeConfiguration) in externalObjects) { @@ -463,6 +460,7 @@ public void ExtractBlendShapes() if (string.IsNullOrEmpty(assetPath) == false) { EditorUtility.SetDirty(blendShapeConfiguration); + AssetDatabase.WriteImportSettingsIfDirty(assetPath); } } @@ -476,6 +474,7 @@ public void ExtractBlendShapes() public virtual void ExtractMaterials() { ExtractAssets(MaterialDirectoryName, ".mat"); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -485,6 +484,7 @@ public virtual void ExtractMaterials() public void ExtractMeshes() { ExtractAssets(MeshDirectoryName, ".mesh"); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -494,6 +494,7 @@ public void ExtractMeshes() public virtual void ExtractTexturesAndMaterials() { ExtractTextures(TextureDirectoryName, continueMaterial: true); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -503,6 +504,7 @@ public virtual void ExtractTexturesAndMaterials() public virtual void ExtractTextures() { ExtractTextures(TextureDirectoryName); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -524,7 +526,7 @@ public virtual void ExtractTextures(in string dirName, bool continueMaterial = f Path.GetDirectoryName(assetPath), Path.GetFileNameWithoutExtension(assetPath), dirName - ); + ); CreateDirectoryIfNotExists(path); @@ -690,7 +692,9 @@ public virtual void ClearExternalObjects() { RemoveRemap(extarnalObject.Key); } + AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -700,11 +704,13 @@ public virtual void ClearExternalObjects() /// public virtual void ClearExternalObjects() where T : UnityEngine.Object { - foreach (var extarnalObject in GetExternalObjectMap().Where(x => x.Key.type == typeof(T))) + foreach (var extarnalObject in GetExternalUnityObjects()) { RemoveRemap(extarnalObject.Key); } + AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -727,7 +733,7 @@ protected virtual void ExtractAssets(in string dirName, in string extension) Path.GetDirectoryName(assetPath), Path.GetFileNameWithoutExtension(assetPath), dirName - ); + ); CreateDirectoryIfNotExists(path); @@ -758,6 +764,7 @@ protected virtual void ExtractFromAsset(in UnityEngine.Object subAsset, in strin if (isForceUpdate) { AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } } @@ -767,11 +774,11 @@ protected virtual void ExtractFromAsset(in UnityEngine.Object subAsset, in strin /// /// /// - public virtual Dictionary GetExternalUnityObjects() where T : UnityEngine.Object + public virtual Dictionary GetExternalUnityObjects() where T : UnityEngine.Object { return GetExternalObjectMap() .Where(x => x.Key.type == typeof(T)) - .ToDictionary(x => x.Key.name, x => (T)x.Value); + .ToDictionary(x => x.Key, x => (T)x.Value); } /// @@ -782,7 +789,9 @@ public virtual Dictionary GetExternalUnityObjects() where T : Unit public virtual void SetExternalUnityObject(in SourceAssetIdentifier sourceAssetIdentifier, T obj) where T : UnityEngine.Object { AddRemap(sourceAssetIdentifier, obj); + AssetDatabase.WriteImportSettingsIfDirty(assetPath); + AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceUpdate); } @@ -798,8 +807,10 @@ public virtual void SetExternalUnityObject(in SourceAssetIdentifier sourceAss /// protected virtual IEnumerable GetSubAssets(in string assetPath) where T : UnityEngine.Object { - return AssetDatabase - .LoadAllAssetsAtPath(assetPath) + UnityEngine.Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath); + + return allAssets + .Where(x => x != null) .Where(x => AssetDatabase.IsSubAsset(x)) .Where(x => x is T) .Select(x => (T)x); @@ -812,7 +823,7 @@ protected virtual IEnumerable GetSubAssets(in string assetPath) where T : /// protected virtual DirectoryInfo CreateDirectoryIfNotExists(in string path) { - DirectoryInfo directoryInfo = new DirectoryInfo(path); + var directoryInfo = new DirectoryInfo(path); if (directoryInfo.Exists == false) { diff --git a/UniVgo2/Editor/UniVgo2.Editor.asmdef b/UniVgo2/Editor/UniVgo2.Editor.asmdef index 9835a97..0166940 100644 --- a/UniVgo2/Editor/UniVgo2.Editor.asmdef +++ b/UniVgo2/Editor/UniVgo2.Editor.asmdef @@ -11,7 +11,8 @@ "overrideReferences": false, "references": [ "NewtonVgo", - "UniVgo2" + "UniVgo2", + "UniTask" ], "precompiledReferences": [], "optionalUnityReferences": [], diff --git a/UniVgo2/Runtime/Configurations/AvatarConfiguration.cs b/UniVgo2/Runtime/Configurations/AvatarConfiguration.cs index a7fbf71..042dc75 100644 --- a/UniVgo2/Runtime/Configurations/AvatarConfiguration.cs +++ b/UniVgo2/Runtime/Configurations/AvatarConfiguration.cs @@ -28,8 +28,10 @@ public string Name } /// List of the human bone. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private List humanBones = new List(); + public List humanBones = new List(); + //private List humanBones = new List(); /// List of the human bone. public List HumanBones diff --git a/UniVgo2/Runtime/Configurations/BlendShapeConfiguration.cs b/UniVgo2/Runtime/Configurations/BlendShapeConfiguration.cs index efa938c..40eaada 100644 --- a/UniVgo2/Runtime/Configurations/BlendShapeConfiguration.cs +++ b/UniVgo2/Runtime/Configurations/BlendShapeConfiguration.cs @@ -28,8 +28,10 @@ public string Name } /// The kind of the BlendShape. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private VgoBlendShapeKind kind; + public VgoBlendShapeKind kind; + //private VgoBlendShapeKind kind; /// The kind of the BlendShape. public VgoBlendShapeKind Kind @@ -39,8 +41,10 @@ public VgoBlendShapeKind Kind } /// List of face parts. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private List faceParts = new List(); + public List faceParts = new List(); + //private List faceParts = new List(); /// List of face parts. /// @@ -55,8 +59,10 @@ public List FaceParts } /// List of blink. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private List blinks = new List(); + public List blinks = new List(); + //private List blinks = new List(); /// List of blink. public List Blinks @@ -66,8 +72,10 @@ public List Blinks } /// Visemes. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private List visemes = new List(); + public List visemes = new List(); + //private List visemes = new List(); /// Visemes. public List Visemes @@ -77,8 +85,10 @@ public List Visemes } /// List of preset. + /// For VgoScriptedImporter it is set to public. [SerializeField] - private List presets = new List(); + public List presets = new List(); + //private List presets = new List(); /// List of preset. public List Presets diff --git a/UniVgo2/Runtime/Porters/VgoImporter.Async.cs b/UniVgo2/Runtime/Porters/VgoImporter.Async.cs deleted file mode 100644 index 3f7a5fe..0000000 --- a/UniVgo2/Runtime/Porters/VgoImporter.Async.cs +++ /dev/null @@ -1,250 +0,0 @@ -// ---------------------------------------------------------------------- -// @Namespace : UniVgo2 -// @Class : VgoImporter -// ---------------------------------------------------------------------- -#nullable enable -namespace UniVgo2 -{ -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - // -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - using Cysharp.Threading.Tasks; -#else - using System.Threading.Tasks; -#endif - - using NewtonVgo; - using System.Collections.Generic; - using System.Threading; - using UnityEngine; - - /// - /// VGO Importer - /// - public partial class VgoImporter - { -#if UNITY_WEBGL - // -#else - #region Public Methods - - /// - /// Load a 3D model from the specified file. - /// - /// The file path of the vgo. - /// - /// A vgo model asset. -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable LoadAsync(string vgoFilePath, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask LoadAsync(string vgoFilePath, CancellationToken cancellationToken) -#else - public virtual async Task LoadAsync(string vgoFilePath, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoFilePath); - - return await LoadAsync(vgoStorage, cancellationToken); - } - - /// - /// Load a 3D model from the specified file. - /// - /// The file path of the vgo. - /// The file path of the crypt key. - /// - /// A vgo model asset. -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#else - public virtual async Task LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoFilePath, vgkFilePath); - - return await LoadAsync(vgoStorage, cancellationToken); - } - - /// - /// Load a 3D model from the specified bytes. - /// - /// The vgo bytes. - /// - /// A vgo model asset. -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) -#else - public virtual async Task LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoBytes); - - return await LoadAsync(vgoStorage, cancellationToken); - } - - /// - /// Load a 3D model from the specified bytes. - /// - /// The vgo bytes. - /// The vgk bytes. - /// - /// A vgo model asset. -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) -#else - public virtual async Task LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoBytes, vgkBytes); - - return await LoadAsync(vgoStorage, cancellationToken); - } - - /// - /// Load a 3D model from the specified vgo storage. - /// - /// A vgo storage. - /// - /// A vgo model asset. -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) -#else - public virtual async Task LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) -#endif - { - var vgoModelAsset = new VgoModelAsset(); - - vgoModelAsset.Layout = vgoStorage.Layout; - - // UnityEngine.Texture2D - //vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); - //vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsAsync(vgoStorage, cancellationToken); - vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsParallelAsync(vgoStorage, cancellationToken); - - // UnityEngine.Material - vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); - - // UnityEngine.Mesh - if (vgoStorage.IsSpecVersion_2_4_orLower) - { - //vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage, modelAsset.MaterialList); - vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsAsync(vgoStorage, vgoModelAsset.MaterialList, cancellationToken); - //vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsParallelAsync(vgoStorage, modelAsset.MaterialList, cancellationToken); - } - else - { - //vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage); - vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsAsync(vgoStorage, null, cancellationToken); - //vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsParallelAsync(vgoStorage, null, cancellationToken); - } - - // UnityEngine.AnimationClip - vgoModelAsset.AnimationClipList = CreateAnimationClipAssets(vgoStorage.Layout, vgoStorage.GeometryCoordinate); - - // UnityEngine.VgoSpringBoneColliderGroup - vgoModelAsset.SpringBoneColliderGroupArray = CreateSpringBoneColliderGroupArray(vgoStorage.Layout); - - // UnityEngine.GameObejct - List nodes = CreateNodes(vgoStorage); - - CreateNodeHierarchy(nodes, vgoStorage.Layout); - - vgoModelAsset.Root = nodes[0].gameObject; - - if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) - { - FixCoordinate(nodes); - } - - SetupNodes(nodes, vgoStorage, vgoModelAsset); - - SetupAssetInfo(vgoStorage, vgoModelAsset); - -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - //await Awaitable.NextFrameAsync(cancellationToken); - - return vgoModelAsset; -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - return await UniTask.FromResult(vgoModelAsset); -#else - return await Task.FromResult(vgoModelAsset); -#endif - } - - #endregion -#endif // UNITY_WEBGL - - #region Public Methods - - /// - /// Extract a 3D model asset from the specified file. - /// - /// The file path of the vgo. - /// - /// A vgo model asset. - /// for ScriptedImporter -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable ExtractAsync(string vgoFilePath, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask ExtractAsync(string vgoFilePath, CancellationToken cancellationToken) -#else - public virtual async Task ExtractAsync(string vgoFilePath, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoFilePath); - - var vgoModelAsset = new VgoModelAsset(); - - vgoModelAsset.Layout = vgoStorage.Layout; - - // UnityEngine.Texture2D - vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsAsync(vgoStorage, cancellationToken); - - // UnityEngine.Material - vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); - - return vgoModelAsset; - } - - /// - /// Extract a 3D model asset from the specified file. - /// - /// The file path of the vgo. - /// The file path of the vgk. - /// - /// A vgo model asset. - /// for ScriptedImporter -#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE - public virtual async Awaitable ExtractAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK - public virtual async UniTask ExtractAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#else - public virtual async Task ExtractAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) -#endif - { - var vgoStorage = new VgoStorage(vgoFilePath, vgkFilePath); - - var vgoModelAsset = new VgoModelAsset(); - - vgoModelAsset.Layout = vgoStorage.Layout; - - // UnityEngine.Texture2D - vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsAsync(vgoStorage, cancellationToken); - - // UnityEngine.Material - vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); - - return vgoModelAsset; - } - - #endregion -} -} diff --git a/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs b/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs new file mode 100644 index 0000000..b9c1b0d --- /dev/null +++ b/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs @@ -0,0 +1,1370 @@ +// ---------------------------------------------------------------------- +// @Namespace : UniVgo2 +// @Class : VgoImporter +// ---------------------------------------------------------------------- +#nullable enable +namespace UniVgo2 +{ +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + // +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + using Cysharp.Threading.Tasks; +#else + using System.Threading.Tasks; +#endif + + using NewtonVgo; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using UnityEngine; + using UniVgo2.Converters; + + /// + /// VGO Importer + /// + public partial class VgoImporter + { + #region Protected Methods (Load) + + /// + /// Load a 3D model from the specified vgo storage. + /// + /// A vgo storage. + /// A vgo model asset. + protected virtual VgoModelAsset LoadInternal(in IVgoStorage vgoStorage) + { + var vgoModelAsset = new VgoModelAsset(); + + vgoModelAsset.Layout = vgoStorage.Layout; + + // UnityEngine.Texture2D + vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); + + // UnityEngine.Material + vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); + + // UnityEngine.Mesh + if (vgoStorage.IsSpecVersion_2_4_orLower) + { + vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage, vgoModelAsset.MaterialList); + } + else + { + vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage); + } + + // UnityEngine.AnimationClip + vgoModelAsset.AnimationClipList = CreateAnimationClipAssets(vgoStorage.Layout, vgoStorage.GeometryCoordinate); + + // UnityEngine.VgoSpringBoneColliderGroup + vgoModelAsset.SpringBoneColliderGroupArray = CreateSpringBoneColliderGroupArray(vgoStorage.Layout); + + // UnityEngine.GameObejct + List nodes = CreateNodes(vgoStorage); + + CreateNodeHierarchy(nodes, vgoStorage.Layout); + + vgoModelAsset.Root = nodes[0].gameObject; + + if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) + { + FixCoordinate(nodes); + } + + SetupNodes(nodes, vgoStorage, vgoModelAsset); + + SetupAssetInfo(vgoStorage, vgoModelAsset); + + return vgoModelAsset; + } + + /// + /// Load a 3D model from the specified vgo storage. + /// + /// A vgo storage. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + protected virtual async Awaitable LoadInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + protected virtual async UniTask LoadInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#else + protected virtual async Task LoadInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#endif + { + var vgoModelAsset = new VgoModelAsset(); + + vgoModelAsset.Layout = vgoStorage.Layout; + + // UnityEngine.Texture2D + //vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); + //vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsAsync(vgoStorage, cancellationToken); + vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsParallelAsync(vgoStorage, cancellationToken); + + // UnityEngine.Material + vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); + + // UnityEngine.Mesh + if (vgoStorage.IsSpecVersion_2_4_orLower) + { + //vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage, modelAsset.MaterialList); + vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsAsync(vgoStorage, vgoModelAsset.MaterialList, cancellationToken); + //vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsParallelAsync(vgoStorage, modelAsset.MaterialList, cancellationToken); + } + else + { + //vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage); + vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsAsync(vgoStorage, null, cancellationToken); + //vgoModelAsset.MeshAssetList = await _MeshImporter.CreateMeshAssetsParallelAsync(vgoStorage, null, cancellationToken); + } + + // UnityEngine.AnimationClip + vgoModelAsset.AnimationClipList = CreateAnimationClipAssets(vgoStorage.Layout, vgoStorage.GeometryCoordinate); + + // UnityEngine.VgoSpringBoneColliderGroup + vgoModelAsset.SpringBoneColliderGroupArray = CreateSpringBoneColliderGroupArray(vgoStorage.Layout); + + // UnityEngine.GameObejct + List nodes = CreateNodes(vgoStorage); + + CreateNodeHierarchy(nodes, vgoStorage.Layout); + + vgoModelAsset.Root = nodes[0].gameObject; + + if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) + { + FixCoordinate(nodes); + } + + SetupNodes(nodes, vgoStorage, vgoModelAsset); + + SetupAssetInfo(vgoStorage, vgoModelAsset); + +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + //await Awaitable.NextFrameAsync(cancellationToken); + + return vgoModelAsset; +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + return await UniTask.FromResult(vgoModelAsset); +#else + return await Task.FromResult(vgoModelAsset); +#endif + } + + #endregion + + #region Protected Methods (Extract) + + /// + /// Extract a 3D model asset from the specified vgo storage. + /// + /// A vgo storage. + /// A vgo model asset. + protected virtual VgoModelAsset ExtractInternal(in IVgoStorage vgoStorage) + { + var vgoModelAsset = new VgoModelAsset(); + + vgoModelAsset.Layout = vgoStorage.Layout; + + // UnityEngine.Texture2D + vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); + + // UnityEngine.Material + vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); + + return vgoModelAsset; + } + + /// + /// Extract a 3D model asset from the specified vgo storage. + /// + /// A vgo storage. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + protected virtual async Awaitable ExtractInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + protected virtual async UniTask ExtractInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#else + protected virtual async Task ExtractInternalAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#endif + { + var vgoModelAsset = new VgoModelAsset(); + + vgoModelAsset.Layout = vgoStorage.Layout; + + // UnityEngine.Texture2D + //vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); + //vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsAsync(vgoStorage, cancellationToken); + vgoModelAsset.Texture2dList = await _TextureImporter.CreateTextureAssetsParallelAsync(vgoStorage, cancellationToken); + + // UnityEngine.Material + vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); + + return vgoModelAsset; + } + + #endregion + + #region layout.materials + + /// + /// Create material assets. + /// + /// A vgo storage. + /// List of unity texture 2D. + /// List of unity material. + protected virtual List CreateMaterialAssets(in IVgoStorage vgoStorage, in List texture2dList) + { + var materialList = new List(); + + if ((vgoStorage.Layout.materials == null) || (vgoStorage.Layout.materials.Any() == false)) + { + return materialList; + } + + for (int materialIndex = 0; materialIndex < vgoStorage.Layout.materials.Count; materialIndex++) + { + VgoMaterial? vgoMaterial = vgoStorage.Layout.materials[materialIndex]; + + if (vgoMaterial is null) + { + materialList.Add(null); + } + else + { + Material material = _MaterialImporter.CreateMaterialAsset(vgoMaterial, texture2dList); + + materialList.Add(material); + } + } + + return materialList; + } + + #endregion + + #region layout.springBoneInfo + + /// + /// Create vgo spring bone collider groups. + /// + /// A vgo layout. + /// An array of vgo spring bone collider group. + protected virtual VgoSpringBone.VgoSpringBoneColliderGroup[]? CreateSpringBoneColliderGroupArray(in VgoLayout vgoLayout) + { + if (vgoLayout.springBoneInfo == null) + { + return null; + } + + if (vgoLayout.springBoneInfo.colliderGroups == null) + { + return null; + } + + if (vgoLayout.springBoneInfo.colliderGroups.Any() == false) + { + return null; + } + + return new VgoSpringBone.VgoSpringBoneColliderGroup[vgoLayout.springBoneInfo.colliderGroups.Count]; + } + + #endregion + + #region layout.nodes + + /// + /// Create nodes. + /// + /// A vgo storage. + /// List of transform. + protected virtual List CreateNodes(in IVgoStorage vgoStorage) + { + if (vgoStorage.Layout.nodes is null) + { +#if NET_STANDARD_2_1 + ThrowHelper.ThrowException(); +#else + throw new Exception(); +#endif + } + + var nodes = new List(vgoStorage.Layout.nodes.Count); + + System.Numerics.Matrix4x4[] matrixes = GetVgoNodeTransforms(vgoStorage); + + for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) + { + string? name = vgoStorage.Layout.nodes[nodeIndex]?.name; + + if (name is null || name == string.Empty) + { + name = string.Format("node_{0:000}", nodeIndex); + } + else if (name.Contains("/")) + { + name = name.Replace("/", "_"); + } + + // GameObject + var go = new GameObject(name); + + // Transform + Matrix4x4 matrix = matrixes[nodeIndex].ToUnityMatrix(); + + go.transform.localPosition = matrix.ExtractTranslation(); + go.transform.localRotation = matrix.ExtractRotation(); + go.transform.localScale = matrix.ExtractScale(); + + nodes.Add(go.transform); + } + + return nodes; + } + + /// + /// Create node hierarchy. + /// + /// List of node. + /// A vgo layout. + protected virtual void CreateNodeHierarchy(List nodes, in VgoLayout vgoLayout) + { + if (vgoLayout.nodes is null) + { + ThrowHelper.ThrowException(); + + return; + } + + var rootNode = vgoLayout.nodes[0]; + + if (rootNode is null) + { + ThrowHelper.ThrowException(); + + return; + } + + if (rootNode.isRoot == false) + { + ThrowHelper.ThrowFormatException("nodes[0].isRoot: false"); + } + + for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++) + { + VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; + + if (vgoNode == null) + { + continue; + } + + if (vgoNode.children == null) + { + continue; + } + + foreach (int child in vgoNode.children) + { + nodes[child].transform.SetParent(parent: nodes[nodeIndex].transform, worldPositionStays: false); + } + } + } + + /// + /// Fix node's coordinate. z-back to z-forward + /// + /// List of node. + protected virtual void FixCoordinate(List nodes) + { + Dictionary globalTransformMap + = nodes.ToDictionary(x => x, x => (x.position, x.rotation)); + + Transform root = nodes[0]; + + foreach (Transform transform in root.Traverse()) + { + var (position, rotation) = globalTransformMap[transform]; + + transform.SetPositionAndRotation(position.ReverseZ(), rotation.ReverseZ()); + } + } + + /// + /// Setup nodes. + /// + /// List of node. + /// A vgo storage. + /// A vgo model asset. + protected virtual void SetupNodes(List nodes, in IVgoStorage vgoStorage, VgoModelAsset vgoModelAsset) + { + if (vgoStorage.Layout.nodes is null) + { + return; + } + + for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) + { + SetupNode(nodes, nodeIndex, vgoStorage.Layout, vgoStorage.GeometryCoordinate, vgoModelAsset); + } + + // UnityEngine.Renderer + for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) + { + var vgoNode = vgoStorage.Layout.nodes[nodeIndex]; + + if (vgoNode is null) + { + continue; + } + + if (vgoStorage.IsSpecVersion_2_4_orLower) + { + if (vgoNode.mesh >= 0) + { + AttachMeshAndRenderer(nodes, nodeIndex, vgoStorage, vgoModelAsset, _Option.ShowMesh, _Option.UpdateWhenOffscreen); + } + } + else + { + if (vgoNode.meshRenderer != null) + { + AttachMeshAndRenderer(nodes, nodeIndex, vgoStorage, vgoModelAsset, _Option.ShowMesh, _Option.UpdateWhenOffscreen); + } + } + } + + vgoModelAsset.ColliderList = CreateUnityColliderList(nodes); + + // UnigyEngine.Cloth + for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) + { + SetupNodeCloth(nodes, nodeIndex, vgoStorage, vgoModelAsset.ColliderList); + } + + // UnigyEngine.VgoSpringBone + for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) + { + SetupNodeSpringBone(nodes, nodeIndex, vgoStorage.Layout, vgoStorage.GeometryCoordinate, vgoModelAsset); + } + } + + /// + /// Setup a node. + /// + /// List of node. + /// The index of layout.nodes. + /// A vgo layout. + /// + /// A vgo model asset. + protected virtual void SetupNode( + List nodes, + in int nodeIndex, + in VgoLayout vgoLayout, + in VgoGeometryCoordinate geometryCoordinate, + VgoModelAsset vgoModelAsset) + { + if (vgoLayout.nodes is null) + { + return; + } + + // GameObject + GameObject go = nodes[nodeIndex].gameObject; + + VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; + + if (vgoNode is null) + { + return; + } + + go.SetActive(vgoNode.isActive); + + go.isStatic = vgoNode.isStatic; + + // Tag + if ((string.IsNullOrEmpty(vgoNode.tag) == false) && (vgoNode.tag != "Untagged")) + { + try + { + go.tag = vgoNode.tag; + } + catch + { + // UnityException: Tag: is not defined. + } + } + + // Layer + if ((0 <= vgoNode.layer) && (vgoNode.layer <= 31)) + { + try + { + go.layer = vgoNode.layer; + } + catch + { + // + } + } + + // Animator + if (vgoNode.animator != null) + { + Animator animator = go.AddComponent(); + + VgoAnimatorConverter.SetComponentValue(animator, vgoNode.animator); + + // Avatar + if (vgoNode.animator.humanAvatar != null) + { + animator.avatar = VgoAvatarConverter.CreateHumanAvatar(vgoNode.animator.humanAvatar, go, nodes); + + AvatarConfiguration avatarConfiguration = CreateAvatarConfiguration(vgoNode.animator.humanAvatar); + + vgoModelAsset.Avatar = animator.avatar; + + vgoModelAsset.ScriptableObjectList.Add(avatarConfiguration); + } + } + + // Animation + if (vgoNode.animation != null) + { + Animation animation = go.AddComponent(); + + try + { + VgoAnimationConverter.SetComponentValue(animation, vgoNode.animation, vgoModelAsset.AnimationClipList, geometryCoordinate); + + if (animation.enabled && + animation.playAutomatically && + animation.isPlaying == false) + { + animation.Play(); + } + } + catch + { + // + } + } + + // Rigidbody + if (vgoNode.rigidbody != null) + { + Rigidbody rigidbody = go.AddComponent(); ; + + VgoRigidbodyConverter.SetComponentValue(rigidbody, vgoNode.rigidbody); + } + + // Colliders + if ((vgoNode.colliders != null) && (vgoLayout.colliders != null)) + { + foreach (int colliderIndex in vgoNode.colliders) + { + VgoCollider? vgoCollider = vgoLayout.colliders.GetNullableValueOrDefault(colliderIndex); + + if (vgoCollider is null) + { + continue; + } + + Collider collider; + + switch (vgoCollider.type) + { + case VgoColliderType.Box: + collider = go.AddComponent(); + break; + case VgoColliderType.Capsule: + collider = go.AddComponent(); + break; + case VgoColliderType.Sphere: + collider = go.AddComponent(); + break; + default: + continue; + } + + VgoColliderConverter.SetComponentValue(collider, vgoCollider, geometryCoordinate); + } + } + + // SpringBoneCollider + try + { + if (vgoNode.springBoneColliderGroup != -1 && + vgoLayout.springBoneInfo?.colliderGroups != null && + vgoLayout.springBoneInfo.colliderGroups.TryGetValue(vgoNode.springBoneColliderGroup, out var layoutSpringBoneColliderGroup) && + layoutSpringBoneColliderGroup != null) + { + var component = go.AddComponent(); + + if ((layoutSpringBoneColliderGroup.colliders != null) && (layoutSpringBoneColliderGroup.colliders.Length > 0)) + { + component.colliders = new VgoSpringBone.SpringBoneCollider[layoutSpringBoneColliderGroup.colliders.Length]; + + for (int index = 0; index < layoutSpringBoneColliderGroup.colliders.Length; index++) + { + VgoSpringBoneCollider? layoutCollider = layoutSpringBoneColliderGroup.colliders[index]; + + if (layoutCollider == null) + { + continue; + } + + component.colliders[index] = new VgoSpringBone.SpringBoneCollider + { + colliderType = (VgoSpringBone.SpringBoneColliderType)layoutCollider.colliderType, + offset = layoutCollider.offset.ToUnityVector3(geometryCoordinate), + radius = layoutCollider.radius.GetValueOrDefault(0.0f), + }; + } + } + + component.gizmoColor = layoutSpringBoneColliderGroup.gizmoColor.ToUnityColor(); + + if (vgoModelAsset.SpringBoneColliderGroupArray != null) + { + vgoModelAsset.SpringBoneColliderGroupArray[vgoNode.springBoneColliderGroup] = component; + } + } + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + // Light + try + { + if (vgoNode.light != -1 && + vgoLayout.lights != null && + vgoLayout.lights.TryGetValue(vgoNode.light, out var vgoLight) && + vgoLight != null) + { + Light light = go.AddComponent(); + + VgoLightConverter.SetComponentValue(light, vgoLight); + } + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + // ParticleSystem + try + { + if (vgoNode.particle != -1 && + vgoLayout.particles != null && + vgoLayout.particles.TryGetValue(vgoNode.particle, out var vgoParticleSystem) && + vgoParticleSystem != null) + { + _ParticleSystemImporter.AddComponent(go, vgoParticleSystem, geometryCoordinate, vgoModelAsset.MaterialList, vgoModelAsset.Texture2dList); + } + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + // Skybox + if (vgoNode.skybox != null) + { + var skybox = go.AddComponent(); + + skybox.enabled = vgoNode.skybox.enabled; + + if (vgoModelAsset.MaterialList != null && + vgoModelAsset.MaterialList.TryGetValue(vgoNode.skybox.materialIndex, out var skyboxMaterial) && + skyboxMaterial != null) + { + skybox.material = skyboxMaterial; + } + } + + // Right + if (vgoNode.right != null) + { + VgoRight vgoRightComponent = go.AddComponent(); + + if (vgoRightComponent != null) + { + vgoRightComponent.Right = new NewtonVgo.VgoRight(vgoNode.right); + } + } + } + + #endregion + + #region layout.springBoneInfo + + /// + /// Setup a node spring bone. + /// + /// List of node. + /// The index of layout.nodes. + /// A vgo layout. + /// + /// A vgo model asset. + protected virtual void SetupNodeSpringBone( + List nodes, + in int nodeIndex, + in VgoLayout vgoLayout, + in VgoGeometryCoordinate geometryCoordinate, + VgoModelAsset vgoModelAsset) + { + if (vgoLayout.nodes is null) + { + return; + } + + if (vgoLayout.springBoneInfo?.springBoneGroups == null) + { + return; + } + + VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; + + if (vgoNode == null) + { + return; + } + + if (vgoNode.springBoneGroups == null) + { + return; + } + + GameObject go = nodes[nodeIndex].gameObject; + + foreach (var groupIndex in vgoNode.springBoneGroups) + { + if (vgoLayout.springBoneInfo.springBoneGroups.TryGetValue(groupIndex, out var layoutSpringBoneGroup) == false) + { + continue; + } + + if (layoutSpringBoneGroup == null) + { + continue; + } + + var component = go.AddComponent(); + + // @notice component.name indicates gameObject.name + //component.name = string.IsNullOrEmpty(layoutSpringBoneGroup.name) ? go.name : layoutSpringBoneGroup.name; + component.enabled = layoutSpringBoneGroup.enabled; + component.comment = layoutSpringBoneGroup.comment; + component.dragForce = layoutSpringBoneGroup.dragForce.SafeValue(0.0f, 1.0f, 0.0f); + component.stiffnessForce = layoutSpringBoneGroup.stiffnessForce.SafeValue(0.0f, 4.0f, 1.0f); + component.gravityDirection = layoutSpringBoneGroup.gravityDirection.ToUnityVector3(geometryCoordinate); + component.gravityPower = layoutSpringBoneGroup.gravityPower.SafeValue(0.0f, 2.0f, 1.0f); + component.hitRadius = layoutSpringBoneGroup.hitRadius.SafeValue(0.0f, 0.5f, 0.1f); + component.drawGizmo = layoutSpringBoneGroup.drawGizmo; + component.gizmoColor = layoutSpringBoneGroup.gizmoColor.ToUnityColor(); + + // rootBones + if (layoutSpringBoneGroup.rootBones != null && + layoutSpringBoneGroup.rootBones.Any()) + { + component.rootBones = new Transform[layoutSpringBoneGroup.rootBones.Length]; + + for (int index = 0; index < layoutSpringBoneGroup.rootBones.Length; index++) + { + if (nodes.TryGetValue(layoutSpringBoneGroup.rootBones[index], out Transform rootBoneNode)) + { + component.rootBones[index] = rootBoneNode; + } + } + } + + // colliderGroups + if (layoutSpringBoneGroup.colliderGroups != null && + layoutSpringBoneGroup.colliderGroups.Any() && + vgoModelAsset.SpringBoneColliderGroupArray != null) + { + component.colliderGroups = new VgoSpringBone.VgoSpringBoneColliderGroup[layoutSpringBoneGroup.colliderGroups.Length]; + + for (int index = 0; index < layoutSpringBoneGroup.colliderGroups.Length; index++) + { + if (layoutSpringBoneGroup.colliderGroups[index].IsInRangeOf(vgoModelAsset.SpringBoneColliderGroupArray)) + { + var colliderGroup = vgoModelAsset.SpringBoneColliderGroupArray[layoutSpringBoneGroup.colliderGroups[index]]; + + component.colliderGroups[index] = colliderGroup; + } + } + } + } + } + + #endregion + + #region layout.clothes + + /// + /// Setup a node cloth. + /// + /// List of node. + /// The index of layout.nodes. + /// A vgo storage. + /// List of collider. + protected virtual void SetupNodeCloth(List nodes, in int nodeIndex, in IVgoStorage vgoStorage, in List colliderList) + { + if (vgoStorage.Layout.nodes is null) + { + return; + } + + if (vgoStorage.Layout.clothes is null) + { + return; + } + + VgoNode? vgoNode = vgoStorage.Layout.nodes[nodeIndex]; + + if (vgoNode is null) + { + return; + } + + if (vgoNode.cloth == -1) + { + return; + } + + VgoCloth? vgoCloth = vgoStorage.Layout.clothes.GetNullableValueOrDefault(vgoNode.cloth); + + if (vgoCloth == null) + { + return; + } + + GameObject go = nodes[nodeIndex].gameObject; + + Cloth cloth = go.AddComponent(); + + VgoClothConverter.SetComponentValue(cloth, vgoCloth, vgoStorage.GeometryCoordinate, colliderList, vgoStorage); + } + + #endregion + + #region Collider + + /// + /// Create list of unity collider. + /// + /// List of node. + /// list of unity collider. + /// + /// VgoExporter.CreateUnityColliderList is same logic. + /// + protected virtual List CreateUnityColliderList(in List nodes) + { + var colliderList = new List(); + + for (int index = 0; index < nodes.Count; index++) + { + GameObject node = nodes[index].gameObject; + + if (node.TryGetComponentEx(out Collider collider)) + { + if ((collider is BoxCollider) || + (collider is CapsuleCollider) || + (collider is SphereCollider)) + { + colliderList.Add(collider); + } + } + } + + return colliderList; + } + + #endregion + + #region Renderer + + /// + /// Attach mesh and renderer to node. + /// + /// List of node. + /// The index of layout.node. + /// A vgo storage. + /// A vgo model asset. + /// Whether show mesh renderer. + /// Whether update skinned mesh renderer when off screen. + protected virtual void AttachMeshAndRenderer( + List nodes, + in int nodeIndex, + in IVgoStorage vgoStorage, + VgoModelAsset vgoModelAsset, +#if UNITY_2021_2_OR_NEWER + in bool showMesh = true, + in bool updateWhenOffscreen = false +#else + bool showMesh = true, + bool updateWhenOffscreen = false +#endif + ) + { + if (vgoStorage.Layout.nodes is null) + { + return; + } + + if (vgoModelAsset.MeshAssetList is null) + { + return; + } + + VgoNode? vgoNode = vgoStorage.Layout.nodes[nodeIndex]; + + if (vgoNode is null) + { + return; + } + + GameObject go = nodes[nodeIndex].gameObject; + + Renderer renderer; + + if (vgoStorage.IsSpecVersion_2_4_orLower) + { + MeshAsset meshAsset = vgoModelAsset.MeshAssetList[vgoNode.mesh]; + + Mesh mesh = meshAsset.Mesh; + + Material?[]? materials = meshAsset.Materials; + + if ((meshAsset.Mesh.blendShapeCount == 0) && (vgoNode.skin == -1)) + { + // without blendshape and bone skinning + var filter = go.AddComponent(); + + filter.sharedMesh = mesh; + + var meshRenderer = go.AddComponent(); + + if (materials != null) + { + meshRenderer.sharedMaterials = materials; + } + + renderer = meshRenderer; + } + else + { + var skinnedMeshRenderer = go.AddComponent(); + + skinnedMeshRenderer.sharedMesh = mesh; + + if (vgoNode.skin >= 0) + { + SetupSkin(skinnedMeshRenderer, vgoNode.skin, nodes, vgoStorage); + } + + skinnedMeshRenderer.sharedMaterials = materials; + + if (updateWhenOffscreen) + { + skinnedMeshRenderer.updateWhenOffscreen = true; + } + + renderer = skinnedMeshRenderer; + } + + if ((meshAsset.BlendShapeConfig != null) && + (meshAsset.BlendShapeConfig.Kind != VgoBlendShapeKind.None)) + { + var vgoBlendShape = go.AddComponent(); + + BlendShapeConfiguration blendShapeConfiguration = ScriptableObject.CreateInstance(); + + blendShapeConfiguration.Name = meshAsset.BlendShapeConfig.Name ?? string.Empty; + blendShapeConfiguration.Kind = meshAsset.BlendShapeConfig.Kind; + blendShapeConfiguration.FaceParts = meshAsset.BlendShapeConfig.FaceParts; + blendShapeConfiguration.Blinks = meshAsset.BlendShapeConfig.Blinks; + blendShapeConfiguration.Visemes = meshAsset.BlendShapeConfig.Visemes; + blendShapeConfiguration.Presets = meshAsset.BlendShapeConfig.Presets; + + vgoBlendShape.BlendShapeConfiguration = blendShapeConfiguration; + + vgoModelAsset.ScriptableObjectList.Add(blendShapeConfiguration); + } + } + else + { + if (vgoNode.meshRenderer is null) + { + return; + } + + VgoMeshRenderer vgoMeshRenderer = vgoNode.meshRenderer; + + MeshAsset meshAsset = vgoModelAsset.MeshAssetList[vgoMeshRenderer.mesh]; + + Mesh mesh = meshAsset.Mesh; + + Material?[]? materials = null; + + if (vgoMeshRenderer.materials != null && + vgoMeshRenderer.materials.Any() && + vgoModelAsset.MaterialList != null && + vgoModelAsset.MaterialList.Any()) + { + var materialList = vgoModelAsset.MaterialList; + + materials = vgoMeshRenderer.materials + .Select(materialIndex => materialList[materialIndex]) + .ToArray(); + } + + if (vgoNode.particle >= 0) + { + if (go.TryGetComponentEx(out ParticleSystemRenderer particleSystemRenderer)) + { + if (particleSystemRenderer.renderMode == UnityEngine.ParticleSystemRenderMode.Mesh) + { + particleSystemRenderer.mesh = mesh; + + if (materials != null) + { + particleSystemRenderer.sharedMaterials = materials; + } + } + } + + return; + } + else if (vgoNode.skin >= 0) + { + var skinnedMeshRenderer = go.AddComponent(); + + //skinnedMeshRenderer.name = vgoMeshRenderer.name; + skinnedMeshRenderer.enabled = vgoMeshRenderer.enabled; + skinnedMeshRenderer.sharedMesh = mesh; + + SetupSkin(skinnedMeshRenderer, vgoNode.skin, nodes, vgoStorage); + + if (materials != null) + { + skinnedMeshRenderer.sharedMaterials = materials; + } + + if (updateWhenOffscreen) + { + skinnedMeshRenderer.updateWhenOffscreen = true; + } + + renderer = skinnedMeshRenderer; + } + else + { + // without blendshape and bone skinning + var filter = go.AddComponent(); + + filter.sharedMesh = mesh; + + var meshRenderer = go.AddComponent(); + + //meshRenderer.name = vgoMeshRenderer.name; + meshRenderer.enabled = vgoMeshRenderer.enabled; + + if (materials != null) + { + meshRenderer.sharedMaterials = materials; + } + + renderer = meshRenderer; + } + + if ((vgoMeshRenderer.blendShapeKind != null) && + (meshAsset.BlendShapeConfig != null)) + { + var vgoBlendShape = go.AddComponent(); + + BlendShapeConfiguration blendShapeConfiguration = ScriptableObject.CreateInstance(); + + blendShapeConfiguration.Name = meshAsset.BlendShapeConfig.Name ?? string.Empty; + + blendShapeConfiguration.Kind = vgoMeshRenderer.blendShapeKind.Value; + + blendShapeConfiguration.FaceParts = meshAsset.BlendShapeConfig.FaceParts; + blendShapeConfiguration.Blinks = meshAsset.BlendShapeConfig.Blinks; + blendShapeConfiguration.Visemes = meshAsset.BlendShapeConfig.Visemes; + + if (vgoMeshRenderer.blendShapePesets != null && + vgoMeshRenderer.blendShapePesets.Any()) + { + blendShapeConfiguration.Presets.AddRange(vgoMeshRenderer.blendShapePesets); + } + + vgoBlendShape.BlendShapeConfiguration = blendShapeConfiguration; + + vgoModelAsset.ScriptableObjectList.Add(blendShapeConfiguration); + } + } + + if (showMesh == false) + { + renderer.enabled = false; + } + } + + #endregion + + #region layout.skins + + /// + /// Set up a skin. + /// + /// A skinned mesh renderer. + /// The index of gltf.skin. + /// List of node. + /// A vgo storage. + protected virtual void SetupSkin(SkinnedMeshRenderer skinnedMeshRenderer, in int skinIndex, in List nodes, in IVgoStorage vgoStorage) + { + if (skinnedMeshRenderer.sharedMesh == null) + { + ThrowHelper.ThrowException(); + + return; + } + + if (vgoStorage.Layout.skins == null) + { + ThrowHelper.ThrowException(); + + return; + } + + if (vgoStorage.Layout.skins.TryGetValue(skinIndex, out var vgoSkin) == false) + { + ThrowHelper.ThrowIndexOutOfRangeException("skins[i]", skinIndex, min: 0, max: vgoStorage.Layout.skins.Count); + } + + if (vgoSkin is null) + { + return; + } + + Mesh mesh = skinnedMeshRenderer.sharedMesh; + + // calculate internal values(boundingBox etc...) when sharedMesh assigned ? + skinnedMeshRenderer.sharedMesh = null; + + if (vgoSkin.joints != null && vgoSkin.joints.Any()) + { + // have bones + + Transform?[] joints = new Transform?[vgoSkin.joints.Length]; + + for (int jointIndex = 0; jointIndex < vgoSkin.joints.Length; jointIndex++) + { + int vgoSkinJointNodeIndex = vgoSkin.joints[jointIndex]; + + if (vgoSkinJointNodeIndex.IsInRangeOf(nodes)) + { + joints[jointIndex] = nodes[vgoSkinJointNodeIndex]; + } + else + { + Debug.LogWarning($"skins[{skinIndex}].joints[{jointIndex}] is out of the range. 0 <= x < {nodes.Count}"); + + //ThrowHelper.ThrowIndexOutOfRangeException($"skins[{skinIndex}].joints[i]", jointIndex, min: 0, max: nodes.Count); + } + } + + skinnedMeshRenderer.bones = joints; + + if (vgoSkin.inverseBindMatrices >= 0) + { + if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) + { + ReadOnlySpan matrixSpan = vgoStorage.GetAccessorSpan(vgoSkin.inverseBindMatrices); + + var bindPoses = new Matrix4x4[matrixSpan.Length]; + + for (int i = 0; i < matrixSpan.Length; i++) + { + bindPoses[i] = matrixSpan[i].ReverseZ(); + } + + mesh.bindposes = bindPoses; + } + else + { + mesh.bindposes = vgoStorage.GetAccessorArrayData(vgoSkin.inverseBindMatrices); + } + } + else + { + // calculate default matrices + // https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html + + Transform meshCoords = skinnedMeshRenderer.transform; // ? + + Matrix4x4[] calculatedBindPoses = new Matrix4x4[joints.Length]; + + for (int index = 0; index < joints.Length; index++) + { + Transform? jointTransform = joints[index]; + + if (jointTransform == null) + { + continue; + } + + calculatedBindPoses[index] = jointTransform.worldToLocalMatrix * meshCoords.localToWorldMatrix; + } + + mesh.bindposes = calculatedBindPoses; + } + } + + skinnedMeshRenderer.sharedMesh = mesh; + + if (vgoSkin.skeleton.IsInRangeOf(nodes)) + { + skinnedMeshRenderer.rootBone = nodes[vgoSkin.skeleton]; + } + } + + #endregion + + #region layout.animationClips + + /// + /// Create animation clip assets. + /// + /// A vgo layout. + /// + /// List of unity animation clip. + protected virtual List CreateAnimationClipAssets(in VgoLayout vgoLayout, in VgoGeometryCoordinate geometryCoordinate) + { + var animationClipList = new List(); + + if ((vgoLayout.animationClips == null) || (vgoLayout.animationClips.Any() == false)) + { + return animationClipList; + } + + for (int animationClipIndex = 0; animationClipIndex < vgoLayout.animationClips.Count; animationClipIndex++) + { + VgoAnimationClip? vgoAnimationClip = vgoLayout.animationClips[animationClipIndex]; + + AnimationClip? animationClip = VgoAnimationClipConverter.CreateAnimationClipOrDefault(vgoAnimationClip, geometryCoordinate); + + animationClipList.Add(animationClip); + } + + return animationClipList; + } + + #endregion + + #region Asset Info + + /// + /// Setup a asset info to root GameObject. + /// + /// A vgo storage. + /// A vgo model asset. + protected virtual void SetupAssetInfo(in IVgoStorage vgoStorage, VgoModelAsset vgoModelAsset) + { + if (vgoStorage.AssetInfo == null) + { + return; + } + + if (vgoModelAsset.Root == null) + { + return; + } + + VgoAssetInfo vgoAssetInfo = vgoStorage.AssetInfo; + + if (vgoAssetInfo.right != null) + { + VgoRight vgoRightComponent = vgoModelAsset.Root.AddComponent(); + + vgoRightComponent.Right = new NewtonVgo.VgoRight(vgoAssetInfo.right); + } + + if (vgoAssetInfo.generator != null) + { + VgoGenerator vgoGeneratorComponent = vgoModelAsset.Root.AddComponent(); + + vgoGeneratorComponent.GeneratorInfo = new VgoGeneratorInfo(vgoAssetInfo.generator); + } + } + + #endregion + + #region Helpers + + /// + /// Create and add avatar configuration. + /// + /// The vgo human avatar. + protected virtual AvatarConfiguration CreateAvatarConfiguration(in VgoHumanAvatar vgoHumanAvatar) + { + var avatarConfiguration = ScriptableObject.CreateInstance(); + + avatarConfiguration.Name = vgoHumanAvatar.name ?? string.Empty; + avatarConfiguration.HumanBones = vgoHumanAvatar.humanBones + .Where(x => x != null) + .Select(x => x!) + .ToList(); + + return avatarConfiguration; + } + + /// + /// Get vgo node transforms. + /// + /// A vgo storage. + /// An array of matrix. + protected virtual System.Numerics.Matrix4x4[] GetVgoNodeTransforms(in IVgoStorage vgoStorage) + { + if (vgoStorage.ResourceAccessors.Count(x => x.kind == VgoResourceAccessorKind.NodeTransform) != 1) + { +#if NET_STANDARD_2_1 + ThrowHelper.ThrowException(); +#else + throw new Exception(); +#endif + } + + if (vgoStorage.Layout.nodes is null) + { +#if NET_STANDARD_2_1 + ThrowHelper.ThrowException(); +#else + throw new Exception(); +#endif + } + + VgoResourceAccessor accessor = vgoStorage.ResourceAccessors.First(x => x.kind == VgoResourceAccessorKind.NodeTransform); + + ArraySegment transformBytes = vgoStorage.GetAccessorBytes(accessor); + + var transforms = new System.Numerics.Matrix4x4[vgoStorage.Layout.nodes.Count]; + + transformBytes.MarshalCopyTo(transforms); + + return transforms; + } + + #endregion + } +} diff --git a/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs.meta b/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs.meta new file mode 100644 index 0000000..73151f6 --- /dev/null +++ b/UniVgo2/Runtime/Porters/VgoImporter.Internal.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3885837035a81346b9bb651fbb1db6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UniVgo2/Runtime/Porters/VgoImporter.cs b/UniVgo2/Runtime/Porters/VgoImporter.cs index 5bdddab..0aebc3d 100644 --- a/UniVgo2/Runtime/Porters/VgoImporter.cs +++ b/UniVgo2/Runtime/Porters/VgoImporter.cs @@ -5,12 +5,18 @@ #nullable enable namespace UniVgo2 { +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + // +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + using Cysharp.Threading.Tasks; +#else + using System.Threading.Tasks; +#endif + using NewtonVgo; - using System; - using System.Collections.Generic; - using System.Linq; + using System.IO; + using System.Threading; using UnityEngine; - using UniVgo2.Converters; using UniVgo2.Porters; /// @@ -67,7 +73,7 @@ public VgoImporter(in VgoImporterOption option) #endregion - #region Public Methods + #region Public Methods (Sync) /// /// Load a 3D model from the specified file. @@ -77,9 +83,11 @@ public VgoImporter(in VgoImporterOption option) /// A vgo model asset. public virtual VgoModelAsset Load(in string vgoFilePath, in string? vgkFilePath = null) { - var vgoStorage = new VgoStorage(vgoFilePath, vgkFilePath); + var vgoStorage = new VgoStorage(); + + vgoStorage.ParseVgo(vgoFilePath, vgkFilePath); - return Load(vgoStorage); + return LoadInternal(vgoStorage); } /// @@ -90,1192 +98,308 @@ public virtual VgoModelAsset Load(in string vgoFilePath, in string? vgkFilePath /// A vgo model asset. public virtual VgoModelAsset Load(in byte[] vgoBytes, in byte[]? vgkBytes = null) { - var vgoStorage = new VgoStorage(vgoBytes, vgkBytes); - - return Load(vgoStorage); - } - - /// - /// Load a 3D model from the specified vgo storage. - /// - /// A vgo storage. - /// A vgo model asset. - public virtual VgoModelAsset Load(in IVgoStorage vgoStorage) - { - var vgoModelAsset = new VgoModelAsset(); - - vgoModelAsset.Layout = vgoStorage.Layout; - - // UnityEngine.Texture2D - vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); - - // UnityEngine.Material - vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); - - // UnityEngine.Mesh - if (vgoStorage.IsSpecVersion_2_4_orLower) - { - vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage, vgoModelAsset.MaterialList); - } - else - { - vgoModelAsset.MeshAssetList = _MeshImporter.CreateMeshAssets(vgoStorage); - } - - // UnityEngine.AnimationClip - vgoModelAsset.AnimationClipList = CreateAnimationClipAssets(vgoStorage.Layout, vgoStorage.GeometryCoordinate); - - // UnityEngine.VgoSpringBoneColliderGroup - vgoModelAsset.SpringBoneColliderGroupArray = CreateSpringBoneColliderGroupArray(vgoStorage.Layout); - - // UnityEngine.GameObejct - List nodes = CreateNodes(vgoStorage); - - CreateNodeHierarchy(nodes, vgoStorage.Layout); - - vgoModelAsset.Root = nodes[0].gameObject; - - if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) - { - FixCoordinate(nodes); - } - - SetupNodes(nodes, vgoStorage, vgoModelAsset); + var vgoStorage = new VgoStorage(); - SetupAssetInfo(vgoStorage, vgoModelAsset); + vgoStorage.ParseVgo(vgoBytes, vgkBytes); - return vgoModelAsset; + return LoadInternal(vgoStorage); } /// - /// Extract a 3D model asset from the specified file. + /// Load a 3D model from the specified bytes. /// - /// The file path of the vgo. - /// The file path of the vgk. + /// The vgo stream. + /// The vgk stream. /// A vgo model asset. - /// for ScriptedImporter - public virtual VgoModelAsset Extract(in string vgoFilePath, in string? vgkFilePath = null) + public virtual VgoModelAsset Load(in Stream vgoStream, in Stream? vgkStream = null) { - var vgoStorage = new VgoStorage(vgoFilePath, vgkFilePath); - - var vgoModelAsset = new VgoModelAsset(); - - vgoModelAsset.Layout = vgoStorage.Layout; + var vgoStorage = new VgoStorage(); - // UnityEngine.Texture2D - vgoModelAsset.Texture2dList = _TextureImporter.CreateTextureAssets(vgoStorage); + vgoStorage.ParseVgo(vgoStream, vgkStream); - // UnityEngine.Material - vgoModelAsset.MaterialList = CreateMaterialAssets(vgoStorage, vgoModelAsset.Texture2dList); - - return vgoModelAsset; + return LoadInternal(vgoStorage); } - #endregion - - #region layout.materials - /// - /// Create material assets. + /// Load a 3D model from the specified vgo storage. /// /// A vgo storage. - /// List of unity texture 2D. - /// List of unity material. - protected virtual List CreateMaterialAssets(in IVgoStorage vgoStorage, in List texture2dList) - { - var materialList = new List(); - - if ((vgoStorage.Layout.materials == null) || (vgoStorage.Layout.materials.Any() == false)) - { - return materialList; - } - - for (int materialIndex = 0; materialIndex < vgoStorage.Layout.materials.Count; materialIndex++) - { - VgoMaterial? vgoMaterial = vgoStorage.Layout.materials[materialIndex]; - - if (vgoMaterial is null) - { - materialList.Add(null); - } - else - { - Material material = _MaterialImporter.CreateMaterialAsset(vgoMaterial, texture2dList); - - materialList.Add(material); - } - } - - return materialList; - } - - #endregion - - #region layout.springBoneInfo - - /// - /// Create vgo spring bone collider groups. - /// - /// A vgo layout. - /// An array of vgo spring bone collider group. - protected virtual VgoSpringBone.VgoSpringBoneColliderGroup[]? CreateSpringBoneColliderGroupArray(in VgoLayout vgoLayout) + /// A vgo model asset. + public virtual VgoModelAsset Load(in IVgoStorage vgoStorage) { - if (vgoLayout.springBoneInfo == null) - { - return null; - } - - if (vgoLayout.springBoneInfo.colliderGroups == null) - { - return null; - } - - if (vgoLayout.springBoneInfo.colliderGroups.Any() == false) - { - return null; - } - - return new VgoSpringBone.VgoSpringBoneColliderGroup[vgoLayout.springBoneInfo.colliderGroups.Count]; + return LoadInternal(vgoStorage); } #endregion - #region layout.nodes +#if UNITY_WEBGL + // +#else + #region Public Methods (Async) /// - /// Create nodes. + /// Load a 3D model from the specified file. /// - /// A vgo storage. - /// List of transform. - protected virtual List CreateNodes(in IVgoStorage vgoStorage) - { - if (vgoStorage.Layout.nodes is null) - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowException(); + /// The file path of the vgo. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(string vgoFilePath, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(string vgoFilePath, CancellationToken cancellationToken) #else - throw new Exception(); + public virtual async Task LoadAsync(string vgoFilePath, CancellationToken cancellationToken) #endif - } - - var nodes = new List(vgoStorage.Layout.nodes.Count); - - System.Numerics.Matrix4x4[] matrixes = GetVgoNodeTransforms(vgoStorage); - - for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) - { - string? name = vgoStorage.Layout.nodes[nodeIndex]?.name; - - if (name is null || name == string.Empty) - { - name = string.Format("node_{0:000}", nodeIndex); - } - else if (name.Contains("/")) - { - name = name.Replace("/", "_"); - } - - // GameObject - var go = new GameObject(name); - - // Transform - Matrix4x4 matrix = matrixes[nodeIndex].ToUnityMatrix(); - - go.transform.localPosition = matrix.ExtractTranslation(); - go.transform.localRotation = matrix.ExtractRotation(); - go.transform.localScale = matrix.ExtractScale(); - - nodes.Add(go.transform); - } - - return nodes; - } - - /// - /// Create node hierarchy. - /// - /// List of node. - /// A vgo layout. - protected virtual void CreateNodeHierarchy(List nodes, in VgoLayout vgoLayout) - { - if (vgoLayout.nodes is null) - { - ThrowHelper.ThrowException(); - - return; - } - - var rootNode = vgoLayout.nodes[0]; - - if (rootNode is null) - { - ThrowHelper.ThrowException(); - - return; - } - - if (rootNode.isRoot == false) - { - ThrowHelper.ThrowFormatException("nodes[0].isRoot: false"); - } - - for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++) - { - VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; - - if (vgoNode == null) - { - continue; - } - - if (vgoNode.children == null) - { - continue; - } - - foreach (int child in vgoNode.children) - { - nodes[child].transform.SetParent(parent: nodes[nodeIndex].transform, worldPositionStays: false); - } - } - } - - /// - /// Fix node's coordinate. z-back to z-forward - /// - /// List of node. - protected virtual void FixCoordinate(List nodes) { - Dictionary globalTransformMap - = nodes.ToDictionary(x => x, x => (x.position, x.rotation)); + var vgoStorage = new VgoStorage(); - Transform root = nodes[0]; - - foreach (Transform transform in root.Traverse()) - { - var (position, rotation) = globalTransformMap[transform]; - - transform.SetPositionAndRotation(position.ReverseZ(), rotation.ReverseZ()); - } - } - - /// - /// Setup nodes. - /// - /// List of node. - /// A vgo storage. - /// A vgo model asset. - protected virtual void SetupNodes(List nodes, in IVgoStorage vgoStorage, VgoModelAsset vgoModelAsset) - { - if (vgoStorage.Layout.nodes is null) - { - return; - } - - for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) - { - SetupNode(nodes, nodeIndex, vgoStorage.Layout, vgoStorage.GeometryCoordinate, vgoModelAsset); - } - - // UnityEngine.Renderer - for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) - { - var vgoNode = vgoStorage.Layout.nodes[nodeIndex]; - - if (vgoNode is null) - { - continue; - } - - if (vgoStorage.IsSpecVersion_2_4_orLower) - { - if (vgoNode.mesh >= 0) - { - AttachMeshAndRenderer(nodes, nodeIndex, vgoStorage, vgoModelAsset, _Option.ShowMesh, _Option.UpdateWhenOffscreen); - } - } - else - { - if (vgoNode.meshRenderer != null) - { - AttachMeshAndRenderer(nodes, nodeIndex, vgoStorage, vgoModelAsset, _Option.ShowMesh, _Option.UpdateWhenOffscreen); - } - } - } - - vgoModelAsset.ColliderList = CreateUnityColliderList(nodes); - - // UnigyEngine.Cloth - for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) - { - SetupNodeCloth(nodes, nodeIndex, vgoStorage, vgoModelAsset.ColliderList); - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoFilePath, vgkFilePath: null, cancellationToken); - // UnigyEngine.VgoSpringBone - for (int nodeIndex = 0; nodeIndex < vgoStorage.Layout.nodes.Count; nodeIndex++) - { - SetupNodeSpringBone(nodes, nodeIndex, vgoStorage.Layout, vgoStorage.GeometryCoordinate, vgoModelAsset); - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } /// - /// Setup a node. + /// Load a 3D model from the specified file. /// - /// List of node. - /// The index of layout.nodes. - /// A vgo layout. - /// - /// A vgo model asset. - protected virtual void SetupNode( - List nodes, - in int nodeIndex, - in VgoLayout vgoLayout, - in VgoGeometryCoordinate geometryCoordinate, - VgoModelAsset vgoModelAsset) + /// The file path of the vgo. + /// The file path of the crypt key. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) +#else + public virtual async Task LoadAsync(string vgoFilePath, string vgkFilePath, CancellationToken cancellationToken) +#endif { - if (vgoLayout.nodes is null) - { - return; - } - - // GameObject - GameObject go = nodes[nodeIndex].gameObject; - - VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; - - if (vgoNode is null) - { - return; - } - - go.SetActive(vgoNode.isActive); - - go.isStatic = vgoNode.isStatic; - - // Tag - if ((string.IsNullOrEmpty(vgoNode.tag) == false) && (vgoNode.tag != "Untagged")) - { - try - { - go.tag = vgoNode.tag; - } - catch - { - // UnityException: Tag: is not defined. - } - } - - // Layer - if ((0 <= vgoNode.layer) && (vgoNode.layer <= 31)) - { - try - { - go.layer = vgoNode.layer; - } - catch - { - // - } - } - - // Animator - if (vgoNode.animator != null) - { - Animator animator = go.AddComponent(); - - VgoAnimatorConverter.SetComponentValue(animator, vgoNode.animator); - - // Avatar - if (vgoNode.animator.humanAvatar != null) - { - animator.avatar = VgoAvatarConverter.CreateHumanAvatar(vgoNode.animator.humanAvatar, go, nodes); - - AvatarConfiguration avatarConfiguration = CreateAvatarConfiguration(vgoNode.animator.humanAvatar); - - vgoModelAsset.Avatar = animator.avatar; - - vgoModelAsset.ScriptableObjectList.Add(avatarConfiguration); - } - } - - // Animation - if (vgoNode.animation != null) - { - Animation animation = go.AddComponent(); - - try - { - VgoAnimationConverter.SetComponentValue(animation, vgoNode.animation, vgoModelAsset.AnimationClipList, geometryCoordinate); - - if (animation.enabled && - animation.playAutomatically && - animation.isPlaying == false) - { - animation.Play(); - } - } - catch - { - // - } - } + var vgoStorage = new VgoStorage(); - // Rigidbody - if (vgoNode.rigidbody != null) - { - Rigidbody rigidbody = go.AddComponent(); ; - - VgoRigidbodyConverter.SetComponentValue(rigidbody, vgoNode.rigidbody); - } - - // Colliders - if ((vgoNode.colliders != null) && (vgoLayout.colliders != null)) - { - foreach (int colliderIndex in vgoNode.colliders) - { - VgoCollider? vgoCollider = vgoLayout.colliders.GetNullableValueOrDefault(colliderIndex); - - if (vgoCollider is null) - { - continue; - } - - Collider collider; - - switch (vgoCollider.type) - { - case VgoColliderType.Box: - collider = go.AddComponent(); - break; - case VgoColliderType.Capsule: - collider = go.AddComponent(); - break; - case VgoColliderType.Sphere: - collider = go.AddComponent(); - break; - default: - continue; - } - - VgoColliderConverter.SetComponentValue(collider, vgoCollider, geometryCoordinate); - } - } - - // SpringBoneCollider - try - { - if (vgoNode.springBoneColliderGroup != -1 && - vgoLayout.springBoneInfo?.colliderGroups != null && - vgoLayout.springBoneInfo.colliderGroups.TryGetValue(vgoNode.springBoneColliderGroup, out var layoutSpringBoneColliderGroup) && - layoutSpringBoneColliderGroup != null) - { - var component = go.AddComponent(); - - if ((layoutSpringBoneColliderGroup.colliders != null) && (layoutSpringBoneColliderGroup.colliders.Length > 0)) - { - component.colliders = new VgoSpringBone.SpringBoneCollider[layoutSpringBoneColliderGroup.colliders.Length]; - - for (int index = 0; index < layoutSpringBoneColliderGroup.colliders.Length; index++) - { - VgoSpringBoneCollider? layoutCollider = layoutSpringBoneColliderGroup.colliders[index]; - - if (layoutCollider == null) - { - continue; - } - - component.colliders[index] = new VgoSpringBone.SpringBoneCollider - { - colliderType = (VgoSpringBone.SpringBoneColliderType)layoutCollider.colliderType, - offset = layoutCollider.offset.ToUnityVector3(geometryCoordinate), - radius = layoutCollider.radius.GetValueOrDefault(0.0f), - }; - } - } - - component.gizmoColor = layoutSpringBoneColliderGroup.gizmoColor.ToUnityColor(); - - if (vgoModelAsset.SpringBoneColliderGroupArray != null) - { - vgoModelAsset.SpringBoneColliderGroupArray[vgoNode.springBoneColliderGroup] = component; - } - } - } - catch (Exception ex) - { - Debug.LogException(ex); - } - - // Light - try - { - if (vgoNode.light != -1 && - vgoLayout.lights != null && - vgoLayout.lights.TryGetValue(vgoNode.light, out var vgoLight) && - vgoLight != null) - { - Light light = go.AddComponent(); - - VgoLightConverter.SetComponentValue(light, vgoLight); - } - } - catch (Exception ex) - { - Debug.LogException(ex); - } - - // ParticleSystem - try - { - if (vgoNode.particle != -1 && - vgoLayout.particles != null && - vgoLayout.particles.TryGetValue(vgoNode.particle, out var vgoParticleSystem) && - vgoParticleSystem != null) - { - _ParticleSystemImporter.AddComponent(go, vgoParticleSystem, geometryCoordinate, vgoModelAsset.MaterialList, vgoModelAsset.Texture2dList); - } - } - catch (Exception ex) - { - Debug.LogException(ex); - } - - // Skybox - if (vgoNode.skybox != null) - { - var skybox = go.AddComponent(); - - skybox.enabled = vgoNode.skybox.enabled; - - if (vgoModelAsset.MaterialList != null && - vgoModelAsset.MaterialList.TryGetValue(vgoNode.skybox.materialIndex, out var skyboxMaterial) && - skyboxMaterial != null) - { - skybox.material = skyboxMaterial; - } - } - - // Right - if (vgoNode.right != null) - { - VgoRight vgoRightComponent = go.AddComponent(); +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoFilePath, vgkFilePath, cancellationToken); - if (vgoRightComponent != null) - { - vgoRightComponent.Right = new NewtonVgo.VgoRight(vgoNode.right); - } - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } - #endregion - - #region layout.springBoneInfo - /// - /// Setup a node spring bone. + /// Load a 3D model from the specified bytes. /// - /// List of node. - /// The index of layout.nodes. - /// A vgo layout. - /// - /// A vgo model asset. - protected virtual void SetupNodeSpringBone( - List nodes, - in int nodeIndex, - in VgoLayout vgoLayout, - in VgoGeometryCoordinate geometryCoordinate, - VgoModelAsset vgoModelAsset) + /// The vgo bytes. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) +#else + public virtual async Task LoadAsync(byte[] vgoBytes, CancellationToken cancellationToken) +#endif { - if (vgoLayout.nodes is null) - { - return; - } - - if (vgoLayout.springBoneInfo?.springBoneGroups == null) - { - return; - } - - VgoNode? vgoNode = vgoLayout.nodes[nodeIndex]; + var vgoStorage = new VgoStorage(); - if (vgoNode == null) - { - return; - } - - if (vgoNode.springBoneGroups == null) - { - return; - } - - GameObject go = nodes[nodeIndex].gameObject; - - foreach (var groupIndex in vgoNode.springBoneGroups) - { - if (vgoLayout.springBoneInfo.springBoneGroups.TryGetValue(groupIndex, out var layoutSpringBoneGroup) == false) - { - continue; - } - - if (layoutSpringBoneGroup == null) - { - continue; - } - - var component = go.AddComponent(); - - // @notice component.name indicates gameObject.name - //component.name = string.IsNullOrEmpty(layoutSpringBoneGroup.name) ? go.name : layoutSpringBoneGroup.name; - component.enabled = layoutSpringBoneGroup.enabled; - component.comment = layoutSpringBoneGroup.comment; - component.dragForce = layoutSpringBoneGroup.dragForce.SafeValue(0.0f, 1.0f, 0.0f); - component.stiffnessForce = layoutSpringBoneGroup.stiffnessForce.SafeValue(0.0f, 4.0f, 1.0f); - component.gravityDirection = layoutSpringBoneGroup.gravityDirection.ToUnityVector3(geometryCoordinate); - component.gravityPower = layoutSpringBoneGroup.gravityPower.SafeValue(0.0f, 2.0f, 1.0f); - component.hitRadius = layoutSpringBoneGroup.hitRadius.SafeValue(0.0f, 0.5f, 0.1f); - component.drawGizmo = layoutSpringBoneGroup.drawGizmo; - component.gizmoColor = layoutSpringBoneGroup.gizmoColor.ToUnityColor(); - - // rootBones - if (layoutSpringBoneGroup.rootBones != null && - layoutSpringBoneGroup.rootBones.Any()) - { - component.rootBones = new Transform[layoutSpringBoneGroup.rootBones.Length]; - - for (int index = 0; index < layoutSpringBoneGroup.rootBones.Length; index++) - { - if (nodes.TryGetValue(layoutSpringBoneGroup.rootBones[index], out Transform rootBoneNode)) - { - component.rootBones[index] = rootBoneNode; - } - } - } - - // colliderGroups - if (layoutSpringBoneGroup.colliderGroups != null && - layoutSpringBoneGroup.colliderGroups.Any() && - vgoModelAsset.SpringBoneColliderGroupArray != null) - { - component.colliderGroups = new VgoSpringBone.VgoSpringBoneColliderGroup[layoutSpringBoneGroup.colliderGroups.Length]; - - for (int index = 0; index < layoutSpringBoneGroup.colliderGroups.Length; index++) - { - if (layoutSpringBoneGroup.colliderGroups[index].IsInRangeOf(vgoModelAsset.SpringBoneColliderGroupArray)) - { - var colliderGroup = vgoModelAsset.SpringBoneColliderGroupArray[layoutSpringBoneGroup.colliderGroups[index]]; +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoBytes, vgkBytes: null, cancellationToken); - component.colliderGroups[index] = colliderGroup; - } - } - } - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } - #endregion - - #region layout.clothes - /// - /// Setup a node cloth. + /// Load a 3D model from the specified bytes. /// - /// List of node. - /// The index of layout.nodes. - /// A vgo storage. - /// List of collider. - protected virtual void SetupNodeCloth(List nodes, in int nodeIndex, in IVgoStorage vgoStorage, in List colliderList) + /// The vgo bytes. + /// The vgk bytes. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) +#else + public virtual async Task LoadAsync(byte[] vgoBytes, byte[] vgkBytes, CancellationToken cancellationToken) +#endif { - if (vgoStorage.Layout.nodes is null) - { - return; - } - - if (vgoStorage.Layout.clothes is null) - { - return; - } - - VgoNode? vgoNode = vgoStorage.Layout.nodes[nodeIndex]; - - if (vgoNode is null) - { - return; - } - - if (vgoNode.cloth == -1) - { - return; - } - - VgoCloth? vgoCloth = vgoStorage.Layout.clothes.GetNullableValueOrDefault(vgoNode.cloth); - - if (vgoCloth == null) - { - return; - } + var vgoStorage = new VgoStorage(); - GameObject go = nodes[nodeIndex].gameObject; - - Cloth cloth = go.AddComponent(); +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoBytes, vgkBytes, cancellationToken); - VgoClothConverter.SetComponentValue(cloth, vgoCloth, vgoStorage.GeometryCoordinate, colliderList, vgoStorage); +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } - #endregion - - #region Collider - /// - /// Create list of unity collider. + /// Load a 3D model from the specified stream. /// - /// List of node. - /// list of unity collider. - /// - /// VgoExporter.CreateUnityColliderList is same logic. - /// - protected virtual List CreateUnityColliderList(in List nodes) + /// The vgo stream. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(Stream vgoStream, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(Stream vgoStream, CancellationToken cancellationToken) +#else + public virtual async Task LoadAsync(Stream vgoStream, CancellationToken cancellationToken) +#endif { - var colliderList = new List(); + var vgoStorage = new VgoStorage(); - for (int index = 0; index < nodes.Count; index++) - { - GameObject node = nodes[index].gameObject; - - if (node.TryGetComponentEx(out Collider collider)) - { - if ((collider is BoxCollider) || - (collider is CapsuleCollider) || - (collider is SphereCollider)) - { - colliderList.Add(collider); - } - } - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoStream, vgkStream: null, cancellationToken); - return colliderList; +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } - #endregion - - #region Renderer - /// - /// Attach mesh and renderer to node. + /// Load a 3D model from the specified stream. /// - /// List of node. - /// The index of layout.node. - /// A vgo storage. - /// A vgo model asset. - /// Whether show mesh renderer. - /// Whether update skinned mesh renderer when off screen. - protected virtual void AttachMeshAndRenderer( - List nodes, - in int nodeIndex, - in IVgoStorage vgoStorage, - VgoModelAsset vgoModelAsset, -#if UNITY_2021_2_OR_NEWER - in bool showMesh = true, - in bool updateWhenOffscreen = false + /// The vgo stream. + /// The vgk stream. + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(Stream vgoStream, Stream vgkStream, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(Stream vgoStream, Stream vgkStream, CancellationToken cancellationToken) #else - bool showMesh = true, - bool updateWhenOffscreen = false + public virtual async Task LoadAsync(Stream vgoStream, Stream vgkStream, CancellationToken cancellationToken) #endif - ) { - if (vgoStorage.Layout.nodes is null) - { - return; - } - - if (vgoModelAsset.MeshAssetList is null) - { - return; - } - - VgoNode? vgoNode = vgoStorage.Layout.nodes[nodeIndex]; - - if (vgoNode is null) - { - return; - } - - GameObject go = nodes[nodeIndex].gameObject; - - Renderer renderer; - - if (vgoStorage.IsSpecVersion_2_4_orLower) - { - MeshAsset meshAsset = vgoModelAsset.MeshAssetList[vgoNode.mesh]; - - Mesh mesh = meshAsset.Mesh; - - Material?[]? materials = meshAsset.Materials; - - if ((meshAsset.Mesh.blendShapeCount == 0) && (vgoNode.skin == -1)) - { - // without blendshape and bone skinning - var filter = go.AddComponent(); - - filter.sharedMesh = mesh; - - var meshRenderer = go.AddComponent(); - - if (materials != null) - { - meshRenderer.sharedMaterials = materials; - } - - renderer = meshRenderer; - } - else - { - var skinnedMeshRenderer = go.AddComponent(); - - skinnedMeshRenderer.sharedMesh = mesh; - - if (vgoNode.skin >= 0) - { - SetupSkin(skinnedMeshRenderer, vgoNode.skin, nodes, vgoStorage); - } - - skinnedMeshRenderer.sharedMaterials = materials; - - if (updateWhenOffscreen) - { - skinnedMeshRenderer.updateWhenOffscreen = true; - } - - renderer = skinnedMeshRenderer; - } - - if ((meshAsset.BlendShapeConfig != null) && - (meshAsset.BlendShapeConfig.Kind != VgoBlendShapeKind.None)) - { - var vgoBlendShape = go.AddComponent(); - - BlendShapeConfiguration blendShapeConfiguration = ScriptableObject.CreateInstance(); - - blendShapeConfiguration.Name = meshAsset.BlendShapeConfig.Name ?? string.Empty; - blendShapeConfiguration.Kind = meshAsset.BlendShapeConfig.Kind; - blendShapeConfiguration.FaceParts = meshAsset.BlendShapeConfig.FaceParts; - blendShapeConfiguration.Blinks = meshAsset.BlendShapeConfig.Blinks; - blendShapeConfiguration.Visemes = meshAsset.BlendShapeConfig.Visemes; - blendShapeConfiguration.Presets = meshAsset.BlendShapeConfig.Presets; - - vgoBlendShape.BlendShapeConfiguration = blendShapeConfiguration; - - vgoModelAsset.ScriptableObjectList.Add(blendShapeConfiguration); - } - } - else - { - if (vgoNode.meshRenderer is null) - { - return; - } - - VgoMeshRenderer vgoMeshRenderer = vgoNode.meshRenderer; - - MeshAsset meshAsset = vgoModelAsset.MeshAssetList[vgoMeshRenderer.mesh]; - - Mesh mesh = meshAsset.Mesh; - - Material?[]? materials = null; - - if (vgoMeshRenderer.materials != null && - vgoMeshRenderer.materials.Any() && - vgoModelAsset.MaterialList != null && - vgoModelAsset.MaterialList.Any()) - { - var materialList = vgoModelAsset.MaterialList; - - materials = vgoMeshRenderer.materials - .Select(materialIndex => materialList[materialIndex]) - .ToArray(); - } - - if (vgoNode.particle >= 0) - { - if (go.TryGetComponentEx(out ParticleSystemRenderer particleSystemRenderer)) - { - if (particleSystemRenderer.renderMode == UnityEngine.ParticleSystemRenderMode.Mesh) - { - particleSystemRenderer.mesh = mesh; - - if (materials != null) - { - particleSystemRenderer.sharedMaterials = materials; - } - } - } - - return; - } - else if (vgoNode.skin >= 0) - { - var skinnedMeshRenderer = go.AddComponent(); - - //skinnedMeshRenderer.name = vgoMeshRenderer.name; - skinnedMeshRenderer.enabled = vgoMeshRenderer.enabled; - skinnedMeshRenderer.sharedMesh = mesh; - - SetupSkin(skinnedMeshRenderer, vgoNode.skin, nodes, vgoStorage); - - if (materials != null) - { - skinnedMeshRenderer.sharedMaterials = materials; - } - - if (updateWhenOffscreen) - { - skinnedMeshRenderer.updateWhenOffscreen = true; - } - - renderer = skinnedMeshRenderer; - } - else - { - // without blendshape and bone skinning - var filter = go.AddComponent(); - - filter.sharedMesh = mesh; - - var meshRenderer = go.AddComponent(); - - //meshRenderer.name = vgoMeshRenderer.name; - meshRenderer.enabled = vgoMeshRenderer.enabled; - - if (materials != null) - { - meshRenderer.sharedMaterials = materials; - } - - renderer = meshRenderer; - } - - if ((vgoMeshRenderer.blendShapeKind != null) && - (meshAsset.BlendShapeConfig != null)) - { - var vgoBlendShape = go.AddComponent(); - - BlendShapeConfiguration blendShapeConfiguration = ScriptableObject.CreateInstance(); - - blendShapeConfiguration.Name = meshAsset.BlendShapeConfig.Name ?? string.Empty; - - blendShapeConfiguration.Kind = vgoMeshRenderer.blendShapeKind.Value; - - blendShapeConfiguration.FaceParts = meshAsset.BlendShapeConfig.FaceParts; - blendShapeConfiguration.Blinks = meshAsset.BlendShapeConfig.Blinks; - blendShapeConfiguration.Visemes = meshAsset.BlendShapeConfig.Visemes; - - if (vgoMeshRenderer.blendShapePesets != null && - vgoMeshRenderer.blendShapePesets.Any()) - { - blendShapeConfiguration.Presets.AddRange(vgoMeshRenderer.blendShapePesets); - } - - vgoBlendShape.BlendShapeConfiguration = blendShapeConfiguration; + var vgoStorage = new VgoStorage(); - vgoModelAsset.ScriptableObjectList.Add(blendShapeConfiguration); - } - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoStream, vgkStream, cancellationToken); - if (showMesh == false) - { - renderer.enabled = false; - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await LoadInternalAsync(vgoStorage, cancellationToken); } - #endregion - - #region layout.skins - /// - /// Set up a skin. + /// Load a 3D model from the specified vgo storage. /// - /// A skinned mesh renderer. - /// The index of gltf.skin. - /// List of node. /// A vgo storage. - protected virtual void SetupSkin(SkinnedMeshRenderer skinnedMeshRenderer, in int skinIndex, in List nodes, in IVgoStorage vgoStorage) + /// The token to monitor for cancellation requests. + /// A vgo model asset. +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#else + public virtual async Task LoadAsync(IVgoStorage vgoStorage, CancellationToken cancellationToken) +#endif { - if (skinnedMeshRenderer.sharedMesh == null) - { - ThrowHelper.ThrowException(); - - return; - } - - if (vgoStorage.Layout.skins == null) - { - ThrowHelper.ThrowException(); - - return; - } - - if (vgoStorage.Layout.skins.TryGetValue(skinIndex, out var vgoSkin) == false) - { - ThrowHelper.ThrowIndexOutOfRangeException("skins[i]", skinIndex, min: 0, max: vgoStorage.Layout.skins.Count); - } - - if (vgoSkin is null) - { - return; - } - - Mesh mesh = skinnedMeshRenderer.sharedMesh; - - // calculate internal values(boundingBox etc...) when sharedMesh assigned ? - skinnedMeshRenderer.sharedMesh = null; - - if (vgoSkin.joints != null && vgoSkin.joints.Any()) - { - // have bones - - Transform?[] joints = new Transform?[vgoSkin.joints.Length]; - - for (int jointIndex = 0; jointIndex < vgoSkin.joints.Length; jointIndex++) - { - int vgoSkinJointNodeIndex = vgoSkin.joints[jointIndex]; - - if (vgoSkinJointNodeIndex.IsInRangeOf(nodes)) - { - joints[jointIndex] = nodes[vgoSkinJointNodeIndex]; - } - else - { - Debug.LogWarning($"skins[{skinIndex}].joints[{jointIndex}] is out of the range. 0 <= x < {nodes.Count}"); - - //ThrowHelper.ThrowIndexOutOfRangeException($"skins[{skinIndex}].joints[i]", jointIndex, min: 0, max: nodes.Count); - } - } - - skinnedMeshRenderer.bones = joints; - - if (vgoSkin.inverseBindMatrices >= 0) - { - if (vgoStorage.GeometryCoordinate == VgoGeometryCoordinate.RightHanded) - { - ReadOnlySpan matrixSpan = vgoStorage.GetAccessorSpan(vgoSkin.inverseBindMatrices); - - var bindPoses = new Matrix4x4[matrixSpan.Length]; - - for (int i = 0; i < matrixSpan.Length; i++) - { - bindPoses[i] = matrixSpan[i].ReverseZ(); - } - - mesh.bindposes = bindPoses; - } - else - { - mesh.bindposes = vgoStorage.GetAccessorArrayData(vgoSkin.inverseBindMatrices); - } - } - else - { - // calculate default matrices - // https://docs.unity3d.com/ScriptReference/Mesh-bindposes.html - - Transform meshCoords = skinnedMeshRenderer.transform; // ? - - Matrix4x4[] calculatedBindPoses = new Matrix4x4[joints.Length]; - - for (int index = 0; index < joints.Length; index++) - { - Transform? jointTransform = joints[index]; - - if (jointTransform == null) - { - continue; - } - - calculatedBindPoses[index] = jointTransform.worldToLocalMatrix * meshCoords.localToWorldMatrix; - } - - mesh.bindposes = calculatedBindPoses; - } - } - - skinnedMeshRenderer.sharedMesh = mesh; - - if (vgoSkin.skeleton.IsInRangeOf(nodes)) - { - skinnedMeshRenderer.rootBone = nodes[vgoSkin.skeleton]; - } + return await LoadInternalAsync(vgoStorage, cancellationToken); } #endregion +#endif // UNITY_WEBGL - #region layout.animationClips + #region Public Methods (Extract) /// - /// Create animation clip assets. + /// Extract a 3D model asset from the specified file. /// - /// A vgo layout. - /// - /// List of unity animation clip. - protected virtual List CreateAnimationClipAssets(in VgoLayout vgoLayout, in VgoGeometryCoordinate geometryCoordinate) + /// The file path of the vgo. + /// The file path of the vgk. + /// A vgo model asset. + /// for ScriptedImporter + public virtual VgoModelAsset Extract(in string vgoFilePath, in string? vgkFilePath = null) { - var animationClipList = new List(); - - if ((vgoLayout.animationClips == null) || (vgoLayout.animationClips.Any() == false)) - { - return animationClipList; - } - - for (int animationClipIndex = 0; animationClipIndex < vgoLayout.animationClips.Count; animationClipIndex++) - { - VgoAnimationClip? vgoAnimationClip = vgoLayout.animationClips[animationClipIndex]; + var vgoStorage = new VgoStorage(); - AnimationClip? animationClip = VgoAnimationClipConverter.CreateAnimationClipOrDefault(vgoAnimationClip, geometryCoordinate); + vgoStorage.ParseVgo(vgoFilePath, vgkFilePath); - animationClipList.Add(animationClip); - } - - return animationClipList; + return ExtractInternal(vgoStorage); } - #endregion - - #region Asset Info - /// - /// Setup a asset info to root GameObject. + /// Extract a 3D model asset from the specified file. /// - /// A vgo storage. - /// A vgo model asset. - protected virtual void SetupAssetInfo(in IVgoStorage vgoStorage, VgoModelAsset vgoModelAsset) + /// The file path of the vgo. + /// The file path of the vgk. + /// The token to monitor for cancellation requests. + /// A vgo model asset. + /// for ScriptedImporter +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + public virtual async Awaitable ExtractAsync(string vgoFilePath, string? vgkFilePath, CancellationToken cancellationToken) +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + public virtual async UniTask ExtractAsync(string vgoFilePath, string? vgkFilePath, CancellationToken cancellationToken) +#else + public virtual async Task ExtractAsync(string vgoFilePath, string? vgkFilePath, CancellationToken cancellationToken) +#endif { - if (vgoStorage.AssetInfo == null) - { - return; - } - - if (vgoModelAsset.Root == null) - { - return; - } - - VgoAssetInfo vgoAssetInfo = vgoStorage.AssetInfo; - - if (vgoAssetInfo.right != null) - { - VgoRight vgoRightComponent = vgoModelAsset.Root.AddComponent(); - - vgoRightComponent.Right = new NewtonVgo.VgoRight(vgoAssetInfo.right); - } + var vgoStorage = new VgoStorage(); - if (vgoAssetInfo.generator != null) - { - VgoGenerator vgoGeneratorComponent = vgoModelAsset.Root.AddComponent(); +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.BackgroundThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToThreadPool(); +#endif + await vgoStorage.ParseVgoAsync(vgoFilePath, vgkFilePath, cancellationToken); - vgoGeneratorComponent.GeneratorInfo = new VgoGeneratorInfo(vgoAssetInfo.generator); - } +#if UNITY_2023_1_OR_NEWER && UNIVGO_USE_UNITY_AWAITABLE + await Awaitable.MainThreadAsync(); +#elif CYSHARP_UNITASK_2_OR_NEWER && UNIVGO_USE_UNITASK + await UniTask.SwitchToMainThread(); +#endif + return await ExtractInternalAsync(vgoStorage, cancellationToken); } #endregion - #region Skybox + #region Public Methods (Helper) /// /// Reflect VGO skybox to Camera skybox. @@ -1288,62 +412,5 @@ public virtual void ReflectSkybox(Camera camera, VgoModelAsset vgoModelAsset) } #endregion - - #region Helpers - - /// - /// Create and add avatar configuration. - /// - /// The vgo human avatar. - protected virtual AvatarConfiguration CreateAvatarConfiguration(in VgoHumanAvatar vgoHumanAvatar) - { - var avatarConfiguration = ScriptableObject.CreateInstance(); - - avatarConfiguration.Name = vgoHumanAvatar.name ?? string.Empty; - avatarConfiguration.HumanBones = vgoHumanAvatar.humanBones - .Where(x => x != null) - .Select(x => x!) - .ToList(); - - return avatarConfiguration; - } - - /// - /// Get vgo node transforms. - /// - /// A vgo storage. - /// An array of matrix. - protected virtual System.Numerics.Matrix4x4[] GetVgoNodeTransforms(in IVgoStorage vgoStorage) - { - if (vgoStorage.ResourceAccessors.Count(x => x.kind == VgoResourceAccessorKind.NodeTransform) != 1) - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowException(); -#else - throw new Exception(); -#endif - } - - if (vgoStorage.Layout.nodes is null) - { -#if NET_STANDARD_2_1 - ThrowHelper.ThrowException(); -#else - throw new Exception(); -#endif - } - - VgoResourceAccessor accessor = vgoStorage.ResourceAccessors.First(x => x.kind == VgoResourceAccessorKind.NodeTransform); - - ArraySegment transformBytes = vgoStorage.GetAccessorBytes(accessor); - - var transforms = new System.Numerics.Matrix4x4[vgoStorage.Layout.nodes.Count]; - - transformBytes.MarshalCopyTo(transforms); - - return transforms; - } - - #endregion } } diff --git a/UniVgo2/Runtime/VgoVersion.cs b/UniVgo2/Runtime/VgoVersion.cs index babcc25..0b57954 100644 --- a/UniVgo2/Runtime/VgoVersion.cs +++ b/UniVgo2/Runtime/VgoVersion.cs @@ -17,9 +17,9 @@ public class VgoVersion public const int MINOR = 5; /// Patch - public const int PATCH = 16; + public const int PATCH = 17; /// Version - public const string VERSION = "2.5.16"; + public const string VERSION = "2.5.17"; } } diff --git a/package.json b/package.json index ef78427..e8b2461 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.izayoi.univgo", "displayName": "UniVGO", "description": "UniVGO is a package that can handle VGO files in Unity.", - "version": "2.5.16", + "version": "2.5.17", "type": "tool", "category": "", "keywords": [