// 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(); } } }