396 lines
14 KiB
C#
396 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Unity.Collections;
|
|
|
|
namespace UniGLTF
|
|
{
|
|
/// <summary>
|
|
/// * JSON is parsed but not validated as glTF
|
|
/// * For glb, bin chunks are already available
|
|
/// </summary>
|
|
public sealed class GltfData : IDisposable
|
|
{
|
|
/// <summary>
|
|
/// Source file path.
|
|
/// Maybe empty if source file was on memory.
|
|
/// </summary>
|
|
public string TargetPath { get; }
|
|
|
|
/// <summary>
|
|
/// Chunk Data.
|
|
/// Maybe empty if source file was not glb format.
|
|
/// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#chunks
|
|
/// [0] must JSON
|
|
/// [1] must BIN
|
|
/// [2...] may exists.
|
|
/// </summary>
|
|
public IReadOnlyList<GlbChunk> Chunks { get; }
|
|
|
|
/// <summary>
|
|
/// JSON chunk ToString
|
|
/// > This chunk MUST be the very first chunk of Binary glTF asset
|
|
/// </summary>
|
|
public string Json { get; }
|
|
|
|
/// <summary>
|
|
/// GLTF parsed from JSON chunk
|
|
/// </summary>
|
|
public glTF GLTF { get; }
|
|
|
|
public NativeArrayManager NativeArrayManager { get; } = new NativeArrayManager();
|
|
|
|
/// <summary>
|
|
/// BIN chunk
|
|
/// > This chunk MUST be the second chunk of the Binary glTF asset
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public NativeArray<byte> Bin { get; }
|
|
|
|
/// <summary>
|
|
/// Migration Flags used by ImporterContext
|
|
/// </summary>
|
|
public MigrationFlags MigrationFlags { get; }
|
|
|
|
/// <summary>
|
|
/// URI access
|
|
/// </summary>
|
|
IStorage _storage;
|
|
|
|
/// <summary>
|
|
/// uri = data: base64デコード
|
|
/// uri = 相対パス。File.ReadAllBytes
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
Dictionary<string, NativeArray<byte>> _UriCache = new Dictionary<string, NativeArray<byte>>();
|
|
|
|
public GltfData(string targetPath, string json, glTF gltf, IReadOnlyList<GlbChunk> chunks, IStorage storage, MigrationFlags migrationFlags)
|
|
{
|
|
TargetPath = targetPath;
|
|
Json = json;
|
|
GLTF = gltf;
|
|
Chunks = chunks;
|
|
_storage = storage;
|
|
MigrationFlags = migrationFlags;
|
|
|
|
// init
|
|
if (Chunks != null)
|
|
{
|
|
if (Chunks.Count >= 2)
|
|
{
|
|
Bin = NativeArrayManager.CreateNativeArray(Chunks[1].Bytes);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
NativeArrayManager.Dispose();
|
|
_UriCache.Clear();
|
|
}
|
|
|
|
public static GltfData CreateFromExportForTest(ExportingGltfData data)
|
|
{
|
|
return CreateFromGltfDataForTest(data.Gltf, data.BinBytes);
|
|
}
|
|
|
|
public static GltfData CreateFromGltfDataForTest(glTF gltf, ArraySegment<byte> bytes)
|
|
{
|
|
return new GltfData(
|
|
string.Empty,
|
|
string.Empty,
|
|
gltf,
|
|
new List<GlbChunk>
|
|
{
|
|
new GlbChunk(), // json
|
|
GlbChunk.CreateBin(bytes),
|
|
},
|
|
default,
|
|
new MigrationFlags()
|
|
);
|
|
}
|
|
|
|
NativeArray<Byte> GetBytesFromUri(string uri)
|
|
{
|
|
if (string.IsNullOrEmpty(uri))
|
|
{
|
|
throw new ArgumentNullException();
|
|
}
|
|
|
|
if (_UriCache.TryGetValue(uri, out NativeArray<byte> data))
|
|
{
|
|
// return cache
|
|
return data;
|
|
}
|
|
|
|
if (uri.StartsWith("data:", StringComparison.Ordinal))
|
|
{
|
|
data = NativeArrayManager.CreateNativeArray(UriByteBuffer.ReadEmbedded(uri));
|
|
}
|
|
else
|
|
{
|
|
data = NativeArrayManager.CreateNativeArray(_storage.Get(uri));
|
|
}
|
|
_UriCache.Add(uri, data);
|
|
return data;
|
|
}
|
|
|
|
public NativeArray<Byte> GetBytesFromBuffer(int bufferIndex)
|
|
{
|
|
var buffer = GLTF.buffers[bufferIndex];
|
|
if (bufferIndex == 0 && Bin.IsCreated)
|
|
{
|
|
// https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#glb-stored-buffer
|
|
return Bin;
|
|
}
|
|
else
|
|
{
|
|
return GetBytesFromUri(buffer.uri);
|
|
}
|
|
}
|
|
|
|
public NativeArray<Byte> GetBytesFromBufferView(int bufferView)
|
|
{
|
|
var view = GLTF.bufferViews[bufferView];
|
|
var segment = GetBytesFromBuffer(view.buffer);
|
|
return segment.GetSubArray(view.byteOffset, view.byteLength);
|
|
}
|
|
|
|
NativeArray<byte> GetBytesFromBufferView(glTFBufferView view)
|
|
{
|
|
var segment = GetBytesFromBuffer(view.buffer);
|
|
return segment.GetSubArray(view.byteOffset, view.byteLength);
|
|
}
|
|
|
|
NativeArray<T> GetTypedFromAccessor<T>(glTFAccessor accessor, glTFBufferView view) where T : struct
|
|
{
|
|
var bytes = GetBytesFromBufferView(view);
|
|
return bytes.GetSubArray(accessor.byteOffset, bytes.Length - accessor.byteOffset).Reinterpret<T>(1).GetSubArray(0, accessor.count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// for sparse
|
|
/// </summary>
|
|
/// <param name="view"></param>
|
|
/// <param name="count"></param>
|
|
/// <param name="byteOffset"></param>
|
|
/// <param name="componentType"></param>
|
|
/// <returns></returns>
|
|
BufferAccessor GetIntIndicesFromView(glTFBufferView view, int count, int byteOffset, glComponentType componentType)
|
|
{
|
|
var bytes = GetBytesFromBufferView(view);
|
|
switch (componentType)
|
|
{
|
|
case glComponentType.UNSIGNED_BYTE:
|
|
{
|
|
return new BufferAccessor(NativeArrayManager, bytes.GetSubArray(byteOffset, bytes.Length - byteOffset),
|
|
AccessorValueType.UNSIGNED_BYTE, AccessorVectorType.SCALAR, count);
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_SHORT:
|
|
{
|
|
return new BufferAccessor(NativeArrayManager, bytes.GetSubArray(byteOffset, bytes.Length - byteOffset),
|
|
AccessorValueType.UNSIGNED_SHORT, AccessorVectorType.SCALAR, count);
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_INT:
|
|
{
|
|
return new BufferAccessor(NativeArrayManager, bytes.GetSubArray(byteOffset, bytes.Length - byteOffset),
|
|
AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, count);
|
|
}
|
|
|
|
default:
|
|
throw new NotImplementedException("GetIndices: unknown componenttype: " + componentType);
|
|
}
|
|
}
|
|
|
|
public BufferAccessor GetIndicesFromAccessorIndex(int accessorIndex)
|
|
{
|
|
var accessor = GLTF.accessors[accessorIndex];
|
|
var view = GLTF.bufferViews[accessor.bufferView];
|
|
return GetIntIndicesFromView(view, accessor.count, accessor.byteOffset, accessor.componentType);
|
|
}
|
|
|
|
public NativeArray<T> GetArrayFromAccessor<T>(int accessorIndex) where T : struct
|
|
{
|
|
var vertexAccessor = GLTF.accessors[accessorIndex];
|
|
|
|
if (vertexAccessor.count <= 0) return NativeArrayManager.CreateNativeArray<T>(0);
|
|
|
|
var result = (vertexAccessor.bufferView != -1)
|
|
? GetTypedFromAccessor<T>(vertexAccessor, GLTF.bufferViews[vertexAccessor.bufferView])
|
|
: NativeArrayManager.CreateNativeArray<T>(vertexAccessor.count)
|
|
;
|
|
|
|
var sparse = vertexAccessor.sparse;
|
|
if (sparse != null && sparse.count > 0)
|
|
{
|
|
// override sparse values
|
|
var _indices = GetIntIndicesFromView(GLTF.bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType);
|
|
var bytes = GetBytesFromBufferView(GLTF.bufferViews[sparse.values.bufferView]);
|
|
var values = bytes.GetSubArray(sparse.values.byteOffset, bytes.Length - sparse.values.byteOffset).Reinterpret<T>(1).GetSubArray(0, sparse.count);
|
|
|
|
switch (_indices.ComponentType)
|
|
{
|
|
case AccessorValueType.UNSIGNED_BYTE:
|
|
{
|
|
var indices = _indices.Bytes;
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
case AccessorValueType.UNSIGNED_SHORT:
|
|
{
|
|
var indices = _indices.Bytes.Reinterpret<ushort>(1);
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
case AccessorValueType.UNSIGNED_INT:
|
|
{
|
|
var indices = _indices.Bytes.Reinterpret<int>(1);
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public NativeArray<float> FlatternFloatArrayFromAccessor(int accessorIndex)
|
|
{
|
|
var vertexAccessor = GLTF.accessors[accessorIndex];
|
|
|
|
if (vertexAccessor.count <= 0) return NativeArrayManager.CreateNativeArray<float>(0);
|
|
|
|
var bufferCount = vertexAccessor.count * vertexAccessor.TypeCount;
|
|
|
|
NativeArray<float> result = default;
|
|
if (vertexAccessor.bufferView != -1)
|
|
{
|
|
var view = GLTF.bufferViews[vertexAccessor.bufferView];
|
|
var segment = GetBytesFromBuffer(view.buffer);
|
|
result = segment.GetSubArray(view.byteOffset + vertexAccessor.byteOffset, vertexAccessor.count * 4 * vertexAccessor.TypeCount).Reinterpret<float>(1);
|
|
}
|
|
else
|
|
{
|
|
result = NativeArrayManager.CreateNativeArray<float>(bufferCount);
|
|
}
|
|
|
|
var sparse = vertexAccessor.sparse;
|
|
if (sparse != null && sparse.count > 0)
|
|
{
|
|
// override sparse values
|
|
var _indices = GetIntIndicesFromView(GLTF.bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType);
|
|
var bytes = GetBytesFromBufferView(GLTF.bufferViews[sparse.values.bufferView]);
|
|
var values = bytes.GetSubArray(sparse.values.byteOffset, bytes.Length - sparse.values.byteOffset).Reinterpret<float>(1).GetSubArray(0, sparse.count * vertexAccessor.TypeCount);
|
|
|
|
switch (_indices.ComponentType)
|
|
{
|
|
case AccessorValueType.UNSIGNED_BYTE:
|
|
{
|
|
var indices = _indices.Bytes;
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
case AccessorValueType.UNSIGNED_SHORT:
|
|
{
|
|
var indices = _indices.Bytes.Reinterpret<ushort>(1);
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
case AccessorValueType.UNSIGNED_INT:
|
|
{
|
|
var indices = _indices.Bytes.Reinterpret<int>(1);
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
result[indices[i]] = values[i];
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public (NativeArray<byte> binary, string mimeType)? GetBytesFromImage(int imageIndex)
|
|
{
|
|
if (imageIndex < 0 || imageIndex >= GLTF.images.Count) return default;
|
|
|
|
var image = GLTF.images[imageIndex];
|
|
if (string.IsNullOrEmpty(image.uri))
|
|
{
|
|
return (GetBytesFromBufferView(image.bufferView), image.mimeType);
|
|
}
|
|
else
|
|
{
|
|
return (GetBytesFromUri(image.uri), image.mimeType);
|
|
}
|
|
}
|
|
|
|
// not black(0, 0, 0, 1)
|
|
static readonly UnityEngine.Color ZERO = new UnityEngine.Color(0, 0, 0, 0);
|
|
|
|
public bool HasVertexColor(glTFAttributes attributes)
|
|
{
|
|
if (attributes.COLOR_0 == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var colors = GetArrayFromAccessor<UnityEngine.Color>(attributes.COLOR_0);
|
|
foreach (var color in colors)
|
|
{
|
|
if (color != ZERO)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
// すべて (0, 0, 0, 0) だった。使っていないと見做す。
|
|
return false;
|
|
}
|
|
|
|
public bool MaterialHasVertexColor(int materialIndex)
|
|
{
|
|
if (materialIndex < 0 || materialIndex >= GLTF.materials.Count)
|
|
{
|
|
// index out of range. material not exists
|
|
return false;
|
|
}
|
|
|
|
foreach (var mesh in GLTF.meshes)
|
|
{
|
|
foreach (var prim in mesh.primitives)
|
|
{
|
|
if (prim.material == materialIndex && HasVertexColor(prim.attributes))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
}
|