// SPDX-FileCopyrightText: 2023 Unity Technologies and the glTFast authors
// SPDX-License-Identifier: Apache-2.0
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Assertions;
// GLTF_EXPORT
using UnityEngine.Rendering;
namespace GLTFast.Schema
{
///
[Serializable]
public class Accessor : AccessorBase { }
///
[Serializable]
public abstract class AccessorBase : AccessorBase
where TSparse : AccessorSparseBase
{
///
public TSparse sparse;
///
public override AccessorSparseBase Sparse => sparse;
///
internal override void UnsetSparse()
{
sparse = null;
}
}
///
/// An accessor defines a method for retrieving data as typed arrays from
/// within a buffer view.
/// See .
/// accessor in the glTF 2.0 specification.
///
[Serializable]
public abstract class AccessorBase : NamedObject
{
///
/// The index of the bufferView.
/// If this is undefined, look in the sparse object for the index and value buffer views.
///
public int bufferView = -1;
///
/// The offset relative to the start of the bufferView in bytes.
/// This must be a multiple of the size of the component datatype.
///
public int byteOffset;
///
/// The datatype of components in the attribute.
/// All valid values correspond to WebGL enums.
/// The corresponding typed arrays are: `Int8Array`, `Uint8Array`, `Int16Array`,
/// `Uint16Array`, `Uint32Array`, and `Float32Array`, respectively.
/// 5125 (UNSIGNED_INT) is only allowed when the accessor contains indices
/// i.e., the accessor is only referenced by `primitive.indices`.
///
public GltfComponentType componentType;
///
/// Specifies whether integer data values should be normalized
/// (`true`) to [0, 1] (for unsigned types) or [-1, 1] (for signed types),
/// or converted directly (`false`) when they are accessed.
/// Must be `false` when accessor is used for animation data.
///
public bool normalized;
///
/// The number of attributes referenced by this accessor, not to be confused
/// with the number of bytes or number of components.
///
public int count;
///
/// Specifies if the attribute is a scalar, vector, or matrix,
/// and the number of elements in the vector or matrix.
///
// Field is public for unified serialization only. Warn via Obsolete attribute.
[Obsolete("Use GetAttributeType and SetAttributeType for access.")]
public string type;
[NonSerialized]
GltfAccessorAttributeType m_TypeEnum = GltfAccessorAttributeType.Undefined;
///
/// typed/cached getter from the string.
///
/// The Accessor's attribute type, if it could be retrieved correctly. otherwise
public GltfAccessorAttributeType GetAttributeType()
{
if (m_TypeEnum != GltfAccessorAttributeType.Undefined)
return m_TypeEnum;
#pragma warning disable CS0618 // Type or member is obsolete
if (Enum.TryParse(type, true, out m_TypeEnum))
{
type = null;
return m_TypeEnum;
}
type = null;
#pragma warning restore CS0618 // Type or member is obsolete
return GltfAccessorAttributeType.Undefined;
}
///
/// typed setter for the string.
///
/// Attribute type
public void SetAttributeType(GltfAccessorAttributeType attributeType)
{
m_TypeEnum = attributeType;
#pragma warning disable CS0618 // Type or member is obsolete
type = null;
#pragma warning restore CS0618 // Type or member is obsolete
}
///
/// Maximum value of each component in this attribute.
/// Both min and max arrays have the same length.
/// The length is determined by the value of the type property;
/// it can be 1, 2, 3, 4, 9, or 16.
///
/// When `componentType` is `5126` (FLOAT) each array value must be stored as
/// double-precision JSON number with numerical value which is equal to
/// buffer-stored single-precision value to avoid extra runtime conversions.
///
/// `normalized` property has no effect on array values: they always correspond
/// to the actual values stored in the buffer. When accessor is sparse, this
/// property must contain max values of accessor data with sparse substitution
/// applied.
///
public float[] max;
///
/// Minimum value of each component in this attribute.
/// Both min and max arrays have the same length. The length is determined by
/// the value of the type property; it can be 1, 2, 3, 4, 9, or 16.
///
/// When `componentType` is `5126` (FLOAT) each array value must be stored as
/// double-precision JSON number with numerical value which is equal to
/// buffer-stored single-precision value to avoid extra runtime conversions.
///
/// `normalized` property has no effect on array values: they always correspond
/// to the actual values stored in the buffer. When accessor is sparse, this
/// property must contain min values of accessor data with sparse substitution
/// applied.
///
public float[] min;
///
/// Sparse storage of attributes that deviate from their initialization value.
///
public abstract AccessorSparseBase Sparse { get; }
///
/// Sets to null.
///
internal abstract void UnsetSparse();
///
/// Provides size of components by type
///
/// glTF component type
/// Component size in bytes
/// Thrown when value of is unknown
public static int GetComponentTypeSize(GltfComponentType componentType)
{
switch (componentType)
{
case GltfComponentType.Byte:
case GltfComponentType.UnsignedByte:
return 1;
case GltfComponentType.Short:
case GltfComponentType.UnsignedShort:
return 2;
case GltfComponentType.Float:
case GltfComponentType.UnsignedInt:
return 4;
default:
throw new ArgumentOutOfRangeException(nameof(componentType), componentType, null);
}
}
///
/// Converts Unity vertex attribute format to glTF component type.
///
/// vertex attribute format
/// glTF component type
/// Thrown when the value of is unknown.
public static GltfComponentType GetComponentType(VertexAttributeFormat format)
{
switch (format)
{
case VertexAttributeFormat.Float32:
case VertexAttributeFormat.Float16:
return GltfComponentType.Float;
case VertexAttributeFormat.UNorm8:
case VertexAttributeFormat.UInt8:
return GltfComponentType.UnsignedByte;
case VertexAttributeFormat.SNorm8:
case VertexAttributeFormat.SInt8:
return GltfComponentType.Byte;
case VertexAttributeFormat.UNorm16:
case VertexAttributeFormat.UInt16:
return GltfComponentType.UnsignedShort;
case VertexAttributeFormat.SNorm16:
case VertexAttributeFormat.SInt16:
return GltfComponentType.Short;
case VertexAttributeFormat.UInt32:
case VertexAttributeFormat.SInt32:
return GltfComponentType.UnsignedInt;
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
}
///
/// Get one-dimensional glTF attribute type by number of components per elements.
/// Note that this does not support matrix types.
///
/// Number of components per element
/// Corresponding one-dimensional glTF attribute type
/// Thrown when is not between 1 and 4.
public static GltfAccessorAttributeType GetAccessorAttributeType(int dimension)
{
if (dimension < 1 || dimension > 4)
{
throw new ArgumentOutOfRangeException(nameof(dimension), dimension, null);
}
return (GltfAccessorAttributeType)dimension;
}
///
/// Get number of components of glTF attribute type.
///
/// glTF attribute type
/// Number of components
/// Thrown when the value of is unknown.
public static int GetAccessorAttributeTypeLength(GltfAccessorAttributeType type)
{
switch (type)
{
case GltfAccessorAttributeType.SCALAR:
return 1;
case GltfAccessorAttributeType.VEC2:
return 2;
case GltfAccessorAttributeType.VEC3:
return 3;
case GltfAccessorAttributeType.VEC4:
case GltfAccessorAttributeType.MAT2:
return 4;
case GltfAccessorAttributeType.MAT3:
return 9;
case GltfAccessorAttributeType.MAT4:
return 16;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
///
/// For 3D positional data, returns accessor's bounding box. Applies coordinate system transform (glTF to Unity)
///
/// Bounding box enclosing the minimum and maximum values
public Bounds? TryGetBounds()
{
Assert.AreEqual(GltfAccessorAttributeType.VEC3, GetAttributeType());
if (min != null && min.Length > 2 && max != null && max.Length > 2)
{
var maxBounds = new float3(-min[0], max[1], max[2]);
var minBounds = new float3(-max[0], min[1], min[2]);
if (normalized)
{
switch (componentType)
{
case GltfComponentType.Byte:
maxBounds = math.max(maxBounds / sbyte.MaxValue, -1);
minBounds = math.max(minBounds / sbyte.MaxValue, -1);
break;
case GltfComponentType.UnsignedByte:
maxBounds /= byte.MaxValue;
minBounds /= byte.MaxValue;
break;
case GltfComponentType.Short:
maxBounds = math.max(maxBounds / short.MaxValue, -1);
minBounds = math.max(minBounds / short.MaxValue, -1);
break;
case GltfComponentType.UnsignedShort:
maxBounds /= ushort.MaxValue;
minBounds /= ushort.MaxValue;
break;
case GltfComponentType.UnsignedInt:
maxBounds /= uint.MaxValue;
minBounds /= uint.MaxValue;
break;
}
}
return new Bounds
{
max = maxBounds,
min = minBounds
};
}
return null;
}
///
/// True if the accessor is sparse
///
public bool IsSparse => Sparse != null;
///
/// Byte size of one element
///
public int ElementByteSize => GetAccessorAttributeTypeLength(GetAttributeType()) * GetComponentTypeSize(componentType);
///
/// Overall, byte size.
/// Ignores interleaved or sparse accessors
///
public int ByteSize => ElementByteSize * count;
internal void GltfSerialize(JsonWriter writer)
{
writer.AddObject();
if (bufferView >= 0)
{
writer.AddProperty("bufferView", bufferView);
}
writer.AddProperty("componentType", (int)componentType);
writer.AddProperty("count", count);
Assert.AreNotEqual(GltfAccessorAttributeType.Undefined, m_TypeEnum);
writer.AddProperty("type", m_TypeEnum.ToString());
if (byteOffset > 0)
{
writer.AddProperty("byteOffset", byteOffset);
}
if (normalized)
{
writer.AddProperty("normalized", normalized);
}
if (max != null)
{
writer.AddArrayProperty("max", max);
}
if (min != null)
{
writer.AddArrayProperty("min", min);
}
if (Sparse != null)
{
writer.AddProperty("sparse");
Sparse.GltfSerialize(writer);
writer.Close();
}
writer.Close();
}
}
}