Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
fe3105350This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using SharpGLTF.Memory;
using SharpGLTF.Validation;
using VALIDATIONCTX = SharpGLTF.Validation.ValidationContext;
namespace SharpGLTF.Schema2
{
// https://github.com/KhronosGroup/glTF/issues/827#issuecomment-277537204
[System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
[System.Diagnostics.DebuggerTypeProxy(typeof(Debug._AccessorDebugProxy))]
public sealed partial class Accessor
{
#region debug
internal string _GetDebuggerDisplay()
{
return Debug.DebuggerDisplay.ToReportLong(this);
}
#endregion
#region lifecycle
internal Accessor()
{
_min = new List<double>();
_max = new List<double>();
}
#endregion
#region properties
internal int _SourceBufferViewIndex => this._bufferView.AsValue(-1);
/// <summary>
/// Gets the <see cref="BufferView"/> buffer that contains the items as an encoded byte array.
/// </summary>
public BufferView SourceBufferView => this._bufferView.HasValue ? this.LogicalParent.LogicalBufferViews[this._bufferView.Value] : null;
/// <summary>
/// Gets the number of items.
/// </summary>
public int Count => this._count;
/// <summary>
/// Gets the starting byte offset within <see cref="SourceBufferView"/>.
/// </summary>
public int ByteOffset => this._byteOffset.AsValue(0);
/// <summary>
/// Gets the number of bytes, starting at <see cref="ByteOffset"/> use by this <see cref="Accessor"/>
/// </summary>
public int ByteLength => SourceBufferView.GetAccessorByteLength(Format, Count);
/// <summary>
/// Gets the <see cref="DimensionType"/> of an item.
/// </summary>
public DimensionType Dimensions => this._type;
/// <summary>
/// Gets the <see cref="EncodingType"/> of an item.
/// </summary>
public EncodingType Encoding => this._componentType;
/// <summary>
/// Gets a value indicating whether the items values are normalized.
/// </summary>
public Boolean Normalized => this._normalized.AsValue(false);
/// <summary>
/// Gets a value indicating whether this <see cref="Accessor"/> has a sparse structure.
/// </summary>
public Boolean IsSparse => this._sparse != null;
public AttributeFormat Format => new AttributeFormat(_type, _componentType, this._normalized.AsValue(false));
/// <summary>
/// Gets the number of bytes required to encode a single item in <see cref="SourceBufferView"/>
/// Given the current <see cref="Dimensions"/> and <see cref="Encoding"/> states.
/// </summary>
[Obsolete("Use Format.ByteSize instead")]
public int ElementByteSize => Encoding.ByteLength() * Dimensions.DimCount();
#endregion
#region API
internal MemoryAccessor _GetMemoryAccessor(string name = null)
{
var view = SourceBufferView;
var info = new MemoryAccessInfo(name, ByteOffset, Count, view.ByteStride, Format);
return new MemoryAccessor(view.Content, info);
}
internal KeyValuePair<IntegerArray, MemoryAccessor>? _GetSparseMemoryAccessor()
{
return this._sparse == null
?
(KeyValuePair<IntegerArray, MemoryAccessor>?)null
:
this._sparse._CreateMemoryAccessors(this);
}
protected override IEnumerable<ExtraProperties> GetLogicalChildren()
{
return base.GetLogicalChildren().ConcatElements(_sparse);
}
public void UpdateBounds()
{
this._min.Clear();
this._max.Clear();
if (this.Count == 0) return;
// With the current limitations of the serializer, we can only handle floating point values.
if (this.Encoding != EncodingType.FLOAT) return;
// https://github.com/KhronosGroup/glTF-Validator/issues/79
var dimensions = this.Dimensions.DimCount();
for (int i = 0; i < dimensions; ++i)
{
this._min.Add(double.MaxValue);
this._max.Add(double.MinValue);
}
var array = new MultiArray(this.SourceBufferView.Content, this.ByteOffset, this.Count, this.SourceBufferView.ByteStride, dimensions, this.Encoding, false);
var current = new float[dimensions];
for (int i = 0; i < array.Count; ++i)
{
array.CopyItemTo(i, current);
for (int j = 0; j < current.Length; ++j)
{
this._min[j] = Math.Min(this._min[j], current[j]);
this._max[j] = Math.Max(this._max[j], current[j]);
}
}
}
#endregion
#region Data Buffer API
/// <summary>
/// Associates this <see cref="Accessor"/> with a <see cref="BufferView"/>
/// </summary>
/// <param name="buffer">The <see cref="BufferView"/> source.</param>
/// <param name="bufferByteOffset">The start byte offset within <paramref name="buffer"/>.</param>
/// <param name="itemCount">The number of items in the accessor.</param>
/// <param name="dimensions">The <see cref="DimensionType"/> item type.</param>
/// <param name="encoding">The <see cref="EncodingType"/> item encoding.</param>
/// <param name="normalized">The item normalization mode.</param>
public void SetData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions, EncodingType encoding, Boolean normalized)
{
Guard.NotNull(buffer, nameof(buffer));
Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
Guard.MustBeGreaterThanOrEqualTo(bufferByteOffset, _byteOffsetMinimum, nameof(bufferByteOffset));
Guard.MustBeGreaterThanOrEqualTo(itemCount, _countMinimum, nameof(itemCount));
this._bufferView = buffer.LogicalIndex;
this._byteOffset = bufferByteOffset.AsNullable(_byteOffsetDefault, _byteOffsetMinimum, int.MaxValue);
this._count = itemCount;
this._type = dimensions;
this._componentType = encoding;
this._normalized = normalized.AsNullable(_normalizedDefault);
UpdateBounds();
}
public Matrix4x4Array AsMatrix4x4Array()
{
return _GetMemoryAccessor().AsMatrix4x4Array();
}
#endregion
#region Index Buffer API
public void SetIndexData(MemoryAccessor src)
{
Guard.NotNull(src, nameof(src));
var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.ByteStride, BufferMode.ELEMENT_ARRAY_BUFFER);
SetIndexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Encoding.ToIndex());
}
/// <summary>
/// Associates this <see cref="Accessor"/> with a <see cref="BufferView"/>
/// </summary>
/// <param name="buffer">The <see cref="BufferView"/> source.</param>
/// <param name="bufferByteOffset">The start byte offset within <paramref name="buffer"/>.</param>
/// <param name="itemCount">The number of items in the accessor.</param>
/// <param name="encoding">The <see cref="IndexEncodingType"/> item encoding.</param>
public void SetIndexData(BufferView buffer, int bufferByteOffset, int itemCount, IndexEncodingType encoding)
{
Guard.NotNull(buffer, nameof(buffer));
Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
Guard.IsFalse(buffer.IsVertexBuffer, nameof(buffer));
SetData(buffer, bufferByteOffset, itemCount, DimensionType.SCALAR, encoding.ToComponent(), false);
}
public IntegerArray AsIndicesArray()
{
Guard.IsFalse(this.IsSparse, nameof(IsSparse));
Guard.IsTrue(this.Dimensions == DimensionType.SCALAR, nameof(Dimensions));
return new IntegerArray(SourceBufferView.Content, this.ByteOffset, this._count, this.Encoding.ToIndex());
}
#endregion
#region Vertex Buffer API
public void SetVertexData(MemoryAccessor src)
{
Guard.NotNull(src, nameof(src));
var bv = this.LogicalParent.UseBufferView(src.Data, src.Attribute.StepByteLength, BufferMode.ARRAY_BUFFER);
SetVertexData(bv, src.Attribute.ByteOffset, src.Attribute.ItemsCount, src.Attribute.Dimensions, src.Attribute.Encoding, src.Attribute.Normalized);
}
/// <summary>
/// Associates this <see cref="Accessor"/> with a <see cref="BufferView"/>
/// </summary>
/// <param name="buffer">The <see cref="BufferView"/> source.</param>
/// <param name="bufferByteOffset">The start byte offset within <paramref name="buffer"/>.</param>
/// <param name="itemCount">The number of items in the accessor.</param>
/// <param name="dimensions">The <see cref="DimensionType"/> item type.</param>
/// <param name="encoding">The <see cref="EncodingType"/> item encoding.</param>
/// <param name="normalized">The item normalization mode.</param>
public void SetVertexData(BufferView buffer, int bufferByteOffset, int itemCount, DimensionType dimensions = DimensionType.VEC3, EncodingType encoding = EncodingType.FLOAT, Boolean normalized = false)
{
Guard.NotNull(buffer, nameof(buffer));
Guard.MustShareLogicalParent(this, buffer, nameof(buffer));
Guard.MustBePositiveAndMultipleOf(dimensions.DimCount() * encoding.ByteLength(), 4, nameof(encoding));
Guard.IsFalse(buffer.IsIndexBuffer, nameof(buffer));
SetData(buffer, bufferByteOffset, itemCount, dimensions, encoding, normalized);
}
public IList<Single> AsScalarArray()
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsScalarArray();
var sparseKV = this._sparse._CreateMemoryAccessors(this);
return MemoryAccessor.CreateScalarSparseArray(memory, sparseKV.Key, sparseKV.Value);
}
public IList<Vector2> AsVector2Array()
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsVector2Array();
var sparseKV = this._sparse._CreateMemoryAccessors(this);
return MemoryAccessor.CreateVector2SparseArray(memory, sparseKV.Key, sparseKV.Value);
}
public IList<Vector3> AsVector3Array()
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsVector3Array();
var sparseKV = this._sparse._CreateMemoryAccessors(this);
return MemoryAccessor.CreateVector3SparseArray(memory, sparseKV.Key, sparseKV.Value);
}
public IList<Vector4> AsVector4Array()
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsVector4Array();
var sparseKV = this._sparse._CreateMemoryAccessors(this);
return MemoryAccessor.CreateVector4SparseArray(memory, sparseKV.Key, sparseKV.Value);
}
public IList<Vector4> AsColorArray(Single defaultW = 1)
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsColorArray(defaultW);
var sparseKV = this._sparse._CreateMemoryAccessors(this);
return MemoryAccessor.CreateColorSparseArray(memory, sparseKV.Key, sparseKV.Value, defaultW);
}
public IList<Quaternion> AsQuaternionArray()
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsQuaternionArray();
throw new NotImplementedException();
}
public IList<Single[]> AsMultiArray(int dimensions)
{
var memory = _GetMemoryAccessor();
if (this._sparse == null) return memory.AsMultiArray(dimensions);
throw new NotImplementedException();
}
public ArraySegment<Byte> TryGetVertexBytes(int vertexIdx)
{
if (_sparse != null) throw new InvalidOperationException("Can't be used on Acessors with Sparse Data");
var itemByteSz = Encoding.ByteLength() * Dimensions.DimCount();
var byteStride = Math.Max(itemByteSz, SourceBufferView.ByteStride);
var byteOffset = vertexIdx * byteStride;
return SourceBufferView.Content.Slice(this.ByteOffset + (vertexIdx * byteStride), itemByteSz);
}
#endregion
#region Validation
protected override void OnValidateReferences(VALIDATIONCTX validate)
{
base.OnValidateReferences(validate);
validate
.IsDefined(nameof(_bufferView), _bufferView)
.NonNegative(nameof(_byteOffset), _byteOffset)
.IsGreaterOrEqual(nameof(_count), _count, _countMinimum)
.IsNullOrIndex(nameof(_bufferView), _bufferView, this.LogicalParent.LogicalBufferViews);
}
protected override void OnValidateContent(VALIDATIONCTX validate)
{
base.OnValidateContent(validate);
BufferView.VerifyAccess(validate, this.SourceBufferView, this.ByteOffset, this.Format, this.Count);
validate.That(() => MemoryAccessor.VerifyAccessorBounds(_GetMemoryAccessor(), _min, _max));
// at this point we don't know which kind of data we're accessing, so it's up to the components
// using this accessor to validate the data.
}
internal void ValidateIndices(VALIDATIONCTX validate, uint vertexCount, PrimitiveType drawingType)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ELEMENT_ARRAY_BUFFER);
validate.IsAnyOf("Format", Format, (DimensionType.SCALAR, EncodingType.UNSIGNED_BYTE), (DimensionType.SCALAR, EncodingType.UNSIGNED_SHORT), (DimensionType.SCALAR, EncodingType.UNSIGNED_INT));
validate.AreEqual(nameof(SourceBufferView.ByteStride), SourceBufferView.ByteStride, 0); // "bufferView.byteStride must not be defined for indices accessor.";
validate.That(() => MemoryAccessor.VerifyVertexIndices(_GetMemoryAccessor(), vertexCount));
}
internal static void ValidateVertexAttributes(VALIDATIONCTX validate, IReadOnlyDictionary<string, Accessor> attributes, int skinsMaxJointCount)
{
if (validate.TryFix)
{
foreach (var kvp in attributes.Where(item => item.Key != "POSITION"))
{
// remove unnecessary bounds
kvp.Value._min.Clear();
kvp.Value._max.Clear();
}
}
if (attributes.TryGetValue("POSITION", out Accessor positions)) positions._ValidatePositions(validate);
if (attributes.TryGetValue("NORMAL", out Accessor normals)) normals._ValidateNormals(validate);
if (attributes.TryGetValue("TANGENT", out Accessor tangents)) tangents._ValidateTangents(validate);
if (attributes.TryGetValue("JOINTS_0", out Accessor joints0)) joints0._ValidateJoints(validate, "JOINTS_0", skinsMaxJointCount);
if (attributes.TryGetValue("JOINTS_1", out Accessor joints1)) joints0._ValidateJoints(validate, "JOINTS_1", skinsMaxJointCount);
attributes.TryGetValue("WEIGHTS_0", out Accessor weights0);
attributes.TryGetValue("WEIGHTS_1", out Accessor weights1);
_ValidateWeights(validate, weights0, weights1);
}
private void _ValidatePositions(VALIDATIONCTX validate)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ARRAY_BUFFER);
if (!this.LogicalParent.MeshQuantizationAllowed)
{
validate.IsAnyOf(nameof(Format), Format, DimensionType.VEC3);
}
else
{
validate.IsAnyOf(nameof(Dimensions), Dimensions, DimensionType.VEC3);
}
validate.ArePositions("POSITION", this.AsVector3Array());
}
private void _ValidateNormals(VALIDATIONCTX validate)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ARRAY_BUFFER);
if (!this.LogicalParent.MeshQuantizationAllowed)
{
validate.IsAnyOf(nameof(Format), Format, DimensionType.VEC3);
}
else
{
validate.IsAnyOf(nameof(Dimensions), Dimensions, DimensionType.VEC3);
}
if (validate.TryFix) this.AsVector3Array().SanitizeNormals();
validate.AreNormals("NORMAL", this.AsVector3Array());
}
private void _ValidateTangents(VALIDATIONCTX validate)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ARRAY_BUFFER);
if (!this.LogicalParent.MeshQuantizationAllowed)
{
validate.IsAnyOf(nameof(Format), Format, DimensionType.VEC3, DimensionType.VEC4);
}
else
{
validate.IsAnyOf(nameof(Dimensions), Dimensions, DimensionType.VEC3, DimensionType.VEC4);
}
if (validate.TryFix)
{
if (Dimensions == DimensionType.VEC3) this.AsVector3Array().SanitizeNormals();
if (Dimensions == DimensionType.VEC4) this.AsVector4Array().SanitizeTangents();
}
if (Dimensions == DimensionType.VEC3) validate.AreNormals("TANGENT", this.AsVector3Array());
if (Dimensions == DimensionType.VEC4) validate.AreTangents("TANGENT", this.AsVector4Array());
}
private void _ValidateJoints(VALIDATIONCTX validate, string attributeName, int skinsMaxJointCount)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ARRAY_BUFFER);
validate
.IsAnyOf(nameof(Format), Format, (DimensionType.VEC4, EncodingType.UNSIGNED_BYTE), (DimensionType.VEC4, EncodingType.UNSIGNED_SHORT), DimensionType.VEC4)
.AreJoints(attributeName, this.AsVector4Array(), skinsMaxJointCount);
}
private static void _ValidateWeights(VALIDATIONCTX validate, Accessor weights0, Accessor weights1)
{
weights0?._ValidateWeights(validate);
weights1?._ValidateWeights(validate);
var memory0 = weights0?._GetMemoryAccessor("WEIGHTS_0");
var memory1 = weights1?._GetMemoryAccessor("WEIGHTS_1");
validate.That(() => MemoryAccessor.VerifyWeightsSum(memory0, memory1));
}
private void _ValidateWeights(VALIDATIONCTX validate)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsageGPU(validate, BufferMode.ARRAY_BUFFER);
validate.IsAnyOf(nameof(Format), Format, (DimensionType.VEC4, EncodingType.UNSIGNED_BYTE, true), (DimensionType.VEC4, EncodingType.UNSIGNED_SHORT, true), DimensionType.VEC4);
}
internal void ValidateMatrices(VALIDATIONCTX validate, bool mustDecompose = true, bool mustInvert = true)
{
validate = validate.GetContext(this);
SourceBufferView.ValidateBufferUsagePlainData(validate);
validate.IsAnyOf(nameof(Format), Format, (DimensionType.MAT4, EncodingType.BYTE, true), (DimensionType.MAT4, EncodingType.SHORT, true), DimensionType.MAT4);
var matrices = this.AsMatrix4x4Array();
for (int i = 0; i < matrices.Count; ++i)
{
validate.IsNullOrMatrix("Matrices", matrices[i], mustDecompose, mustInvert);
}
}
internal void ValidateAnimationInput(VALIDATIONCTX validate)
{
SourceBufferView.ValidateBufferUsagePlainData(validate);
validate.IsAnyOf(nameof(Dimensions), Dimensions, DimensionType.SCALAR);
}
internal void ValidateAnimationOutput(VALIDATIONCTX validate)
{
SourceBufferView.ValidateBufferUsagePlainData(validate);
validate.IsAnyOf(nameof(Dimensions), Dimensions, DimensionType.SCALAR, DimensionType.VEC3, DimensionType.VEC4);
}
#endregion
}
public partial class ModelRoot
{
/// <summary>
/// Creates a new <see cref="Accessor"/> instance
/// and adds it to <see cref="ModelRoot.LogicalAccessors"/>.
/// </summary>
/// <param name="name">The name of the instance.</param>
/// <returns>A <see cref="Accessor"/> instance.</returns>
public Accessor CreateAccessor(string name = null)
{
var accessor = new Accessor
{
Name = name
};
_accessors.Add(accessor);
return accessor;
}
}
}