Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
namespace SharpGLTF.Transforms
{
/// <summary>
/// Represents a sparse collection of non zero weight values, with a maximum of 8 weights.
/// </summary>
/// <remarks>
/// <see cref="SparseWeight8"/> is being used in two different contexts:
/// - As an utility class to define per vertex joint weights in mesh skinning.
/// - As an animation key in morph targets; a mesh can have many morph targets, but realistically and due to GPU limitations, only up to 8 morph targets can be blended at the same time.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{_GetDebuggerDisplay(),nq}")]
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly struct SparseWeight8
{
#region debug
private string _GetDebuggerDisplay()
{
var iw = this.GetIndexedWeights()
.Where(item => item.Weight != 0)
.Select(item => $"[{item.Index}]={item.Weight}");
var txt = string.Join(" ", iw);
return string.IsNullOrWhiteSpace(txt) ? "Empty" : txt;
}
#endregion
#region constructors
/// <summary>
/// Creates a new <see cref="SparseWeight8"/> from a weights collection.
/// If there's more than 8 non zero values, the 8 most representative values are taken.
/// </summary>
/// <param name="weights">A sequence of weight values.</param>
/// <returns>A <see cref="SparseWeight8"/> instance.</returns>
public static SparseWeight8 Create(params float[] weights)
{
return Create((IEnumerable<float>)weights);
}
/// <summary>
/// Creates a new <see cref="SparseWeight8"/> from a weights collection.
/// If there's more than 8 non zero values, the 8 most representative values are taken
/// </summary>
/// <param name="weights">A sequence of weight values.</param>
/// <returns>A <see cref="SparseWeight8"/> instance.</returns>
public static SparseWeight8 Create(IEnumerable<float> weights)
{
if (weights == null) return default;
var indexedWeights = weights
.Select((val, idx) => (idx, val))
.Where(item => item.val != 0)
.OrderByDescending(item => Math.Abs(item.val) )
.Take(8)
.ToArray();
return Create(indexedWeights);
}
/// <summary>
/// Creates a new <see cref="SparseWeight8"/> from an indexed weight collection.
/// If there's more than 8 non zero values, the 8 most representative values are taken
/// </summary>
/// <param name="indexedWeights">A sequence of indexed weight values.</param>
/// <returns>A <see cref="SparseWeight8"/> instance.</returns>
public static SparseWeight8 Create(params (int Index, float Weight)[] indexedWeights)
{
if (indexedWeights == null) return default;
Span<IndexWeight> sparse = stackalloc IndexWeight[indexedWeights.Length];
int o = 0;
for (int i = 0; i < indexedWeights.Length; ++i)
{
var p = indexedWeights[i];
if (p.Weight == 0) continue;
Guard.MustBeGreaterThanOrEqualTo(p.Index, 0, nameof(indexedWeights));
sparse[o++] = p;
}
sparse = sparse.Slice(0, o);
if (indexedWeights.Length > 8)
{
IndexWeight.BubbleSortByWeight(sparse);
sparse = sparse.Slice(0, 8);
}
return new SparseWeight8(sparse);
}
/// <summary>
/// Initializes a new instance of the <see cref="SparseWeight8"/> struct.
/// </summary>
/// <param name="idx0123">The indices of weights 0 to 3.</param>
/// <param name="wgt0123">The weights of indices 0 to 3.</param>
public SparseWeight8(in Vector4 idx0123, in Vector4 wgt0123)
{
Index0 = (int)idx0123.X;
Index1 = (int)idx0123.Y;
Index2 = (int)idx0123.Z;
Index3 = (int)idx0123.W;
Index4 = 0;
Index5 = 0;
Index6 = 0;
Index7 = 0;
Weight0 = wgt0123.X;
Weight1 = wgt0123.Y;
Weight2 = wgt0123.Z;
Weight3 = wgt0123.W;
Weight4 = 0;
Weight5 = 0;
Weight6 = 0;
Weight7 = 0;
}
/// <summary>
/// Initializes a new instance of the <see cref="SparseWeight8"/> struct.
/// </summary>
/// <param name="idx0123">The indices of weights 0 to 3.</param>
/// <param name="idx4567">The indices of weights 4 to 7.</param>
/// <param name="wgt0123">The weights of indices 0 to 3.</param>
/// <param name="wgt4567">The weights of indices 4 to 7.</param>
public SparseWeight8(in Vector4 idx0123, in Vector4 idx4567, in Vector4 wgt0123, in Vector4 wgt4567)
{
Index0 = (int)idx0123.X;
Index1 = (int)idx0123.Y;
Index2 = (int)idx0123.Z;
Index3 = (int)idx0123.W;
Index4 = (int)idx4567.X;
Index5 = (int)idx4567.Y;
Index6 = (int)idx4567.Z;
Index7 = (int)idx4567.W;
Weight0 = wgt0123.X;
Weight1 = wgt0123.Y;
Weight2 = wgt0123.Z;
Weight3 = wgt0123.W;
Weight4 = wgt4567.X;
Weight5 = wgt4567.Y;
Weight6 = wgt4567.Z;
Weight7 = wgt4567.W;
}
private SparseWeight8(ReadOnlySpan<IndexWeight> iw)
{
System.Diagnostics.Debug.Assert(iw.Length <= 8, nameof(iw));
this = default;
if (iw.Length < 1) return;
this.Index0 = iw[0].Index;
this.Weight0 = iw[0].Weight;
if (iw.Length < 2) return;
this.Index1 = iw[1].Index;
this.Weight1 = iw[1].Weight;
if (iw.Length < 3) return;
this.Index2 = iw[2].Index;
this.Weight2 = iw[2].Weight;
if (iw.Length < 4) return;
this.Index3 = iw[3].Index;
this.Weight3 = iw[3].Weight;
if (iw.Length < 5) return;
this.Index4 = iw[4].Index;
this.Weight4 = iw[4].Weight;
if (iw.Length < 6) return;
this.Index5 = iw[5].Index;
this.Weight5 = iw[5].Weight;
if (iw.Length < 7) return;
this.Index6 = iw[6].Index;
this.Weight6 = iw[6].Weight;
if (iw.Length < 8) return;
this.Index7 = iw[7].Index;
this.Weight7 = iw[7].Weight;
}
private SparseWeight8(in SparseWeight8 sparse, float scale)
{
Index0 = sparse.Index0;
Index1 = sparse.Index1;
Index2 = sparse.Index2;
Index3 = sparse.Index3;
Index4 = sparse.Index4;
Index5 = sparse.Index5;
Index6 = sparse.Index6;
Index7 = sparse.Index7;
Weight0 = sparse.Weight0 * scale;
Weight1 = sparse.Weight1 * scale;
Weight2 = sparse.Weight2 * scale;
Weight3 = sparse.Weight3 * scale;
Weight4 = sparse.Weight4 * scale;
Weight5 = sparse.Weight5 * scale;
Weight6 = sparse.Weight6 * scale;
Weight7 = sparse.Weight7 * scale;
}
internal static (SparseWeight8 TangentIn, SparseWeight8 Value, SparseWeight8 TangentOut) AsTuple(float[] tangentIn, float[] value, float[] tangentOut)
{
return (Create(tangentIn), Create(value), Create(tangentOut));
}
#endregion
#region data
public readonly int Index0;
public readonly float Weight0;
public readonly int Index1;
public readonly float Weight1;
public readonly int Index2;
public readonly float Weight2;
public readonly int Index3;
public readonly float Weight3;
public readonly int Index4;
public readonly float Weight4;
public readonly int Index5;
public readonly float Weight5;
public readonly int Index6;
public readonly float Weight6;
public readonly int Index7;
public readonly float Weight7;
public static bool AreWeightsEqual(in SparseWeight8 x, in SparseWeight8 y)
{
const int STACKSIZE = 8 * 2;
Span<int> indices = stackalloc int[STACKSIZE];
Span<float> xWeights = stackalloc float[STACKSIZE];
Span<float> yWeights = stackalloc float[STACKSIZE];
int offset = 0;
offset = IndexWeight.CopyTo(x, indices, xWeights, offset);
offset = IndexWeight.CopyTo(y, indices, yWeights, offset);
xWeights = xWeights.Slice(0, offset);
yWeights = yWeights.Slice(0, offset);
return xWeights.SequenceEqual(yWeights);
}
public int GetWeightsHashCode()
{
Span<IndexWeight> iw = stackalloc IndexWeight[8];
var c = IndexWeight.CopyTo(this, iw);
iw = iw.Slice(0, c);
IndexWeight.BubbleSortByIndex(iw);
int h = 0;
for (int i = 0; i < iw.Length; ++i)
{
h += iw[i].GetHashCode();
h *= 17;
}
return h;
}
#endregion
#region properties
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public int Count => GetExpandedCount();
public float this[int index] => GetExpandedAt(index);
/// <summary>
/// Gets a value indicating whether all the weights in this <see cref="SparseWeight8"/> are zero.
/// </summary>
[System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public bool IsWeightless => Weight0 == 0 & Weight1 == 0 & Weight2 == 0 & Weight3 == 0 & Weight4 == 0 & Weight5 == 0 & Weight6 == 0 & Weight7 == 0;
public float WeightSum => Weight0 + Weight1 + Weight2 + Weight3 + Weight4 + Weight5 + Weight6 + Weight7;
public int MaxIndex => _GetMaxIndex();
#endregion
#region API
/// <summary>
/// Returns a copy of this <see cref="SparseWeight8"/> where all the
/// indices have been reordered by weight in descending order.
/// </summary>
/// <param name="sparse">The <see cref="SparseWeight8"/> to get ordered.</param>
/// <returns>A weight ordered <see cref="SparseWeight8"/>.</returns>
public static SparseWeight8 OrderedByWeight(in SparseWeight8 sparse)
{
Span<IndexWeight> iw = stackalloc IndexWeight[8];
var c = IndexWeight.CopyTo(sparse, iw);
iw = iw.Slice(0, c);
IndexWeight.BubbleSortByWeight(iw);
return new SparseWeight8(iw);
}
/// <summary>
/// Returns a copy of this <see cref="SparseWeight8"/> where all the
/// indices have been reordered by index in ascending order.
/// </summary>
/// <param name="sparse">The <see cref="SparseWeight8"/> to get ordered.</param>
/// <returns>An index ordered <see cref="SparseWeight8"/>.</returns>
public static SparseWeight8 OrderedByIndex(in SparseWeight8 sparse)
{
Span<IndexWeight> iw = stackalloc IndexWeight[8];
var c = IndexWeight.CopyTo(sparse, iw);
iw = iw.Slice(0, c);
IndexWeight.BubbleSortByIndex(iw);
return new SparseWeight8(iw);
}
/// <summary>
/// Adds <paramref name="x"/> with <paramref name="y"/> element wise.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first operand.</param>
/// <param name="y">The second operand.</param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 Add(in SparseWeight8 x, in SparseWeight8 y)
{
return _OperateLinear(x, y, (xx, yy) => xx + yy);
}
/// <summary>
/// Subtracts <paramref name="y"/> from <paramref name="x"/> element wise.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first operand.</param>
/// <param name="y">The second operand.</param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 Subtract(in SparseWeight8 x, in SparseWeight8 y)
{
return _OperateLinear(x, y, (xx, yy) => xx - yy);
}
/// <summary>
/// Multiplies <paramref name="x"/> with <paramref name="y"/> element wise.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first operand.</param>
/// <param name="y">The second operand.</param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 Multiply(in SparseWeight8 x, in SparseWeight8 y)
{
return _OperateLinear(x, y, (xx, yy) => xx * yy);
}
/// <summary>
/// Multiplies <paramref name="x"/> with <paramref name="y"/> element wise.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first operand.</param>
/// <param name="y">The second operand.</param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 Multiply(in SparseWeight8 x, Single y)
{
return new SparseWeight8(x, y);
}
/// <summary>
/// Interpolates Linearly <paramref name="x"/> with <paramref name="y"/> an <paramref name="amount"/>.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first operand.</param>
/// <param name="y">The second operand.</param>
/// <param name="amount">The amount of <paramref name="y"/></param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 InterpolateLinear(in SparseWeight8 x, in SparseWeight8 y, float amount)
{
var xAmount = 1.0f - amount;
var yAmount = amount;
return _OperateLinear(x, y, (xx, yy) => (xx * xAmount) + (yy * yAmount));
}
/// <summary>
/// Interpolates (<paramref name="x"/> , <paramref name="xt"/>) with (<paramref name="y"/> , <paramref name="yt"/>) an <paramref name="amount"/>.
/// If there's more than 8 non zero result values, the 8 most representative values are taken.
/// </summary>
/// <param name="x">The first value operand.</param>
/// <param name="xt">The first tangent operand.</param>
/// <param name="y">The second value operand.</param>
/// <param name="yt">The second tangent operand.</param>
/// <param name="amount">The amount of <paramref name="y"/></param>
/// <returns>A new <see cref="SparseWeight8"/></returns>
public static SparseWeight8 InterpolateCubic(in SparseWeight8 x, in SparseWeight8 xt, in SparseWeight8 y, in SparseWeight8 yt, float amount)
{
var basis = Animations.CurveSampler.CreateHermitePointWeights(amount);
return _OperateCubic(x, xt, y, yt, (xx, xxt, yy, yyt) => (xx * basis.StartPosition) + (yy * basis.EndPosition) + (xxt * basis.StartTangent) + (yyt * basis.EndTangent));
}
public IEnumerable<float> Expand(int count)
{
for (int i = 0; i < count; ++i)
{
yield return GetExpandedAt(i);
}
}
public IEnumerable<(int Index, float Weight)> GetIndexedWeights()
{
yield return (Index0, Weight0);
yield return (Index1, Weight1);
yield return (Index2, Weight2);
yield return (Index3, Weight3);
yield return (Index4, Weight4);
yield return (Index5, Weight5);
yield return (Index6, Weight6);
yield return (Index7, Weight7);
}
public static SparseWeight8 Blend(ReadOnlySpan<SparseWeight8> sparses, ReadOnlySpan<float> weight)
{
var r = default(SparseWeight8);
for (int i = 0; i < sparses.Length; ++i)
{
if (sparses[i].IsWeightless) continue;
r = Add(r, Multiply(sparses[i], weight[i]));
}
return r;
}
public SparseWeight8 GetReducedWeights(int maxWeights)
{
Span<IndexWeight> entries = stackalloc IndexWeight[8];
IndexWeight.CopyTo(this, entries);
IndexWeight.BubbleSortByWeight(entries);
for (int i = maxWeights; i < entries.Length; ++i) entries[i] = default;
var reduced = new SparseWeight8(entries);
var scale = reduced.WeightSum == 0f ? 0f : this.WeightSum / reduced.WeightSum;
return Multiply(reduced, scale);
}
public override string ToString()
{
var sb = new StringBuilder();
int c = this.Count;
for (int i = 0; i < c; ++i)
{
if (sb.Length > 0) sb.Append(" ");
sb.Append(this[i]);
}
return sb.ToString();
}
#endregion
#region code
/// <summary>
/// Performs <paramref name="operationFunc"/> over all the elements of the operands.
/// </summary>
/// <param name="x">The first <see cref="SparseWeight8"/> operand.</param>
/// <param name="y">The second <see cref="SparseWeight8"/> operand.</param>
/// <param name="operationFunc">The operator function to apply to every element.</param>
/// <returns>A new <see cref="SparseWeight8"/>.</returns>
private static SparseWeight8 _OperateLinear(in SparseWeight8 x, in SparseWeight8 y, Func<float, float, float> operationFunc)
{
const int STACKSIZE = 8 * 2;
// prepare buffers in the stack
Span<int> indices = stackalloc int[STACKSIZE];
Span<float> xxx = stackalloc float[STACKSIZE];
Span<float> yyy = stackalloc float[STACKSIZE];
// fill the buffers so all the elements are aligned by column
int offset = 0;
offset = IndexWeight.CopyTo(x, indices, xxx, offset);
offset = IndexWeight.CopyTo(y, indices, yyy, offset);
// perform operation element by element
int r = 0;
Span<IndexWeight> rrr = stackalloc IndexWeight[STACKSIZE];
for (int i = 0; i < offset; ++i)
{
var ww = operationFunc(xxx[i], yyy[i]);
if (ww == 0) continue;
rrr[r++] = new IndexWeight(indices[i], ww);
}
rrr = rrr.Slice(0, r);
if (rrr.Length > 8)
{
IndexWeight.BubbleSortByWeight(rrr);
rrr = rrr.Slice(0, 8);
}
return new SparseWeight8(rrr);
}
/// <summary>
/// Performs <paramref name="operationFunc"/> over all the elements of the operands.
/// </summary>
/// <param name="x">The first <see cref="SparseWeight8"/> operand.</param>
/// <param name="y">The second <see cref="SparseWeight8"/> operand.</param>
/// <param name="z">The third <see cref="SparseWeight8"/> operand.</param>
/// <param name="w">The fourth <see cref="SparseWeight8"/> operand.</param>
/// <param name="operationFunc">The operator function to apply to every element.</param>
/// <returns>A new <see cref="SparseWeight8"/>.</returns>
private static SparseWeight8 _OperateCubic
(
in SparseWeight8 x,
in SparseWeight8 y,
in SparseWeight8 z,
in SparseWeight8 w,
Func<float, float, float, float, float> operationFunc
)
{
const int STACKSIZE = 8 * 4;
// prepare buffers in the stack
Span<int> indices = stackalloc int[STACKSIZE];
Span<float> xxx = stackalloc float[STACKSIZE];
Span<float> yyy = stackalloc float[STACKSIZE];
Span<float> zzz = stackalloc float[STACKSIZE];
Span<float> www = stackalloc float[STACKSIZE];
// fill the buffers so all the elements are aligned by column
int offset = 0;
offset = IndexWeight.CopyTo(x, indices, xxx, offset);
offset = IndexWeight.CopyTo(y, indices, yyy, offset);
offset = IndexWeight.CopyTo(z, indices, zzz, offset);
offset = IndexWeight.CopyTo(w, indices, www, offset);
// perform operation element by element
int r = 0;
Span<IndexWeight> rrr = stackalloc IndexWeight[STACKSIZE];
for (int i = 0; i < offset; ++i)
{
var ww = operationFunc(xxx[i], yyy[i], zzz[i], www[i]);
if (ww == 0) continue;
rrr[r++] = new IndexWeight(indices[i], ww);
}
rrr = rrr.Slice(0, r);
if (rrr.Length > 8)
{
IndexWeight.BubbleSortByWeight(rrr);
rrr = rrr.Slice(0, 8);
}
return new SparseWeight8(rrr);
}
internal IEnumerable<(int Index, float Weight)> GetNonZeroWeights()
{
if (Weight0 != 0) yield return (Index0, Weight0);
if (Weight1 != 0) yield return (Index1, Weight1);
if (Weight2 != 0) yield return (Index2, Weight2);
if (Weight3 != 0) yield return (Index3, Weight3);
if (Weight4 != 0) yield return (Index4, Weight4);
if (Weight5 != 0) yield return (Index5, Weight5);
if (Weight6 != 0) yield return (Index6, Weight6);
if (Weight7 != 0) yield return (Index7, Weight7);
}
private float GetExpandedAt(int idx)
{
if (idx == Index0) return Weight0;
if (idx == Index1) return Weight1;
if (idx == Index2) return Weight2;
if (idx == Index3) return Weight3;
if (idx == Index4) return Weight4;
if (idx == Index5) return Weight5;
if (idx == Index6) return Weight6;
if (idx == Index7) return Weight7;
return 0;
}
private int GetExpandedCount()
{
var c = 0;
if (Weight0 != 0 && c <= Index0) c = Index0 + 1;
if (Weight1 != 0 && c <= Index1) c = Index1 + 1;
if (Weight2 != 0 && c <= Index2) c = Index2 + 1;
if (Weight3 != 0 && c <= Index3) c = Index3 + 1;
if (Weight4 != 0 && c <= Index4) c = Index4 + 1;
if (Weight5 != 0 && c <= Index5) c = Index5 + 1;
if (Weight6 != 0 && c <= Index6) c = Index6 + 1;
if (Weight7 != 0 && c <= Index7) c = Index7 + 1;
return c;
}
/// <summary>
/// Normalizes the current <see cref="SparseWeight8"/> by adding a complementary weight
/// at index <paramref name="complementIndex"/> that resolves <see cref="WeightSum"/> to 1.
/// </summary>
/// <returns>A new <see cref="SparseWeight8"/> with a complementary weight.</returns>
internal SparseWeight8 GetNormalizedWithComplement(int complementIndex)
{
var sum = this.WeightSum;
if (sum >= 1) return this;
Span<IndexWeight> weights = stackalloc IndexWeight[8 + 1];
var offset = IndexWeight.CopyTo(this, weights);
weights[offset++] = new IndexWeight(complementIndex, 1 - sum);
weights = weights.Slice(0, offset);
if (offset > 8)
{
IndexWeight.BubbleSortByWeight(weights);
weights = weights.Slice(0, 8);
}
return new SparseWeight8(weights);
}
internal int _GetMaxIndex()
{
int idx = 0;
if (Weight0 != 0) idx = Math.Max(idx, Index0);
if (Weight1 != 0) idx = Math.Max(idx, Index1);
if (Weight2 != 0) idx = Math.Max(idx, Index2);
if (Weight3 != 0) idx = Math.Max(idx, Index3);
if (Weight4 != 0) idx = Math.Max(idx, Index4);
if (Weight5 != 0) idx = Math.Max(idx, Index5);
if (Weight6 != 0) idx = Math.Max(idx, Index6);
if (Weight7 != 0) idx = Math.Max(idx, Index7);
return idx;
}
#endregion
}
}
504f2a2a4This file is pinned to the selected release. Download the original to open it in your design tool.