using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Text; using SharpGLTF.Geometry.VertexTypes; namespace SharpGLTF.Geometry { public interface IPrimitiveMorphTargetReader { /// /// Gets the collection of vertex indices that have morph target deltas. /// /// A collection of vertex indices. IReadOnlyCollection GetTargetIndices(); /// /// Gets the vertex for the given morphed by the current morph target (if any). /// /// The index of the vertex. /// If the given index has a morphed vertex, it will return it, else ir will return the base vertex. IVertexBuilder GetVertex(int vertexIndex); /// /// Gets the of a given vertex for a given morph target. /// /// The index of the vertex. /// A Vertex delta (Morphed vertex minus base vertex). VertexBuilder GetVertexDelta(int vertexIndex); } /// /// Represents the vertex deltas of a specific morph target. /// /// /// The vertex fragment type with Position, Normal and Tangent. class PrimitiveMorphTargetBuilder : IPrimitiveMorphTargetReader where TvG : struct, IVertexGeometry where TvM : struct, IVertexMaterial { #region lifecycle internal PrimitiveMorphTargetBuilder(Func> baseVertexFunc) { this._BaseVertexFunc = baseVertexFunc; this._MorphVertices = new Dictionary>(); } internal PrimitiveMorphTargetBuilder(Func> baseVertexFunc, PrimitiveMorphTargetBuilder other) { this._BaseVertexFunc = baseVertexFunc; this._MorphVertices = new Dictionary>(other._MorphVertices); } #endregion #region data private readonly Func> _BaseVertexFunc; private readonly Dictionary> _MorphVertices; #endregion #region API public IReadOnlyCollection GetTargetIndices() { return _MorphVertices.Keys; } public VertexBuilder GetVertexDelta(int vertexIndex) { if (_MorphVertices.TryGetValue(vertexIndex, out VertexBuilder value)) { var vertex = _BaseVertexFunc(vertexIndex); return new VertexBuilder( value.Geometry.Subtract(vertex.Geometry), value.Material.Subtract(vertex.Material)); } return default; } public void SetVertexDelta(int vertexIndex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta) { if (object.Equals(geometryDelta, default(VertexGeometryDelta))) { _RemoveVertex(vertexIndex); return; } var vertex = _BaseVertexFunc(vertexIndex); vertex.Geometry.Add(geometryDelta); if (typeof(TvM) != typeof(VertexEmpty)) vertex.Material.Add(materialDelta); _SetVertex(vertexIndex, vertex); } IVertexBuilder IPrimitiveMorphTargetReader.GetVertex(int vertexIndex) { return _MorphVertices.TryGetValue(vertexIndex, out VertexBuilder value) ? value : _BaseVertexFunc(vertexIndex); } public VertexBuilder GetVertex(int vertexIndex) { return _MorphVertices.TryGetValue(vertexIndex, out VertexBuilder value) ? value : _BaseVertexFunc(vertexIndex); } public void SetVertex(int vertexIndex, VertexBuilder vertex) { if (object.Equals(vertex, _BaseVertexFunc(vertexIndex))) { _RemoveVertex(vertexIndex); return; } _SetVertex(vertexIndex, vertex); } private void _SetVertex(int vertexIndex, VertexBuilder vertex) { _MorphVertices[vertexIndex] = vertex; } private void _RemoveVertex(int vertexIndex) { _MorphVertices.Remove(vertexIndex); } #endregion #region internals internal void TransformVertices(Func, VertexBuilder> vertexFunc) { foreach (var vidx in _MorphVertices.Keys) { var g = GetVertex(vidx); g = vertexFunc(g); SetVertex(vidx, g); } } internal void SetMorphTargets(IPrimitiveMorphTargetReader other, IReadOnlyDictionary vertexMap, Func> vertexFunc) { Guard.NotNull(vertexFunc, nameof(vertexFunc)); var indices = other.GetTargetIndices(); foreach (var srcVidx in indices) { var g = vertexFunc(other.GetVertex(srcVidx).GetGeometry()); var dstVidx = srcVidx; if (vertexMap != null) { if (!vertexMap.TryGetValue(srcVidx, out dstVidx)) dstVidx = -1; } if (dstVidx >= 0) this.SetVertex(dstVidx, g); } } #endregion } /// /// Represents the vertex deltas of a specific morph target. /// /// public interface IMorphTargetBuilder { IReadOnlyCollection Positions { get; } IReadOnlyCollection Vertices { get; } IReadOnlyList GetVertices(Vector3 position); /// /// Sets an absolute morph target. /// /// The base mesh vertex to morph. /// The morphed vertex. void SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex); /// /// Sets an absolute morph target. /// /// The base mesh vertex to morph. /// The morphed vertex. /// The morphed vertex material. void SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial); /// /// Sets a relative morph target /// /// The base mesh vertex to morph. /// The offset from to morph. void SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta); /// /// Sets a relative morph target /// /// The base mesh vertex to morph. /// The offset from to morph. /// The offset from material to morph. void SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta); /// /// Sets a relative morph target to all base mesh vertices matching . /// /// The base vertex position. /// The offset to apply to each matching vertex found. void SetVertexDelta(Vector3 meshPosition, VertexGeometryDelta geometryDelta); /// /// Sets a relative morph target to all base mesh vertices matching . /// /// The base vertex position. /// The offset to apply to each matching vertex found. /// The offset to apply to each matching vertex material found. void SetVertexDelta(Vector3 meshPosition, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta); } /// /// Represents the vertex deltas of a specific morph target. /// /// /// The material type used by the base mesh. /// The vertex geometry type used by the base mesh. /// The vertex skinning type used by the base mesh. /// The vertex material type used by the base mesh. /// /// Morph targets are stored separately on each , /// so connecting vertices between two primitives might be duplicated. This means that when we set /// a displaced vertex, we must be sure we do so for all instances we can find. /// public sealed class MorphTargetBuilder : IMorphTargetBuilder where TvG : struct, IVertexGeometry where TvM : struct, IVertexMaterial where TvS : struct, IVertexSkinning { #region lifecycle internal MorphTargetBuilder(MeshBuilder mesh, int morphTargetIndex) { _Mesh = mesh; _MorphTargetIndex = morphTargetIndex; foreach (var prim in _Mesh.Primitives) { for (int vidx = 0; vidx < prim.Vertices.Count; ++vidx) { var key = prim.Vertices[vidx].Geometry; if (!_Vertices.TryGetValue(key, out List<(PrimitiveBuilder, int)> val)) { _Vertices[key] = val = new List<(PrimitiveBuilder, int)>(); } val.Add((prim, vidx)); if (!_Positions.TryGetValue(key.GetPosition(), out List geos)) { _Positions[key.GetPosition()] = geos = new List(); } geos.Add(key); } } } #endregion #region data private readonly MeshBuilder _Mesh; private readonly int _MorphTargetIndex; private readonly Dictionary, int)>> _Vertices = new Dictionary, int)>>(); private readonly Dictionary> _Positions = new Dictionary>(); #endregion #region properties public IReadOnlyCollection Positions => _Positions.Keys; public IReadOnlyCollection Vertices => _Vertices.Keys; #endregion #region API public IReadOnlyList GetVertices(Vector3 position) { return _Positions.TryGetValue(position, out List geos) ? (IReadOnlyList)geos : Array.Empty(); } public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta) { if (_Positions.TryGetValue(key, out List geos)) { foreach (var g in geos) SetVertexDelta(g, geometryDelta, VertexMaterialDelta.Zero); } } public void SetVertexDelta(Vector3 key, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta) { if (_Positions.TryGetValue(key, out List geos)) { foreach (var g in geos) SetVertexDelta(g, geometryDelta, materialDelta); } } public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta geometryDelta) { if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder, int)> val)) { foreach (var entry in val) { entry.Item1 ._UseMorphTarget(_MorphTargetIndex) .SetVertexDelta(entry.Item2, geometryDelta, VertexMaterialDelta.Zero); } } } public void SetVertexDelta(TvG meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta) { if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder, int)> val)) { foreach (var entry in val) { entry.Item1 ._UseMorphTarget(_MorphTargetIndex) .SetVertexDelta(entry.Item2, geometryDelta, materialDelta); } } } public void SetVertex(TvG meshVertex, VertexBuilder morphVertex) { if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder, int)> val)) { foreach (var entry in val) { entry.Item1 ._UseMorphTarget(_MorphTargetIndex) .SetVertex(entry.Item2, morphVertex); } } } public void SetVertex(TvG meshVertex, TvG morphVertex) { if (_Vertices.TryGetValue(meshVertex, out List<(PrimitiveBuilder, int)> val)) { foreach (var entry in val) { var vertexMaterial = entry.Item1.Vertices[entry.Item2].Material; entry.Item1 ._UseMorphTarget(_MorphTargetIndex) .SetVertex(entry.Item2, new VertexBuilder(morphVertex, vertexMaterial)); } } } #endregion #region IMorphTargetBuilder IReadOnlyCollection IMorphTargetBuilder.Vertices => (IReadOnlyList)(IReadOnlyCollection)_Vertices.Keys; IReadOnlyList IMorphTargetBuilder.GetVertices(Vector3 position) { return _Positions.TryGetValue(position, out List geos) ? (IReadOnlyList)geos : Array.Empty(); } void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex) { SetVertex(meshVertex.ConvertToGeometry(), new VertexBuilder(morphVertex.ConvertToGeometry(), default(VertexEmpty).ConvertToMaterial())); } void IMorphTargetBuilder.SetVertex(IVertexGeometry meshVertex, IVertexGeometry morphVertex, IVertexMaterial morphMaterial) { SetVertex(meshVertex.ConvertToGeometry(), new VertexBuilder(morphVertex.ConvertToGeometry(), morphMaterial.ConvertToMaterial())); } void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta) { SetVertexDelta(meshVertex.ConvertToGeometry(), geometryDelta, VertexMaterialDelta.Zero); } void IMorphTargetBuilder.SetVertexDelta(IVertexGeometry meshVertex, VertexGeometryDelta geometryDelta, VertexMaterialDelta materialDelta) { SetVertexDelta(meshVertex.ConvertToGeometry(), geometryDelta, materialDelta); } #endregion } }