Editor/Core/Import/Gltf/GltfAccessor.cs
// Adapted from humanoid-retargeter GltfModelDmxWriter.Accessor.
#nullable enable annotations
using System.Text.Json;
namespace HumanoidRigger.Formats.Gltf;
    internal sealed class GltfAccessor
    {
        private readonly byte[] _buffer;
        private readonly int _start;
        private readonly int _stride;
        private readonly int _componentSize;
        private readonly int _componentType;
        private readonly bool _normalized;

        public int Count { get; }
        public int Components { get; }

        public GltfAccessor(GltfDocument document, int index, int expectedComponents)
        {
            var root = document.Root;
            if (!root.TryGetProperty("accessors", out var accessors)
                || index < 0 || index >= accessors.GetArrayLength())
                throw new FormatException($"glTF accessor {index} does not exist.");
            var accessor = accessors[index];

            Components = accessor.GetProperty("type").GetString() switch
            {
                "SCALAR" => 1,
                "VEC2" => 2,
                "VEC3" => 3,
                "VEC4" => 4,
                "MAT4" => 16,
                var type => throw new FormatException($"Unsupported glTF accessor type '{type}'."),
            };
            if (Components != expectedComponents)
                throw new FormatException(
                    $"glTF accessor {index} has {Components} components; expected {expectedComponents}.");

            Count = accessor.GetProperty("count").GetInt32();
            if (Count <= 0 || (long)Count * Components * sizeof(float) > ModelImporter.MaximumBytes)
                throw new FormatException($"glTF accessor {index} has an invalid or excessive count.");
            _componentType = accessor.GetProperty("componentType").GetInt32();
            _componentSize = _componentType switch
            {
                5120 or 5121 => 1,
                5122 or 5123 => 2,
                5125 or 5126 => 4,
                _ => throw new FormatException(
                    $"Unsupported glTF accessor component type {_componentType}."),
            };
            _normalized = accessor.TryGetProperty("normalized", out var normalized)
                && normalized.GetBoolean();

            if (!accessor.TryGetProperty("bufferView", out var viewProperty))
            {
                _buffer = Array.Empty<byte>();
                _start = 0;
                _stride = checked(Components * _componentSize);
                if(accessor.TryGetProperty("byteOffset",out var absentOffset)&&absentOffset.GetInt32()!=0)throw new FormatException("An accessor without a bufferView cannot have a byte offset.");
            }
            else
            {
            var views = root.GetProperty("bufferViews");
            var viewIndex = viewProperty.GetInt32();
            if (viewIndex < 0 || viewIndex >= views.GetArrayLength())
                throw new FormatException($"glTF bufferView {viewIndex} does not exist.");
            var view = views[viewIndex];
            var bufferIndex = view.GetProperty("buffer").GetInt32();
            if (bufferIndex < 0 || bufferIndex >= document.Buffers.Count)
                throw new FormatException($"glTF buffer {bufferIndex} does not exist.");
            _buffer = document.Buffers[bufferIndex];
            var viewOffset = view.TryGetProperty("byteOffset", out var vo) ? vo.GetInt32() : 0;
            var accessorOffset = accessor.TryGetProperty("byteOffset", out var ao) ? ao.GetInt32() : 0;
            _start = checked(viewOffset + accessorOffset);
            var elementSize = checked(Components * _componentSize);
            _stride = view.TryGetProperty("byteStride", out var stride)
                ? stride.GetInt32() : elementSize;
            if (_stride < elementSize || _stride % _componentSize != 0 || (view.TryGetProperty("byteStride",out _) && (_stride>252||_stride%4!=0)))
                throw new FormatException("glTF accessor stride is smaller than its element.");
            var end = Count == 0 ? _start : (long)_start + (long)(Count - 1) * _stride + elementSize;
            int viewLength=view.GetProperty("byteLength").GetInt32();
            if (viewOffset<0||accessorOffset<0||viewLength<0||_start%_componentSize!=0||accessorOffset%_componentSize!=0||(long)viewOffset+viewLength>_buffer.Length||end>(long)viewOffset+viewLength)
                throw new FormatException($"glTF accessor {index} reads beyond its bufferView or is misaligned.");
            }
            if(accessor.TryGetProperty("sparse",out var sparse))
            {
                int count=sparse.GetProperty("count").GetInt32(),elementSize=Components*_componentSize;
                if(count<=0||count>Count)throw new FormatException("Invalid sparse accessor count.");
                var indices=sparse.GetProperty("indices");var values=sparse.GetProperty("values");
                int type=indices.GetProperty("componentType").GetInt32();int size=type switch{5121=>1,5123=>2,5125=>4,_=>throw new FormatException("Invalid sparse index type.")};
                byte[] Read(JsonElement definition,int required,int alignment)
                {
                    int viewId=definition.GetProperty("bufferView").GetInt32();
                    if(document.Root.GetProperty("bufferViews")[viewId].TryGetProperty("byteStride",out _))throw new FormatException("Sparse bufferViews cannot have a stride.");
                    var bytes=document.ViewBytes(viewId);int offset=definition.TryGetProperty("byteOffset",out var off)?off.GetInt32():0;
                    if(offset<0||offset%alignment!=0||(long)offset+required>bytes.Length)throw new FormatException("Sparse accessor exceeds its bufferView.");
                    return bytes.AsSpan(offset,required).ToArray();
                }
                var indexBytes=Read(indices,checked(count*size),size);var valueBytes=Read(values,checked(count*elementSize),_componentSize);
                var expanded=new byte[checked(Count*elementSize)];
                if(_buffer.Length>0)for(int i=0;i<Count;i++)Array.Copy(_buffer,_start+i*_stride,expanded,i*elementSize,elementSize);
                int prior=-1;
                for(int i=0;i<count;i++)
                {
                    int target=type switch{5121=>indexBytes[i],5123=>BitConverter.ToUInt16(indexBytes,i*size),_=>checked((int)BitConverter.ToUInt32(indexBytes,i*size))};
                    if(target<=prior||target>=Count)throw new FormatException("Sparse indices must be increasing and inside the accessor.");
                    Array.Copy(valueBytes,i*elementSize,expanded,target*elementSize,elementSize);prior=target;
                }
                _buffer=expanded;_start=0;_stride=elementSize;
            }
        }

        public float Float(int element, int component)
        {
            if (_buffer.Length == 0)
                return 0f;
            var offset = Offset(element, component);
            return _componentType switch
            {
                5120 => _normalized
                    ? MathF.Max(unchecked((sbyte)_buffer[offset]) / 127f, -1f)
                    : unchecked((sbyte)_buffer[offset]),
                5121 => _normalized ? _buffer[offset] / 255f : _buffer[offset],
                5122 => _normalized
                    ? MathF.Max(BitConverter.ToInt16(_buffer, offset) / 32767f, -1f)
                    : BitConverter.ToInt16(_buffer, offset),
                5123 => _normalized
                    ? BitConverter.ToUInt16(_buffer, offset) / 65535f
                    : BitConverter.ToUInt16(_buffer, offset),
                5125 => BitConverter.ToUInt32(_buffer, offset),
                _ => BitConverter.ToSingle(_buffer, offset),
            };
        }

        public int Unsigned(int element, int component)
        {
            if(_normalized||_componentType is not (5121 or 5123 or 5125))throw new FormatException("Indices and joints must be unnormalized unsigned integers.");
            if (_buffer.Length == 0)
                return 0;
            var offset = Offset(element, component);
            return _componentType switch
            {
                5121 => _buffer[offset],
                5123 => BitConverter.ToUInt16(_buffer, offset),
                5125 => checked((int)BitConverter.ToUInt32(_buffer, offset)),
                _ => throw new FormatException(
                    $"glTF indices require an unsigned integer accessor, got {_componentType}."),
            };
        }

        private int Offset(int element, int component)
        {
            if (element < 0 || element >= Count || component < 0 || component >= Components)
                throw new FormatException("glTF accessor index is out of range.");
            return checked(_start + element * _stride + component * _componentSize);
        }
    }