mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-11 13:04:17 -05:00
523 lines
17 KiB
C#
523 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UniJSON;
|
|
|
|
|
|
namespace UniGLTF
|
|
{
|
|
[Serializable]
|
|
public class gltfScene : JsonSerializableBase
|
|
{
|
|
[JsonSchema(MinItems = 1)]
|
|
[ItemJsonSchema(Minimum = 0)]
|
|
public int[] nodes;
|
|
|
|
public object extensions;
|
|
public object extras;
|
|
public string name;
|
|
|
|
protected override void SerializeMembers(GLTFJsonFormatter f)
|
|
{
|
|
f.KeyValue(() => nodes);
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public class glTF : JsonSerializableBase, IEquatable<glTF>
|
|
{
|
|
[JsonSchema(Required = true)]
|
|
public glTFAssets asset = new glTFAssets();
|
|
|
|
#region Buffer
|
|
[JsonSchema(MinItems = 1)]
|
|
public List<glTFBuffer> buffers = new List<glTFBuffer>();
|
|
public int AddBuffer(IBytesBuffer bytesBuffer)
|
|
{
|
|
var index = buffers.Count;
|
|
buffers.Add(new glTFBuffer(bytesBuffer));
|
|
return index;
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1)]
|
|
public List<glTFBufferView> bufferViews = new List<glTFBufferView>();
|
|
public int AddBufferView(glTFBufferView view)
|
|
{
|
|
var index = bufferViews.Count;
|
|
bufferViews.Add(view);
|
|
return index;
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1)]
|
|
public List<glTFAccessor> accessors = new List<glTFAccessor>();
|
|
|
|
T[] GetAttrib<T>(glTFAccessor accessor, glTFBufferView view) where T : struct
|
|
{
|
|
return GetAttrib<T>(accessor.count, accessor.byteOffset, view);
|
|
}
|
|
T[] GetAttrib<T>(int count, int byteOffset, glTFBufferView view) where T : struct
|
|
{
|
|
var attrib = new T[count];
|
|
//
|
|
var segment = buffers[view.buffer].GetBytes();
|
|
var bytes = new ArraySegment<Byte>(segment.Array, segment.Offset + view.byteOffset + byteOffset, count * view.byteStride);
|
|
bytes.MarshalCoyTo(attrib);
|
|
return attrib;
|
|
}
|
|
|
|
public ArraySegment<Byte> GetViewBytes(int bufferView)
|
|
{
|
|
var view = bufferViews[bufferView];
|
|
var segment = buffers[view.buffer].GetBytes();
|
|
return new ArraySegment<byte>(segment.Array, segment.Offset + view.byteOffset, view.byteLength);
|
|
}
|
|
|
|
IEnumerable<int> _GetIndices(glTFAccessor accessor, out int count)
|
|
{
|
|
count = accessor.count;
|
|
var view = bufferViews[accessor.bufferView];
|
|
switch ((glComponentType)accessor.componentType)
|
|
{
|
|
case glComponentType.UNSIGNED_BYTE:
|
|
{
|
|
return GetAttrib<Byte>(accessor, view).Select(x => (int)(x));
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_SHORT:
|
|
{
|
|
return GetAttrib<UInt16>(accessor, view).Select(x => (int)(x));
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_INT:
|
|
{
|
|
return GetAttrib<UInt32>(accessor, view).Select(x => (int)(x));
|
|
}
|
|
}
|
|
throw new NotImplementedException("GetIndices: unknown componenttype: " + accessor.componentType);
|
|
}
|
|
|
|
IEnumerable<int> _GetIndices(glTFBufferView view, int count, int byteOffset, glComponentType componentType)
|
|
{
|
|
switch (componentType)
|
|
{
|
|
case glComponentType.UNSIGNED_BYTE:
|
|
{
|
|
return GetAttrib<Byte>(count, byteOffset, view).Select(x => (int)(x));
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_SHORT:
|
|
{
|
|
return GetAttrib<UInt16>(count, byteOffset, view).Select(x => (int)(x));
|
|
}
|
|
|
|
case glComponentType.UNSIGNED_INT:
|
|
{
|
|
return GetAttrib<UInt32>(count, byteOffset, view).Select(x => (int)(x));
|
|
}
|
|
}
|
|
throw new NotImplementedException("GetIndices: unknown componenttype: " + componentType);
|
|
}
|
|
|
|
public int[] GetIndices(int accessorIndex)
|
|
{
|
|
int count;
|
|
var result = _GetIndices(accessors[accessorIndex], out count);
|
|
var indices = new int[count];
|
|
|
|
// flip triangles
|
|
var it = result.GetEnumerator();
|
|
{
|
|
for (int i = 0; i < count; i += 3)
|
|
{
|
|
it.MoveNext(); indices[i + 2] = it.Current;
|
|
it.MoveNext(); indices[i + 1] = it.Current;
|
|
it.MoveNext(); indices[i] = it.Current;
|
|
}
|
|
}
|
|
|
|
return indices;
|
|
}
|
|
|
|
public T[] GetArrayFromAccessor<T>(int accessorIndex) where T : struct
|
|
{
|
|
var vertexAccessor = accessors[accessorIndex];
|
|
|
|
if (vertexAccessor.count <= 0) return new T[] { };
|
|
|
|
var result = (vertexAccessor.bufferView != -1)
|
|
? GetAttrib<T>(vertexAccessor, bufferViews[vertexAccessor.bufferView])
|
|
: new T[vertexAccessor.count]
|
|
;
|
|
|
|
var sparse = vertexAccessor.sparse;
|
|
if (sparse != null && sparse.count > 0)
|
|
{
|
|
// override sparse values
|
|
var indices = _GetIndices(bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType);
|
|
var values = GetAttrib<T>(sparse.count, sparse.values.byteOffset, bufferViews[sparse.values.bufferView]);
|
|
|
|
var it = indices.GetEnumerator();
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
it.MoveNext();
|
|
result[it.Current] = values[i];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public float[] GetArrayFromAccessorAsFloat(int accessorIndex)
|
|
{
|
|
var vertexAccessor = accessors[accessorIndex];
|
|
|
|
if (vertexAccessor.count <= 0) return new float[] { };
|
|
|
|
var bufferCount = vertexAccessor.count * vertexAccessor.TypeCount;
|
|
var result = (vertexAccessor.bufferView != -1)
|
|
? GetAttrib<float>(bufferCount, vertexAccessor.byteOffset, bufferViews[vertexAccessor.bufferView])
|
|
: new float[bufferCount]
|
|
;
|
|
|
|
var sparse = vertexAccessor.sparse;
|
|
if (sparse != null && sparse.count > 0)
|
|
{
|
|
// override sparse values
|
|
var indices = _GetIndices(bufferViews[sparse.indices.bufferView], sparse.count, sparse.indices.byteOffset, sparse.indices.componentType);
|
|
var values = GetAttrib<float>(sparse.count * vertexAccessor.TypeCount, sparse.values.byteOffset, bufferViews[sparse.values.bufferView]);
|
|
|
|
var it = indices.GetEnumerator();
|
|
for (int i = 0; i < sparse.count; ++i)
|
|
{
|
|
it.MoveNext();
|
|
result[it.Current] = values[i];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
#endregion
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFTexture> textures = new List<glTFTexture>();
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFTextureSampler> samplers = new List<glTFTextureSampler>();
|
|
public glTFTextureSampler GetSampler(int index)
|
|
{
|
|
if (samplers.Count == 0)
|
|
{
|
|
samplers.Add(new glTFTextureSampler()); // default sampler
|
|
}
|
|
|
|
return samplers[index];
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1)]
|
|
public List<glTFImage> images = new List<glTFImage>();
|
|
|
|
public int GetImageIndexFromTextureIndex(int textureIndex)
|
|
{
|
|
return textures[textureIndex].source;
|
|
}
|
|
|
|
public glTFImage GetImageFromTextureIndex(int textureIndex)
|
|
{
|
|
return images[GetImageIndexFromTextureIndex(textureIndex)];
|
|
}
|
|
|
|
public glTFTextureSampler GetSamplerFromTextureIndex(int textureIndex)
|
|
{
|
|
var samplerIndex = textures[textureIndex].sampler;
|
|
return GetSampler(samplerIndex);
|
|
}
|
|
|
|
public ArraySegment<Byte> GetImageBytes(IStorage storage, int imageIndex, out string textureName)
|
|
{
|
|
var image = images[imageIndex];
|
|
if (string.IsNullOrEmpty(image.uri))
|
|
{
|
|
//
|
|
// use buffer view (GLB)
|
|
//
|
|
//m_imageBytes = ToArray(byteSegment);
|
|
textureName = !string.IsNullOrEmpty(image.name) ? image.name : string.Format("{0:00}#GLB", imageIndex);
|
|
return GetViewBytes(image.bufferView);
|
|
}
|
|
else
|
|
{
|
|
if (image.uri.StartsWith("data:"))
|
|
{
|
|
textureName = !string.IsNullOrEmpty(image.name) ? image.name : string.Format("{0:00}#Base64Embeded", imageIndex);
|
|
}
|
|
else
|
|
{
|
|
textureName = !string.IsNullOrEmpty(image.name) ? image.name : Path.GetFileNameWithoutExtension(image.uri);
|
|
}
|
|
return storage.Get(image.uri);
|
|
}
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFMaterial> materials = new List<glTFMaterial>();
|
|
public string GetUniqueMaterialName(int index)
|
|
{
|
|
if (materials.Any(x => string.IsNullOrEmpty(x.name))
|
|
|| materials.Select(x => x.name).Distinct().Count() != materials.Count)
|
|
{
|
|
return String.Format("{0:00}_{1}", index, materials[index].name);
|
|
}
|
|
else
|
|
{
|
|
return materials[index].name;
|
|
}
|
|
}
|
|
|
|
public bool MaterialHasVertexColor(glTFMaterial material)
|
|
{
|
|
if (material == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var materialIndex = materials.IndexOf(material);
|
|
if (materialIndex == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return MaterialHasVertexColor(materialIndex);
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFMesh> meshes = new List<glTFMesh>();
|
|
|
|
public bool MaterialHasVertexColor(int materialIndex)
|
|
{
|
|
if (materialIndex < 0 || materialIndex >= materials.Count)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var hasVertexColor = meshes.SelectMany(x => x.primitives).Any(x => x.material == materialIndex && x.HasVertexColor);
|
|
return hasVertexColor;
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFNode> nodes = new List<glTFNode>();
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFSkin> skins = new List<glTFSkin>();
|
|
|
|
[JsonSchema(Dependencies = new string[] { "scenes" }, Minimum = 0)]
|
|
public int scene;
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<gltfScene> scenes = new List<gltfScene>();
|
|
public int[] rootnodes
|
|
{
|
|
get
|
|
{
|
|
return scenes[scene].nodes;
|
|
}
|
|
}
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFAnimation> animations = new List<glTFAnimation>();
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<glTFCamera> cameras = new List<glTFCamera>();
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<string> extensionsUsed = new List<string>();
|
|
|
|
[JsonSchema(MinItems = 1, ExplicitIgnorableItemLength = 0)]
|
|
public List<string> extensionsRequired = new List<string>();
|
|
|
|
public glTF_extensions extensions = new glTF_extensions();
|
|
public gltf_extras extras = new gltf_extras();
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Format("{0}", asset);
|
|
}
|
|
|
|
protected override void SerializeMembers(GLTFJsonFormatter f)
|
|
{
|
|
if (extensionsUsed.Count > 0)
|
|
{
|
|
f.Key("extensionsUsed"); f.GLTFValue(extensionsUsed);
|
|
}
|
|
if (extensions.__count > 0)
|
|
{
|
|
f.Key("extensions"); f.GLTFValue(extensions);
|
|
}
|
|
if (extras.__count > 0)
|
|
{
|
|
f.Key("extras"); f.GLTFValue(extras);
|
|
}
|
|
|
|
f.Key("asset"); f.GLTFValue(asset);
|
|
|
|
// buffer
|
|
if (buffers.Any())
|
|
{
|
|
f.Key("buffers"); f.GLTFValue(buffers);
|
|
}
|
|
if (bufferViews.Any())
|
|
{
|
|
f.Key("bufferViews"); f.GLTFValue(bufferViews);
|
|
}
|
|
if (accessors.Any())
|
|
{
|
|
f.Key("accessors"); f.GLTFValue(accessors);
|
|
}
|
|
|
|
// materials
|
|
if (images.Any())
|
|
{
|
|
f.Key("images"); f.GLTFValue(images);
|
|
if (samplers.Count == 0)
|
|
{
|
|
samplers.Add(new glTFTextureSampler());
|
|
}
|
|
}
|
|
|
|
if (samplers.Any())
|
|
{
|
|
f.Key("samplers"); f.GLTFValue(samplers);
|
|
}
|
|
|
|
if (textures.Any())
|
|
{
|
|
f.Key("textures"); f.GLTFValue(textures);
|
|
}
|
|
if (materials.Any())
|
|
{
|
|
f.Key("materials"); f.GLTFValue(materials);
|
|
}
|
|
|
|
// meshes
|
|
if (meshes.Any())
|
|
{
|
|
f.Key("meshes"); f.GLTFValue(meshes);
|
|
}
|
|
if (skins.Any())
|
|
{
|
|
f.Key("skins"); f.GLTFValue(skins);
|
|
}
|
|
|
|
// scene
|
|
if (nodes.Any())
|
|
{
|
|
f.Key("nodes"); f.GLTFValue(nodes);
|
|
}
|
|
if (scenes.Any())
|
|
{
|
|
f.Key("scenes"); f.GLTFValue(scenes);
|
|
if (scene >= 0)
|
|
{
|
|
f.KeyValue(() => scene);
|
|
}
|
|
}
|
|
|
|
// animations
|
|
if (animations.Any())
|
|
{
|
|
f.Key("animations"); f.GLTFValue(animations);
|
|
}
|
|
}
|
|
|
|
public bool Equals(glTF other)
|
|
{
|
|
return
|
|
textures.SequenceEqual(other.textures)
|
|
&& samplers.SequenceEqual(other.samplers)
|
|
&& images.SequenceEqual(other.images)
|
|
&& materials.SequenceEqual(other.materials)
|
|
&& meshes.SequenceEqual(other.meshes)
|
|
&& nodes.SequenceEqual(other.nodes)
|
|
&& skins.SequenceEqual(other.skins)
|
|
&& scene == other.scene
|
|
&& scenes.SequenceEqual(other.scenes)
|
|
&& animations.SequenceEqual(other.animations)
|
|
;
|
|
}
|
|
|
|
bool UsedExtension(string key)
|
|
{
|
|
if (extensionsUsed.Contains(key))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static Utf8String s_extensions = Utf8String.From("extensions");
|
|
|
|
void Traverse(ListTreeNode<JsonValue> node, JsonFormatter f, Utf8String parentKey)
|
|
{
|
|
if(node.IsMap())
|
|
{
|
|
f.BeginMap();
|
|
foreach(var kv in node.ObjectItems())
|
|
{
|
|
if (parentKey == s_extensions)
|
|
{
|
|
if (!UsedExtension(kv.Key.GetString()))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
f.Key(kv.Key.GetUtf8String());
|
|
Traverse(kv.Value, f, kv.Key.GetUtf8String());
|
|
}
|
|
f.EndMap();
|
|
}
|
|
else if(node.IsArray())
|
|
{
|
|
f.BeginList();
|
|
foreach(var x in node.ArrayItems())
|
|
{
|
|
Traverse(x, f, default(Utf8String));
|
|
}
|
|
f.EndList();
|
|
}
|
|
else
|
|
{
|
|
f.Value(node);
|
|
}
|
|
}
|
|
|
|
string RemoveUnusedExtensions(string json)
|
|
{
|
|
var f = new JsonFormatter();
|
|
|
|
Traverse(JsonParser.Parse(json), f, default(Utf8String));
|
|
|
|
return f.ToString();
|
|
}
|
|
|
|
public byte[] ToGlbBytes(bool UseUniJSONSerializer = false)
|
|
{
|
|
string json;
|
|
if (UseUniJSONSerializer)
|
|
{
|
|
var c = new JsonSchemaValidationContext(this)
|
|
{
|
|
EnableDiagnosisForNotRequiredFields = true,
|
|
};
|
|
json = JsonSchema.FromType(GetType()).Serialize(this, c);
|
|
}
|
|
else
|
|
{
|
|
json = ToJson();
|
|
}
|
|
|
|
RemoveUnusedExtensions(json);
|
|
|
|
return Glb.ToBytes(json, buffers[0].GetBytes());
|
|
}
|
|
}
|
|
}
|