mixedreality/com.microsoft.mixedreality..../Core/Utilities/Gltf/Serialization/GltfConversions.cs

525 lines
21 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities.Gltf.Schema;
using System;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Utilities.Gltf.Serialization
{
/// <summary>
/// Provides data accessors and conversions useful while reading and using glTF objects.
/// </summary>
public static class GltfConversions
{
// glTF matrix: column vectors, column-major storage, +Y up, +Z forward, -X right, right-handed
// unity matrix: column vectors, column-major storage, +Y up, +Z forward, +X right, left-handed
// multiply by a negative X scale to convert handedness
private static readonly Vector3 CoordinateSpaceConversionScale = new Vector3(-1, 1, 1);
private static readonly Vector4 TangentSpaceConversionScale = new Vector4(-1, 1, 1, -1);
private static readonly string scalar = GltfAccessorAttributeType.SCALAR.ToString();
private static readonly string vec2 = GltfAccessorAttributeType.VEC2.ToString();
private static readonly string vec3 = GltfAccessorAttributeType.VEC3.ToString();
private static readonly string vec4 = GltfAccessorAttributeType.VEC4.ToString();
/// <summary>
/// Get TRS properties from GltfNode
/// </summary>
public static Matrix4x4 GetTrsProperties(this GltfNode node, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
Matrix4x4 matrix = node.matrix.GetMatrix4X4Value();
if (!node.useTRS)
{
matrix.GetTrsProperties(out position, out rotation, out scale);
}
else
{
position = node.translation.GetVector3Value();
rotation = node.rotation.GetQuaternionValue();
scale = node.scale.GetVector3Value(false);
}
return matrix;
}
/// <summary>
/// Get Color from float array
/// </summary>
public static Color GetColorValue(this float[] colorArray)
{
if (colorArray != null && (colorArray.Length == 3 || colorArray.Length == 4))
{
return new Color(colorArray[0], colorArray[1], colorArray[2], colorArray.Length < 4 ? 1f : colorArray[3]);
}
else
{
Debug.LogWarning($"GetColorValue() - Invalid color array of size {colorArray?.Length}");
return Color.black;
}
}
internal static float[] SetColorValue(this Color color)
{
return new[] { color.r, color.g, color.b, color.a };
}
/// <summary>
/// Get Vector2 from float array
/// </summary>
public static Vector2 GetVector2Value(this float[] vector2Array)
{
if (vector2Array != null && vector2Array.Length == 2)
{
return new Vector2(vector2Array[0], vector2Array[1]);
}
else
{
Debug.LogWarning($"GetVector2Value() - Invalid Vector2 array of size {vector2Array?.Length}");
return Vector2.zero;
}
}
internal static float[] SetVector2Value(this Vector2 vector)
{
return new[] { vector.x, vector.y };
}
/// <summary>
/// Get Vector3 from float array
/// </summary>
public static Vector3 GetVector3Value(this float[] vector3Array, bool convert = true)
{
if (vector3Array != null && vector3Array.Length == 3)
{
var vector = new Vector3(vector3Array[0], vector3Array[1], vector3Array[2]);
return convert ? Vector3.Scale(vector, CoordinateSpaceConversionScale) : vector;
}
else
{
Debug.LogWarning($"GetVector3Value() - Invalid Vector3 array of size {vector3Array?.Length}");
return Vector3.zero;
}
}
internal static float[] SetVector3Value(this Vector3 vector, bool convert = true)
{
if (convert)
{
vector = Vector3.Scale(vector, CoordinateSpaceConversionScale);
}
return new[] { vector.x, vector.y, vector.z };
}
/// <summary>
/// Get Quaternion from float array
/// </summary>
public static Quaternion GetQuaternionValue(this float[] quaternionArray, bool convert = true)
{
if (quaternionArray != null && quaternionArray.Length == 4)
{
var axes = new Vector3(quaternionArray[0], quaternionArray[1], quaternionArray[2]);
if (convert)
{
axes = Vector3.Scale(axes, CoordinateSpaceConversionScale) * -1.0f;
}
return new Quaternion(axes.x, axes.y, axes.z, quaternionArray[3]);
}
else
{
Debug.LogWarning($"GetQuaternionValue() - Invalid Quaternion array of size {quaternionArray?.Length}");
return Quaternion.identity;
}
}
internal static float[] SetQuaternionValue(this Quaternion quaternion, bool convert = true)
{
// get the original axis and apply conversion scale as well as potential rotation axis flip
var axes = new Vector3(quaternion.x, quaternion.y, quaternion.z);
if (convert)
{
axes = Vector3.Scale(axes, CoordinateSpaceConversionScale) * 1.0f;
}
return new[] { axes.x, axes.y, axes.z, quaternion.w };
}
/// <summary>
/// Get Matrix from double array
/// </summary>
public static Matrix4x4 GetMatrix4X4Value(this double[] matrixArray)
{
if (matrixArray != null && matrixArray.Length == 16)
{
var matrix = new Matrix4x4(
new Vector4((float)matrixArray[0], (float)matrixArray[1], (float)matrixArray[2], (float)matrixArray[3]),
new Vector4((float)matrixArray[4], (float)matrixArray[5], (float)matrixArray[6], (float)matrixArray[7]),
new Vector4((float)matrixArray[8], (float)matrixArray[9], (float)matrixArray[10], (float)matrixArray[11]),
new Vector4((float)matrixArray[12], (float)matrixArray[13], (float)matrixArray[14], (float)matrixArray[15]));
Matrix4x4 convert = Matrix4x4.Scale(CoordinateSpaceConversionScale);
return convert * matrix * convert;
}
else
{
Debug.LogWarning($"GetMatrix4X4Value() - Invalid matrix array of size {matrixArray?.Length}");
return Matrix4x4.identity;
}
}
internal static float[] SetMatrix4X4Value(this Matrix4x4 matrix)
{
var convert = Matrix4x4.Scale(CoordinateSpaceConversionScale);
matrix = convert * matrix * convert;
return new[]
{
matrix.m00, matrix.m10, matrix.m20, matrix.m30,
matrix.m01, matrix.m11, matrix.m21, matrix.m31,
matrix.m02, matrix.m12, matrix.m22, matrix.m32,
matrix.m03, matrix.m13, matrix.m23, matrix.m33
};
}
/// <summary>
/// Get TRS properties from matrix
/// </summary>
public static void GetTrsProperties(this Matrix4x4 matrix, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = matrix.GetColumn(3);
Vector3 x = matrix.GetColumn(0);
Vector3 y = matrix.GetColumn(1);
Vector3 z = matrix.GetColumn(2);
Vector3 calculatedZ = Vector3.Cross(x, y);
bool mirrored = Vector3.Dot(calculatedZ, z) < 0.0f;
scale.x = x.magnitude * (mirrored ? -1.0f : 1.0f);
scale.y = y.magnitude;
scale.z = z.magnitude;
rotation = Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
}
/// <summary>
/// Get Int array from accessor
/// </summary>
public static int[] GetIntArray(this GltfAccessor accessor, bool flipFaces = true)
{
if (accessor.type != scalar)
{
return null;
}
var array = new int[accessor.count];
GetTypeDetails(accessor.ComponentType, out int componentSize, out float _);
var stride = accessor.BufferView.byteStride > 0 ? accessor.BufferView.byteStride : componentSize;
var byteOffset = accessor.BufferView.byteOffset;
var bufferData = accessor.BufferView.Buffer.BufferData;
if (accessor.byteOffset >= 0)
{
byteOffset += accessor.byteOffset;
}
for (int i = 0; i < accessor.count; i++)
{
if (accessor.ComponentType == GltfComponentType.Float)
{
array[i] = (int)Mathf.Floor(BitConverter.ToSingle(bufferData, byteOffset + i * stride));
}
else
{
array[i] = (int)GetDiscreteUnsignedElement(bufferData, byteOffset + i * stride, accessor.ComponentType);
}
}
if (flipFaces)
{
for (int i = 0; i < array.Length; i += 3)
{
var temp = array[i];
array[i] = array[i + 2];
array[i + 2] = temp;
}
}
return array;
}
/// <summary>
/// Get Vector2 array from accessor
/// </summary>
public static Vector2[] GetVector2Array(this GltfAccessor accessor, bool flip = true)
{
if (accessor.type != vec2 || accessor.ComponentType == GltfComponentType.UnsignedInt)
{
return null;
}
var array = new Vector2[accessor.count];
GetTypeDetails(accessor.ComponentType, out int componentSize, out float maxValue);
var stride = accessor.BufferView.byteStride > 0 ? accessor.BufferView.byteStride : componentSize * 2;
var byteOffset = accessor.BufferView.byteOffset;
var bufferData = accessor.BufferView.Buffer.BufferData;
if (accessor.byteOffset >= 0)
{
byteOffset += accessor.byteOffset;
}
if (accessor.normalized) { maxValue = 1; }
for (int i = 0; i < accessor.count; i++)
{
if (accessor.ComponentType == GltfComponentType.Float)
{
array[i].x = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 0);
array[i].y = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 1);
}
else
{
array[i].x = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 0, accessor.ComponentType) / maxValue;
array[i].y = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 1, accessor.ComponentType) / maxValue;
}
if (flip)
{
array[i].y = 1.0f - array[i].y;
}
}
return array;
}
/// <summary>
/// Get Vector3 array from accessor
/// </summary>
public static Vector3[] GetVector3Array(this GltfAccessor accessor, bool convert = true)
{
if (accessor.type != vec3 || accessor.ComponentType == GltfComponentType.UnsignedInt)
{
return null;
}
var array = new Vector3[accessor.count];
GetTypeDetails(accessor.ComponentType, out int componentSize, out float maxValue);
var stride = accessor.BufferView.byteStride > 0 ? accessor.BufferView.byteStride : componentSize * 3;
var byteOffset = accessor.BufferView.byteOffset;
var bufferData = accessor.BufferView.Buffer.BufferData;
if (accessor.byteOffset >= 0)
{
byteOffset += accessor.byteOffset;
}
if (accessor.normalized) { maxValue = 1; }
for (int i = 0; i < accessor.count; i++)
{
if (accessor.ComponentType == GltfComponentType.Float)
{
array[i].x = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 0);
array[i].y = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 1);
array[i].z = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 2);
}
else
{
array[i].x = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 0, accessor.ComponentType) / maxValue;
array[i].y = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 1, accessor.ComponentType) / maxValue;
array[i].z = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 2, accessor.ComponentType) / maxValue;
}
if (convert)
{
array[i].x *= CoordinateSpaceConversionScale.x;
array[i].y *= CoordinateSpaceConversionScale.y;
array[i].z *= CoordinateSpaceConversionScale.z;
}
}
return array;
}
/// <summary>
/// Get Vector4 array from accessor
/// </summary>
public static Vector4[] GetVector4Array(this GltfAccessor accessor, bool convert = true)
{
if (accessor.type != vec4 || accessor.ComponentType == GltfComponentType.UnsignedInt)
{
return null;
}
var array = new Vector4[accessor.count];
GetTypeDetails(accessor.ComponentType, out int componentSize, out float maxValue);
var stride = accessor.BufferView.byteStride > 0 ? accessor.BufferView.byteStride : componentSize * 4;
var byteOffset = accessor.BufferView.byteOffset;
var bufferData = accessor.BufferView.Buffer.BufferData;
if (accessor.byteOffset >= 0)
{
byteOffset += accessor.byteOffset;
}
if (accessor.normalized) { maxValue = 1; }
for (int i = 0; i < accessor.count; i++)
{
if (accessor.ComponentType == GltfComponentType.Float)
{
array[i].x = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 0);
array[i].y = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 1);
array[i].z = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 2);
array[i].w = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 3);
}
else
{
array[i].x = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 0, accessor.ComponentType) / maxValue;
array[i].y = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 1, accessor.ComponentType) / maxValue;
array[i].z = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 2, accessor.ComponentType) / maxValue;
array[i].w = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 3, accessor.ComponentType) / maxValue;
}
if (convert)
{
array[i].x *= TangentSpaceConversionScale.x;
array[i].y *= TangentSpaceConversionScale.y;
array[i].z *= TangentSpaceConversionScale.z;
array[i].w *= TangentSpaceConversionScale.w;
}
}
return array;
}
/// <summary>
/// Get Color array from accessor
/// </summary>
public static Color[] GetColorArray(this GltfAccessor accessor)
{
if (accessor.type != vec3 && accessor.type != vec4 || accessor.ComponentType == GltfComponentType.UnsignedInt)
{
return null;
}
var array = new Color[accessor.count];
GetTypeDetails(accessor.ComponentType, out int componentSize, out float maxValue);
bool hasAlpha = accessor.type == vec4;
var stride = accessor.BufferView.byteStride > 0 ? accessor.BufferView.byteStride : componentSize * (hasAlpha ? 4 : 3);
var byteOffset = accessor.BufferView.byteOffset;
var bufferData = accessor.BufferView.Buffer.BufferData;
if (accessor.byteOffset >= 0)
{
byteOffset += accessor.byteOffset;
}
for (int i = 0; i < accessor.count; i++)
{
if (accessor.ComponentType == GltfComponentType.Float)
{
array[i].r = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 0);
array[i].g = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 1);
array[i].b = BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 2);
array[i].a = hasAlpha ? BitConverter.ToSingle(bufferData, byteOffset + i * stride + componentSize * 3) : 1f;
}
else
{
array[i].r = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 0, accessor.ComponentType) / maxValue;
array[i].g = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 1, accessor.ComponentType) / maxValue;
array[i].b = GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 2, accessor.ComponentType) / maxValue;
array[i].a = hasAlpha ? GetDiscreteElement(bufferData, byteOffset + i * stride + componentSize * 3, accessor.ComponentType) / maxValue : 1f;
}
}
return array;
}
private static void GetTypeDetails(GltfComponentType type, out int componentSize, out float maxValue)
{
componentSize = 1;
maxValue = byte.MaxValue;
switch (type)
{
case GltfComponentType.Byte:
componentSize = sizeof(sbyte);
maxValue = sbyte.MaxValue;
break;
case GltfComponentType.UnsignedByte:
componentSize = sizeof(byte);
maxValue = byte.MaxValue;
break;
case GltfComponentType.Short:
componentSize = sizeof(short);
maxValue = short.MaxValue;
break;
case GltfComponentType.UnsignedShort:
componentSize = sizeof(ushort);
maxValue = ushort.MaxValue;
break;
case GltfComponentType.UnsignedInt:
componentSize = sizeof(uint);
maxValue = uint.MaxValue;
break;
case GltfComponentType.Float:
componentSize = sizeof(float);
maxValue = float.MaxValue;
break;
default:
throw new Exception("Unsupported component type.");
}
}
private static int GetDiscreteElement(byte[] data, int offset, GltfComponentType type)
{
switch (type)
{
case GltfComponentType.Byte:
return Convert.ToSByte(data[offset]);
case GltfComponentType.UnsignedByte:
return data[offset];
case GltfComponentType.Short:
return BitConverter.ToInt16(data, offset);
case GltfComponentType.UnsignedShort:
return BitConverter.ToUInt16(data, offset);
case GltfComponentType.UnsignedInt:
return (int)BitConverter.ToUInt32(data, offset);
default:
throw new Exception($"Unsupported type passed in: {type}");
}
}
private static uint GetDiscreteUnsignedElement(byte[] data, int offset, GltfComponentType type)
{
switch (type)
{
case GltfComponentType.Byte:
return (uint)Convert.ToSByte(data[offset]);
case GltfComponentType.UnsignedByte:
return data[offset];
case GltfComponentType.Short:
return (uint)BitConverter.ToInt16(data, offset);
case GltfComponentType.UnsignedShort:
return BitConverter.ToUInt16(data, offset);
case GltfComponentType.UnsignedInt:
return BitConverter.ToUInt32(data, offset);
default:
throw new Exception($"Unsupported type passed in: {type}");
}
}
}
}