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;
}
}
}