|
|
@@ -12,16 +12,16 @@ namespace SharpGLTF.Geometry
|
|
|
/// Represents a vertex buffer, where every vertex attribute is represented as a vector column.
|
|
|
/// </summary>
|
|
|
/// <remarks>
|
|
|
- /// One of the use cases of <see cref="VertexColumns"/> is to bind the different attribute
|
|
|
+ /// One of the use cases of <see cref="VertexBufferColumns"/> is to bind the different attribute
|
|
|
/// columns directly to the <see cref="Schema2.Accessor"/> source feed, which means that
|
|
|
/// if you modify the contents of a column that is binded directly to a model, you're
|
|
|
/// modifying the model's internal data.
|
|
|
/// </remarks>
|
|
|
- public class VertexColumns
|
|
|
+ public class VertexBufferColumns
|
|
|
{
|
|
|
#region Data Columns
|
|
|
|
|
|
-#pragma warning disable CA2227 // Collection properties should be read only
|
|
|
+ #pragma warning disable CA2227 // Collection properties should be read only
|
|
|
|
|
|
public IList<Vector3> Positions { get; set; }
|
|
|
public IList<Vector3> Normals { get; set; }
|
|
|
@@ -30,8 +30,8 @@ namespace SharpGLTF.Geometry
|
|
|
public IList<Vector4> Colors0 { get; set; }
|
|
|
public IList<Vector4> Colors1 { get; set; }
|
|
|
|
|
|
- public IList<Vector2> Textures0 { get; set; }
|
|
|
- public IList<Vector2> Textures1 { get; set; }
|
|
|
+ public IList<Vector2> TexCoords0 { get; set; }
|
|
|
+ public IList<Vector2> TexCoords1 { get; set; }
|
|
|
|
|
|
public IList<Vector4> Joints0 { get; set; }
|
|
|
public IList<Vector4> Joints1 { get; set; }
|
|
|
@@ -39,105 +39,99 @@ namespace SharpGLTF.Geometry
|
|
|
public IList<Vector4> Weights0 { get; set; }
|
|
|
public IList<Vector4> Weights1 { get; set; }
|
|
|
|
|
|
- public class MorphTarget
|
|
|
- {
|
|
|
- public IList<Vector3> Positions { get; set; }
|
|
|
- public IList<Vector3> Normals { get; set; }
|
|
|
- public IList<Vector3> Tangents { get; set; }
|
|
|
- }
|
|
|
-
|
|
|
-#pragma warning restore CA2227 // Collection properties should be read only
|
|
|
+ #pragma warning restore CA2227 // Collection properties should be read only
|
|
|
|
|
|
- private List<MorphTarget> _MorphTargets;
|
|
|
+ private List<MorphTargetColumns> _MorphTargets;
|
|
|
|
|
|
- private static readonly IReadOnlyList<MorphTarget> _EmptyMorphTargets = new MorphTarget[0];
|
|
|
-
|
|
|
- public IReadOnlyList<MorphTarget> MorphTargets => _MorphTargets == null ? _EmptyMorphTargets : _MorphTargets;
|
|
|
+ public IReadOnlyList<MorphTargetColumns> MorphTargets => _MorphTargets == null ? (IReadOnlyList<MorphTargetColumns>)Array.Empty<MorphTargetColumns>() : _MorphTargets;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region API
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// Creates an internal copy of the <see cref="Positions"/> columns,
|
|
|
- /// which ensures the column data is not shared with other objects
|
|
|
- /// </summary>
|
|
|
- public void IsolatePositions()
|
|
|
- {
|
|
|
- if (this.Positions == null) return;
|
|
|
-
|
|
|
- var newPositions = new Vector3[this.Positions.Count];
|
|
|
-
|
|
|
- this.Positions.CopyTo(newPositions, 0);
|
|
|
-
|
|
|
- this.Positions = newPositions;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// Creates an internal copy of the <see cref="Normals"/> columns,
|
|
|
- /// which ensures the column data is not shared with other objects
|
|
|
- /// </summary>
|
|
|
- public void IsolateNormals()
|
|
|
+ private static IList<T> _IsolateColumn<T>(IList<T> column)
|
|
|
{
|
|
|
- if (this.Normals == null) return;
|
|
|
+ if (column == null) return null;
|
|
|
|
|
|
- var newNormals = new Vector3[this.Normals.Count];
|
|
|
+ var newColumn = new T[column.Count];
|
|
|
|
|
|
- this.Normals.CopyTo(newNormals, 0);
|
|
|
+ column.CopyTo(newColumn, 0);
|
|
|
|
|
|
- this.Normals = newNormals;
|
|
|
+ return newColumn;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
- /// Creates an internal copy of the <see cref="Tangents"/> columns,
|
|
|
- /// which ensures the column data is not shared with other objects
|
|
|
+ /// Performs an in-place copy of the contents of every column,
|
|
|
+ /// which guarantees that the columns of this <see cref="VertexBufferColumns"/>
|
|
|
+ /// are not shared by any other object and can be modified safely.
|
|
|
/// </summary>
|
|
|
- public void IsolateTangents()
|
|
|
+ public void IsolateColumns()
|
|
|
{
|
|
|
- if (this.Tangents == null) return;
|
|
|
+ this.Positions = _IsolateColumn(this.Positions);
|
|
|
+ this.Normals = _IsolateColumn(this.Normals);
|
|
|
+ this.Tangents = _IsolateColumn(this.Tangents);
|
|
|
|
|
|
- var newTangents = new Vector4[this.Tangents.Count];
|
|
|
+ this.Colors0 = _IsolateColumn(this.Colors0);
|
|
|
+ this.Colors1 = _IsolateColumn(this.Colors1);
|
|
|
|
|
|
- this.Tangents.CopyTo(newTangents, 0);
|
|
|
+ this.TexCoords0 = _IsolateColumn(this.TexCoords0);
|
|
|
+ this.TexCoords1 = _IsolateColumn(this.TexCoords1);
|
|
|
|
|
|
- this.Tangents = newTangents;
|
|
|
- }
|
|
|
+ this.Joints0 = _IsolateColumn(this.Joints0);
|
|
|
+ this.Joints1 = _IsolateColumn(this.Joints1);
|
|
|
|
|
|
- public void ApplyNormals(IReadOnlyDictionary<Vector3, Vector3> normalsMap)
|
|
|
- {
|
|
|
- IsolateNormals();
|
|
|
+ this.Weights0 = _IsolateColumn(this.Weights0);
|
|
|
+ this.Weights1 = _IsolateColumn(this.Weights1);
|
|
|
|
|
|
- if (this.Normals == null) this.Normals = new Vector3[Positions.Count];
|
|
|
+ if (_MorphTargets == null) return;
|
|
|
|
|
|
- for (int i = 0; i < Normals.Count; ++i)
|
|
|
- {
|
|
|
- if (normalsMap.TryGetValue(this.Positions[i], out Vector3 nrm))
|
|
|
- {
|
|
|
- this.Normals[i] = nrm;
|
|
|
- }
|
|
|
- }
|
|
|
+ foreach (var mt in _MorphTargets) mt.IsolateColumns();
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Applies a transform to the columns of this <see cref="VertexBufferColumns"/>
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="transform">A Mesh transformer object</param>
|
|
|
+ /// <remarks>
|
|
|
+ /// This is a one time operation.
|
|
|
+ /// Once it's applied, skinning and morphing columns are removed, since they're baked
|
|
|
+ /// into the position, normal and tangent columns.
|
|
|
+ /// </remarks>
|
|
|
public void ApplyTransform(Transforms.ITransform transform)
|
|
|
{
|
|
|
- // since positions, normals and tangents might be binded directly to the model's buffer data,
|
|
|
- // and we don't want to modify the source data, we create a copy of the columns.
|
|
|
-
|
|
|
- IsolatePositions();
|
|
|
- IsolateNormals();
|
|
|
- IsolateTangents();
|
|
|
+ Guard.NotNull(this.Positions, nameof(this.Positions), "Missing Positions column");
|
|
|
+ if (this.Normals != null) Guard.IsTrue(this.Positions.Count == this.Normals.Count, nameof(this.Normals), "Column length mismatch.");
|
|
|
+ if (this.Tangents != null) Guard.IsTrue(this.Positions.Count == this.Tangents.Count, nameof(this.Tangents), "Column length mismatch.");
|
|
|
+ if (this.Colors0 != null) Guard.IsTrue(this.Positions.Count == this.Colors0.Count, nameof(this.Colors0), "Column length mismatch.");
|
|
|
+ if (this.Colors1 != null) Guard.IsTrue(this.Positions.Count == this.Colors1.Count, nameof(this.Colors1), "Column length mismatch.");
|
|
|
+ if (this.TexCoords0 != null) Guard.IsTrue(this.Positions.Count == this.TexCoords0.Count, nameof(this.TexCoords0), "Column length mismatch.");
|
|
|
+ if (this.TexCoords1 != null) Guard.IsTrue(this.Positions.Count == this.TexCoords1.Count, nameof(this.TexCoords1), "Column length mismatch.");
|
|
|
+ if (this.Joints0 != null) Guard.IsTrue(this.Positions.Count == this.Joints0.Count, nameof(this.Joints0), "Column length mismatch.");
|
|
|
+ if (this.Joints1 != null) Guard.IsTrue(this.Positions.Count == this.Joints1.Count, nameof(this.Joints1), "Column length mismatch.");
|
|
|
+ if (this.Weights0 != null) Guard.IsTrue(this.Positions.Count == this.Weights0.Count, nameof(this.Weights0), "Column length mismatch.");
|
|
|
+ if (this.Weights1 != null) Guard.IsTrue(this.Positions.Count == this.Weights1.Count, nameof(this.Weights1), "Column length mismatch.");
|
|
|
+
|
|
|
+ // since the attributes we want to overwrite might be binded directly to the model's buffer
|
|
|
+ // data, and we don't want to modify the source data, we isolate the columns to be overwritten.
|
|
|
+
|
|
|
+ this.Positions = _IsolateColumn(this.Positions);
|
|
|
+ this.Normals = _IsolateColumn(this.Normals);
|
|
|
+ this.Tangents = _IsolateColumn(this.Tangents);
|
|
|
+ this.Colors0 = _IsolateColumn(this.Colors0);
|
|
|
|
|
|
// prepare morph data, if available
|
|
|
|
|
|
Vector3[] morphPositions = null;
|
|
|
Vector3[] morphNormals = null;
|
|
|
Vector3[] morphTangents = null;
|
|
|
+ Vector4[] morphColors0 = null;
|
|
|
|
|
|
if (_MorphTargets != null)
|
|
|
{
|
|
|
if (_MorphTargets.All(item => item.Positions != null)) morphPositions = new Vector3[this.MorphTargets.Count];
|
|
|
if (_MorphTargets.All(item => item.Normals != null)) morphNormals = new Vector3[this.MorphTargets.Count];
|
|
|
if (_MorphTargets.All(item => item.Tangents != null)) morphTangents = new Vector3[this.MorphTargets.Count];
|
|
|
+ if (_MorphTargets.All(item => item.Colors0 != null)) morphColors0 = new Vector4[this.MorphTargets.Count];
|
|
|
}
|
|
|
|
|
|
// prepare skinning data, if available
|
|
|
@@ -145,7 +139,9 @@ namespace SharpGLTF.Geometry
|
|
|
var jw0 = Joints0 != null && Weights0 != null;
|
|
|
var jw1 = Joints1 != null && Weights1 != null;
|
|
|
|
|
|
- var jwjwjwjw = new (int, float)[(jw0 ? 4 : 0) + (jw1 ? 4 : 0)];
|
|
|
+ var skinning = new (int, float)[(jw0 ? 4 : 0) + (jw1 ? 4 : 0)];
|
|
|
+
|
|
|
+ // loop over every vertex
|
|
|
|
|
|
int vcount = Positions.Count;
|
|
|
|
|
|
@@ -155,38 +151,44 @@ namespace SharpGLTF.Geometry
|
|
|
{
|
|
|
var j = Joints0[i];
|
|
|
var w = Weights0[i];
|
|
|
- jwjwjwjw[0] = ((int)j.X, w.X);
|
|
|
- jwjwjwjw[1] = ((int)j.Y, w.Y);
|
|
|
- jwjwjwjw[2] = ((int)j.Z, w.Z);
|
|
|
- jwjwjwjw[3] = ((int)j.W, w.W);
|
|
|
+ skinning[0] = ((int)j.X, w.X);
|
|
|
+ skinning[1] = ((int)j.Y, w.Y);
|
|
|
+ skinning[2] = ((int)j.Z, w.Z);
|
|
|
+ skinning[3] = ((int)j.W, w.W);
|
|
|
}
|
|
|
|
|
|
if (jw1)
|
|
|
{
|
|
|
var j = Joints1[i];
|
|
|
var w = Weights1[i];
|
|
|
- jwjwjwjw[4] = ((int)j.X, w.X);
|
|
|
- jwjwjwjw[5] = ((int)j.Y, w.Y);
|
|
|
- jwjwjwjw[6] = ((int)j.Z, w.Z);
|
|
|
- jwjwjwjw[7] = ((int)j.W, w.W);
|
|
|
+ skinning[4] = ((int)j.X, w.X);
|
|
|
+ skinning[5] = ((int)j.Y, w.Y);
|
|
|
+ skinning[6] = ((int)j.Z, w.Z);
|
|
|
+ skinning[7] = ((int)j.W, w.W);
|
|
|
}
|
|
|
|
|
|
if (Positions != null)
|
|
|
{
|
|
|
_FillMorphData(morphPositions, vc => vc.Positions[i]);
|
|
|
- Positions[i] = transform.TransformPosition(Positions[i], morphPositions, jwjwjwjw);
|
|
|
+ Positions[i] = transform.TransformPosition(Positions[i], morphPositions, skinning);
|
|
|
}
|
|
|
|
|
|
if (Normals != null)
|
|
|
{
|
|
|
_FillMorphData(morphNormals, vc => vc.Normals[i]);
|
|
|
- Normals[i] = transform.TransformNormal(Normals[i], morphNormals, jwjwjwjw);
|
|
|
+ Normals[i] = transform.TransformNormal(Normals[i], morphNormals, skinning);
|
|
|
}
|
|
|
|
|
|
if (Tangents != null)
|
|
|
{
|
|
|
_FillMorphData(morphTangents, vc => vc.Tangents[i]);
|
|
|
- Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, jwjwjwjw);
|
|
|
+ Tangents[i] = transform.TransformTangent(Tangents[i], morphTangents, skinning);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Colors0 != null)
|
|
|
+ {
|
|
|
+ _FillMorphData(morphColors0, vc => vc.Colors0[i]);
|
|
|
+ Colors0[i] = transform.MorphColors(Colors0[i], morphColors0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -201,7 +203,17 @@ namespace SharpGLTF.Geometry
|
|
|
Weights1 = null;
|
|
|
}
|
|
|
|
|
|
- private void _FillMorphData(Vector3[] array, Func<MorphTarget, Vector3> selector)
|
|
|
+ private void _FillMorphData(Vector3[] array, Func<MorphTargetColumns, Vector3> selector)
|
|
|
+ {
|
|
|
+ if (array == null) return;
|
|
|
+
|
|
|
+ for (int i = 0; i < this._MorphTargets.Count; ++i)
|
|
|
+ {
|
|
|
+ array[i] = selector(this._MorphTargets[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void _FillMorphData(Vector4[] array, Func<MorphTargetColumns, Vector4> selector)
|
|
|
{
|
|
|
if (array == null) return;
|
|
|
|
|
|
@@ -231,8 +243,8 @@ namespace SharpGLTF.Geometry
|
|
|
if (Colors0 != null && cctt.MaxColors > 0) cctt.SetColor(0, Colors0[index]);
|
|
|
if (Colors1 != null && cctt.MaxColors > 1) cctt.SetColor(1, Colors1[index]);
|
|
|
|
|
|
- if (Textures0 != null && cctt.MaxTextCoords > 0) cctt.SetTexCoord(0, Textures0[index]);
|
|
|
- if (Textures1 != null && cctt.MaxTextCoords > 1) cctt.SetTexCoord(1, Textures1[index]);
|
|
|
+ if (TexCoords0 != null && cctt.MaxTextCoords > 0) cctt.SetTexCoord(0, TexCoords0[index]);
|
|
|
+ if (TexCoords1 != null && cctt.MaxTextCoords > 1) cctt.SetTexCoord(1, TexCoords1[index]);
|
|
|
|
|
|
return cctt;
|
|
|
}
|
|
|
@@ -291,10 +303,10 @@ namespace SharpGLTF.Geometry
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- public MorphTarget AddMorphTarget()
|
|
|
+ public MorphTargetColumns AddMorphTarget()
|
|
|
{
|
|
|
- if (_MorphTargets == null) _MorphTargets = new List<MorphTarget>();
|
|
|
- var mt = new MorphTarget();
|
|
|
+ if (_MorphTargets == null) _MorphTargets = new List<MorphTargetColumns>();
|
|
|
+ var mt = new MorphTargetColumns();
|
|
|
_MorphTargets.Add(mt);
|
|
|
|
|
|
return mt;
|