using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using UniGLTF;
using VrmLib;
namespace UniVRM10
{
public static class MeshAdapter
{
///
/// VertexBufferはひとつでIndexBufferの参照が異なる
///
/// VertexBuffer
/// +----------------------------------+
/// | |
/// +----------------------------------+
/// A A A
/// | | |
/// +---------+--------+--------+
/// | submesh0|submesh1|submesh2|
/// +---------+--------+--------+
/// IndexBuffer
///
public static Mesh SharedBufferFromGltf(this glTFMesh x, Vrm10Storage storage)
{
// 先頭を使う
return FromGltf(storage, x, x.primitives[0], true);
}
///
/// IndexBuffer毎に異なるVertexBufferを参照する
///
/// VertexBuffer
/// +--------+ +--------+ +--------+
/// |0 | |1 | |2 |
/// +--------+ +--------+ +--------+
/// A A A
/// | | |
/// +---------+--------+--------+
/// | submesh0|submesh1|submesh2|
/// +---------+--------+--------+
/// IndexBuffer
///
public static Mesh FromGltf(this glTFPrimitives primitive, Vrm10Storage storage, glTFMesh x)
{
return FromGltf(storage, x, primitive, false);
}
static Mesh FromGltf(Vrm10Storage storage, glTFMesh x, glTFPrimitives primitive, bool isShared)
{
var mesh = new Mesh((TopologyType)primitive.mode)
{
VertexBuffer = primitive.attributes.FromGltf(storage)
};
if (isShared)
{
// create joined index buffer
mesh.IndexBuffer = storage.CreateAccessor(x.primitives.Select(y => y.indices).ToArray());
}
else
{
mesh.IndexBuffer = storage.CreateAccessor(primitive.indices);
}
{
gltf_mesh_extras_targetNames.TryGet(x, out List targetNames);
for (int i = 0; i < primitive.targets.Count; ++i)
{
var gltfTarget = primitive.targets[i];
string targetName = null;
{
targetName = targetNames[i];
}
var target = new MorphTarget(targetName)
{
VertexBuffer = gltfTarget.FromGltf(storage)
};
// validate count
foreach (var kv in target.VertexBuffer)
{
if (kv.Value.Count != mesh.VertexBuffer.Count)
{
throw new Exception();
}
}
mesh.MorphTargets.Add(target);
}
}
return mesh;
}
public static VertexBuffer FromGltf(this glTFAttributes attributes,
Vrm10Storage storage)
{
var b = new VertexBuffer();
if (storage.TryCreateAccessor(attributes.POSITION, out BufferAccessor position))
{
b.Add(VertexBuffer.PositionKey, position);
}
else
{
// position required
throw new Exception();
}
if (storage.TryCreateAccessor(attributes.NORMAL, out BufferAccessor normal)) b.Add(VertexBuffer.NormalKey, normal);
if (storage.TryCreateAccessor(attributes.COLOR_0, out BufferAccessor color)) b.Add(VertexBuffer.ColorKey, color);
if (storage.TryCreateAccessor(attributes.TEXCOORD_0, out BufferAccessor tex0)) b.Add(VertexBuffer.TexCoordKey, tex0);
if (storage.TryCreateAccessor(attributes.TEXCOORD_1, out BufferAccessor tex1)) b.Add(VertexBuffer.TexCoordKey2, tex1);
// if(storage.TryCreateAccessor(attributes.TANGENT, out BufferAccessor tangent))b.Add(VertexBuffer.TangentKey, tangent);
if (storage.TryCreateAccessor(attributes.WEIGHTS_0, out BufferAccessor weights)) b.Add(VertexBuffer.WeightKey, weights);
if (storage.TryCreateAccessor(attributes.JOINTS_0, out BufferAccessor joints)) b.Add(VertexBuffer.JointKey, joints);
return b;
}
public static VertexBuffer FromGltf(this gltfMorphTarget target, Vrm10Storage storage)
{
var b = new VertexBuffer();
storage.CreateBufferAccessorAndAdd(target.POSITION, b, VertexBuffer.PositionKey);
storage.CreateBufferAccessorAndAdd(target.NORMAL, b, VertexBuffer.NormalKey);
storage.CreateBufferAccessorAndAdd(target.TANGENT, b, VertexBuffer.TangentKey);
return b;
}
public static bool HasSameVertexBuffer(this glTFPrimitives lhs, glTFPrimitives rhs)
{
if (lhs.attributes.POSITION != rhs.attributes.POSITION) return false;
if (lhs.attributes.NORMAL != rhs.attributes.NORMAL) return false;
if (lhs.attributes.TEXCOORD_0 != rhs.attributes.TEXCOORD_0) return false;
if (lhs.attributes.TEXCOORD_1 != rhs.attributes.TEXCOORD_1) return false;
if (lhs.attributes.COLOR_0 != rhs.attributes.COLOR_0) return false;
if (lhs.attributes.WEIGHTS_0 != rhs.attributes.WEIGHTS_0) return false;
if (lhs.attributes.JOINTS_0 != rhs.attributes.JOINTS_0) return false;
return true;
}
public static bool AllPrimitivesHasSameVertexBuffer(this glTFMesh m)
{
if (m.primitives.Count <= 1)
{
return true;
}
var first = m.primitives[0];
for (int i = 1; i < m.primitives.Count; ++i)
{
if (!first.HasSameVertexBuffer(m.primitives[i]))
{
return false;
}
}
return true;
}
public static MeshGroup FromGltf(this glTFMesh x,
Vrm10Storage storage, List materials)
{
var group = new MeshGroup(x.name);
if (x.primitives.Count == 1)
{
var primitive = x.primitives[0];
var mesh = primitive.FromGltf(storage, x);
var materialIndex = primitive.material;
mesh.Submeshes.Add(
new Submesh(0, mesh.IndexBuffer.Count, materials[materialIndex]));
group.Meshes.Add(mesh);
}
else if (!x.AllPrimitivesHasSameVertexBuffer())
{
int offset = 0;
foreach (var primitive in x.primitives)
{
var mesh = primitive.FromGltf(storage, x);
var materialIndex = primitive.material;
mesh.Submeshes.Add(
new Submesh(offset, mesh.IndexBuffer.Count, materials[materialIndex]));
offset += mesh.IndexBuffer.Count;
group.Meshes.Add(mesh);
}
}
else
{
// for VRM
var mesh = x.SharedBufferFromGltf(storage);
int offset = 0;
foreach (var primitive in x.primitives)
{
var materialIndex = primitive.material;
var count = storage.Gltf.accessors[primitive.indices].count;
mesh.Submeshes.Add(
new Submesh(offset, count, materials[materialIndex]));
offset += count;
}
group.Meshes.Add(mesh);
}
return group;
}
static void Vec3MinMax(ArraySegment bytes, glTFAccessor accessor)
{
var positions = SpanLike.Wrap(bytes);
var min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
var max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
foreach (var p in positions)
{
min = Vector3.Min(min, p);
max = Vector3.Max(max, p);
}
accessor.min = min.ToFloat3();
accessor.max = max.ToFloat3();
}
static int ExportIndices(Vrm10Storage storage, BufferAccessor x, int offset, int count, ExportArgs option)
{
if (x.Count <= ushort.MaxValue)
{
if (x.ComponentType == AccessorValueType.UNSIGNED_INT)
{
// ensure ushort
var src = x.GetSpan().Slice(offset, count);
var bytes = new byte[src.Length * 2];
var dst = SpanLike.Wrap(new ArraySegment(bytes));
for (int i = 0; i < src.Length; ++i)
{
dst[i] = (ushort)src[i];
}
var accessor = new BufferAccessor(new ArraySegment(bytes), AccessorValueType.UNSIGNED_SHORT, AccessorVectorType.SCALAR, count);
return accessor.AddAccessorTo(storage, 0, option.sparse, null, 0, count);
}
else
{
return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count);
}
}
else
{
return x.AddAccessorTo(storage, 0, option.sparse, null, offset, count);
}
}
static void ExportMesh(this Mesh mesh, List materials, Vrm10Storage storage, glTFMesh gltfMesh, ExportArgs option)
{
//
// primitive share vertex buffer
//
var attributeAccessorIndexMap = mesh.VertexBuffer
.ToDictionary(
kv => kv.Key,
kv => kv.Value.AddAccessorTo(
storage, 0, option.sparse,
kv.Key == VertexBuffer.PositionKey ? (Action, glTFAccessor>)Vec3MinMax : null
)
);
List> morphTargetAccessorIndexMapList = null;
if (mesh.MorphTargets.Any())
{
morphTargetAccessorIndexMapList = new List>();
foreach (var morphTarget in mesh.MorphTargets)
{
var dict = new Dictionary();
foreach (var kv in morphTarget.VertexBuffer)
{
if (option.removeTangent && kv.Key == VertexBuffer.TangentKey)
{
// remove tangent
continue;
}
if (option.removeMorphNormal && kv.Key == VertexBuffer.NormalKey)
{
// normal normal
continue;
}
if (kv.Value.Count != mesh.VertexBuffer.Count)
{
throw new Exception("inavlid data");
}
var accessorIndex = kv.Value.AddAccessorTo(storage, 0,
option.sparse,
kv.Key == VertexBuffer.PositionKey ? (Action, glTFAccessor>)Vec3MinMax : null);
dict.Add(kv.Key, accessorIndex);
}
morphTargetAccessorIndexMapList.Add(dict);
}
}
var drawCountOffset = 0;
foreach (var y in mesh.Submeshes)
{
// index
// slide index buffer accessor
var indicesAccessorIndex = ExportIndices(storage, mesh.IndexBuffer, drawCountOffset, y.DrawCount, option);
drawCountOffset += y.DrawCount;
var prim = new glTFPrimitives
{
mode = (int)mesh.Topology,
material = materials.IndexOf(y.Material),
indices = indicesAccessorIndex,
attributes = new glTFAttributes(),
};
gltfMesh.primitives.Add(prim);
// attribute
foreach (var kv in mesh.VertexBuffer)
{
var attributeAccessorIndex = attributeAccessorIndexMap[kv.Key];
switch (kv.Key)
{
case VertexBuffer.PositionKey: prim.attributes.POSITION = attributeAccessorIndex; break;
case VertexBuffer.NormalKey: prim.attributes.NORMAL = attributeAccessorIndex; break;
case VertexBuffer.ColorKey: prim.attributes.COLOR_0 = attributeAccessorIndex; break;
case VertexBuffer.TexCoordKey: prim.attributes.TEXCOORD_0 = attributeAccessorIndex; break;
case VertexBuffer.TexCoordKey2: prim.attributes.TEXCOORD_1 = attributeAccessorIndex; break;
case VertexBuffer.JointKey: prim.attributes.JOINTS_0 = attributeAccessorIndex; break;
case VertexBuffer.WeightKey: prim.attributes.WEIGHTS_0 = attributeAccessorIndex; break;
}
}
// morph target
if (mesh.MorphTargets.Any())
{
foreach (var (t, accessorIndexMap) in
Enumerable.Zip(mesh.MorphTargets, morphTargetAccessorIndexMapList, (t, v) => (t, v)))
{
var target = new gltfMorphTarget();
prim.targets.Add(target);
foreach (var kv in t.VertexBuffer)
{
if (!accessorIndexMap.TryGetValue(kv.Key, out int targetAccessorIndex))
{
continue;
}
switch (kv.Key)
{
case VertexBuffer.PositionKey:
target.POSITION = targetAccessorIndex;
break;
case VertexBuffer.NormalKey:
target.NORMAL = targetAccessorIndex;
break;
case VertexBuffer.TangentKey:
target.TANGENT = targetAccessorIndex;
break;
default:
throw new NotImplementedException();
}
}
}
}
}
// target name
if (mesh.MorphTargets.Any())
{
gltf_mesh_extras_targetNames.Serialize(gltfMesh, mesh.MorphTargets.Select(z => z.Name));
}
}
public static glTFMesh ExportMeshGroup(this MeshGroup src, List materials, Vrm10Storage storage, ExportArgs option)
{
var mesh = new glTFMesh
{
name = src.Name
};
foreach (var x in src.Meshes)
{
// MeshとSubmeshがGltfのPrimitiveに相当する?
x.ExportMesh(materials, storage, mesh, option);
}
return mesh;
}
}
}