From 5d029723c7919d3eaa670b282e46d63bfa032b55 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Feb 2018 16:37:23 -0500 Subject: [PATCH 001/225] PerVertexTransform variant for a submesh that has been appended to a larger mesh --- mesh/MeshTransforms.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index 6f4f744d..b11f71be 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -295,5 +295,22 @@ public static void PerVertexTransform(IDeformableMesh mesh, IEnumerable ver } } + + /// + /// Apply TransformF to subset of mesh vertices defined by MapV[vertices] + /// + public static void PerVertexTransform(IDeformableMesh targetMesh, IDeformableMesh sourceMesh, int[] mapV, Func TransformF) + { + foreach (int vid in sourceMesh.VertexIndices()) { + int map_vid = mapV[vid]; + if (targetMesh.IsVertex(map_vid)) { + Vector3d newPos = TransformF(targetMesh.GetVertex(map_vid), vid, map_vid); + targetMesh.SetVertex(map_vid, newPos); + } + } + } + + + } } From 6733b70193f2f3fab323d55e2432087b7ff58ac3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Feb 2018 17:29:23 -0500 Subject: [PATCH 002/225] add quaternionf opreator== --- math/Quaternionf.cs | 47 +++++++++++++++++++++++++++++++++++++++++- mesh/MeshTransforms.cs | 4 +++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/math/Quaternionf.cs b/math/Quaternionf.cs index a36e06e3..5f2db3b9 100644 --- a/math/Quaternionf.cs +++ b/math/Quaternionf.cs @@ -8,7 +8,7 @@ namespace g3 { // mostly ported from WildMagic5 Wm5Quaternion, from geometrictools.com - public struct Quaternionf + public struct Quaternionf : IComparable, IEquatable { // note: in Wm5 version, this is a 4-element array stored in order (w,x,y,z). public float x, y, z, w; @@ -311,6 +311,51 @@ public void SetFromRotationMatrix(Matrix3f rot) + public static bool operator ==(Quaternionf a, Quaternionf b) + { + return (a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w); + } + public static bool operator !=(Quaternionf a, Quaternionf b) + { + return (a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w); + } + public override bool Equals(object obj) + { + return this == (Quaternionf)obj; + } + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hash = (int)2166136261; + // Suitable nullity checks etc, of course :) + hash = (hash * 16777619) ^ x.GetHashCode(); + hash = (hash * 16777619) ^ y.GetHashCode(); + hash = (hash * 16777619) ^ z.GetHashCode(); + hash = (hash * 16777619) ^ w.GetHashCode(); + return hash; + } + } + public int CompareTo(Quaternionf other) + { + if (x != other.x) + return x < other.x ? -1 : 1; + else if (y != other.y) + return y < other.y ? -1 : 1; + else if (z != other.z) + return z < other.z ? -1 : 1; + else if (w != other.w) + return w < other.w ? -1 : 1; + return 0; + } + public bool Equals(Quaternionf other) + { + return (x == other.x && y == other.y && z == other.z && w == other.w); + } + + + + public bool EpsilonEqual(Quaternionf q2, float epsilon) { return (float)Math.Abs(x - q2.x) <= epsilon && diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index b11f71be..e49f4546 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -83,7 +83,7 @@ public static void Scale(IDeformableMesh mesh, double s) Scale(mesh, s, s, s); } - + ///Map mesh *into* local coordinates of Frame public static void ToFrame(IDeformableMesh mesh, Frame3f f) { int NV = mesh.MaxVertexID; @@ -101,6 +101,8 @@ public static void ToFrame(IDeformableMesh mesh, Frame3f f) } } } + + /// Map mesh *from* local frame coordinates into "world" coordinates public static void FromFrame(IDeformableMesh mesh, Frame3f f) { int NV = mesh.MaxVertexID; From 3cb31d542a6d2db0e6791f06fc4bb0f4560e7bd0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 9 Feb 2018 10:40:52 -0500 Subject: [PATCH 003/225] useful fns --- mesh/EdgeLoop.cs | 7 +++++++ mesh/EdgeSpan.cs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/mesh/EdgeLoop.cs b/mesh/EdgeLoop.cs index 88abbfb4..43d8928c 100644 --- a/mesh/EdgeLoop.cs +++ b/mesh/EdgeLoop.cs @@ -127,6 +127,13 @@ public AxisAlignedBox3d GetBounds() } + public DCurve3 ToCurve() + { + DCurve3 curve = MeshUtil.ExtractLoopV(Mesh, Vertices); + curve.Closed = true; + return curve; + } + /// /// if this is a border edge-loop, we can check that it is oriented correctly, and diff --git a/mesh/EdgeSpan.cs b/mesh/EdgeSpan.cs index 567c07ba..df7201f9 100644 --- a/mesh/EdgeSpan.cs +++ b/mesh/EdgeSpan.cs @@ -84,6 +84,14 @@ public AxisAlignedBox3d GetBounds() } + public DCurve3 ToCurve() + { + DCurve3 curve = MeshUtil.ExtractLoopV(Mesh, Vertices); + curve.Closed = true; + return curve; + } + + public bool IsInternalSpan() { int NV = Vertices.Length; From 3a98c9d10196f06c6e907775c608b79f14d78630 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Feb 2018 13:21:42 -0500 Subject: [PATCH 004/225] added box3d.Contains functions --- math/AxisAlignedBox3d.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/math/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs index 8644f1cd..635c227a 100644 --- a/math/AxisAlignedBox3d.cs +++ b/math/AxisAlignedBox3d.cs @@ -192,6 +192,19 @@ public bool Contains(Vector3d v) { return (Min.x <= v.x) && (Min.y <= v.y) && (Min.z <= v.z) && (Max.x >= v.x) && (Max.y >= v.y) && (Max.z >= v.z); } + public bool Contains(ref Vector3d v) { + return (Min.x <= v.x) && (Min.y <= v.y) && (Min.z <= v.z) + && (Max.x >= v.x) && (Max.y >= v.y) && (Max.z >= v.z); + } + + public bool Contains(AxisAlignedBox3d box2) { + return Contains(ref box2.Min) && Contains(ref box2.Max); + } + public bool Contains(ref AxisAlignedBox3d box2) { + return Contains(ref box2.Min) && Contains(ref box2.Max); + } + + public bool Intersects(AxisAlignedBox3d box) { return !((box.Max.x <= Min.x) || (box.Min.x >= Max.x) || (box.Max.y <= Min.y) || (box.Min.y >= Max.y) From 618f5127ab2a0e7e67fcb080646cb80970bb76a4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Feb 2018 13:44:50 -0500 Subject: [PATCH 005/225] delete-tris utilities --- mesh/MeshEditor.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 0d4c1a88..a0079d62 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -260,10 +260,10 @@ public virtual int[] StitchSpan(int[] vspan1, int[] vspan2, int group_id = -1) // [TODO] cannot back-out this operation right now // // Remove list of triangles. Values of triangles[] set to InvalidID are ignored. - public bool RemoveTriangles(int[] triangles, bool bRemoveIsolatedVerts) + public bool RemoveTriangles(IList triangles, bool bRemoveIsolatedVerts) { bool bAllOK = true; - for (int i = 0; i < triangles.Length; ++i ) { + for (int i = 0; i < triangles.Count; ++i ) { if (triangles[i] == DMesh3.InvalidID) continue; @@ -309,8 +309,14 @@ public bool RemoveTriangles(Func selectorF, bool bRemoveIsolatedVerts) return bAllOK; } - - + public static bool RemoveTriangles(DMesh3 Mesh, IList triangles, bool bRemoveIsolatedVerts = true) { + MeshEditor editor = new MeshEditor(Mesh); + return editor.RemoveTriangles(triangles, bRemoveIsolatedVerts); + } + public static bool RemoveTriangles(DMesh3 Mesh, IEnumerable triangles, bool bRemoveIsolatedVerts = true) { + MeshEditor editor = new MeshEditor(Mesh); + return editor.RemoveTriangles(triangles, bRemoveIsolatedVerts); + } From 9d6d680d5381bffc6b834459fc937043f7626774 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 12 Feb 2018 09:48:57 -0500 Subject: [PATCH 006/225] added DSubmesh3Set, for computing set of submeshes --- core/gParallel.cs | 3 +- geometry3Sharp.csproj | 1 + mesh/DSubmesh3Set.cs | 93 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 mesh/DSubmesh3Set.cs diff --git a/core/gParallel.cs b/core/gParallel.cs index 23f02c61..827ef12e 100644 --- a/core/gParallel.cs +++ b/core/gParallel.cs @@ -42,8 +42,9 @@ public static void Evaluate(params Action[] funcs) /// - /// Process indices [iStart,iEnd], inclusive, by passing sub-intervals [start,end] to blockF. + /// Process indices [iStart,iEnd] *inclusive* by passing sub-intervals [start,end] to blockF. /// Blocksize is automatically determind unless you specify one. + /// Iterate over [start,end] *inclusive* in each block /// public static void BlockStartEnd(int iStart, int iEnd, Action blockF, int iBlockSize = -1, bool bDisableParallel = false ) { diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 76901f63..64ccd9c5 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -145,6 +145,7 @@ + diff --git a/mesh/DSubmesh3Set.cs b/mesh/DSubmesh3Set.cs new file mode 100644 index 00000000..fd845f3b --- /dev/null +++ b/mesh/DSubmesh3Set.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + + +namespace g3 +{ + + /// + /// A set of submeshes of a base mesh. You provide a set of keys, and a Func + /// that returns the triangle index list for a given key. The set of DSubmesh3 + /// objects are computed on construction. + /// + public class DSubmesh3Set : IEnumerable + { + public DMesh3 Mesh; + + public IEnumerable TriangleSetKeys; + public Func> TriangleSetF; + + // outputs + + /// List of computed submeshes + public List Submeshes; + + /// Mapping from keys to submeshes + public Dictionary KeyToMesh; + + + /// + /// Construct submesh set from given keys and key-to-indices Func + /// + public DSubmesh3Set(DMesh3 mesh, IEnumerable keys, Func> indexSetsF) + { + Mesh = mesh; + TriangleSetKeys = keys; + TriangleSetF = indexSetsF; + + ComputeSubMeshes(); + } + + + /// + /// Construct submesh set for an already-computed MeshConnectedComponents instance + /// + public DSubmesh3Set(DMesh3 mesh, MeshConnectedComponents components) + { + Mesh = mesh; + + TriangleSetF = (idx) => { + return components.Components[(int)idx].Indices; + }; + List keys = new List(); + for (int k = 0; k < components.Count; ++k) + keys.Add(k); + TriangleSetKeys = keys; + + ComputeSubMeshes(); + } + + + public IEnumerator GetEnumerator() { + return Submeshes.GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() { + return Submeshes.GetEnumerator(); + } + + + + virtual protected void ComputeSubMeshes() + { + Submeshes = new List(); + KeyToMesh = new Dictionary(); + + SpinLock data_lock = new SpinLock(); + + gParallel.ForEach(TriangleSetKeys, (obj) => { + DSubmesh3 submesh = new DSubmesh3(Mesh, TriangleSetF(obj), 0); + + bool taken = false; + data_lock.Enter(ref taken); + Submeshes.Add(submesh); + KeyToMesh[obj] = submesh; + data_lock.Exit(); + }); + } + + } +} From b45313f63812dda53a6929f848e04e2dac4d96ec Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 12 Feb 2018 10:29:53 -0500 Subject: [PATCH 007/225] add support for other rootfinding modes --- mesh_generators/MarchingCubes.cs | 95 +++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/mesh_generators/MarchingCubes.cs b/mesh_generators/MarchingCubes.cs index de6bb5f1..316884d5 100644 --- a/mesh_generators/MarchingCubes.cs +++ b/mesh_generators/MarchingCubes.cs @@ -19,28 +19,48 @@ namespace g3 /// public class MarchingCubes { - // this is the function we will evaluate + /// + /// this is the function we will evaluate + /// public ImplicitFunction3d Implicit; - // mesh surface will be at this isovalue. Normally 0 unless you want - // offset surface or field is not a distance-field. + /// + /// mesh surface will be at this isovalue. Normally 0 unless you want + /// offset surface or field is not a distance-field. + /// public double IsoValue = 0; - // bounding-box we will mesh inside of. We use the min-corner and - // the width/height/depth, but do not clamp vertices to stay within max-corner, - // we may spill one cell over + /// bounding-box we will mesh inside of. We use the min-corner and + /// the width/height/depth, but do not clamp vertices to stay within max-corner, + /// we may spill one cell over public AxisAlignedBox3d Bounds; - // Length of edges of cubes that are marching. - // currently, # of cells along axis = (int)(bounds_dimension / CellSize) + 1 + /// + /// Length of edges of cubes that are marching. + /// currently, # of cells along axis = (int)(bounds_dimension / CellSize) + 1 + /// public double CubeSize = 0.1; - // Use multi-threading? Generally a good idea unless problem is very small or - // you are multi-threading at a higher level (which may be more efficient as - // we currently use very fine-grained spinlocks to synchronize) + /// + /// Use multi-threading? Generally a good idea unless problem is very small or + /// you are multi-threading at a higher level (which may be more efficient as + /// we currently use very fine-grained spinlocks to synchronize) + /// public bool ParallelCompute = true; + public enum RootfindingModes { SingleLerp, LerpSteps, Bisection } + + /// + /// Which rootfinding method will be used to converge on surface along edges + /// + public RootfindingModes RootMode = RootfindingModes.SingleLerp; + + /// + /// number of iterations of rootfinding method (ignored for SingleLerp) + /// + public int RootModeSteps = 5; + /* * Outputs */ @@ -354,7 +374,7 @@ int append_triangle(int a, int b, int c) /// - /// estimate intersection along edge from f(p1)=valp1 to f(p2)=valp2, using linear interpolation + /// root-find the intersection along edge from f(p1)=valp1 to f(p2)=valp2 /// void find_iso(ref Vector3d p1, ref Vector3d p2, double valp1, double valp2, ref Vector3d pIso) { @@ -381,21 +401,54 @@ void find_iso(ref Vector3d p1, ref Vector3d p2, double valp1, double valp2, ref // [RMS] if we don't maintain min/max order here, then numerical error means // that hashing on point x/y/z doesn't work - if (valp1 < valp2) { - double mu = (IsoValue - valp1) / (valp2 - valp1); - pIso.x = p1.x + mu * (p2.x - p1.x); - pIso.y = p1.y + mu * (p2.y - p1.y); - pIso.z = p1.z + mu * (p2.z - p1.z); + Vector3d a = p1, b = p2; + double fa = valp1, fb = valp2; + if (valp2 < valp1) { + a = p2; b = p1; + fb = valp1; fa = valp2; + } + + // converge on root + if (RootMode == RootfindingModes.Bisection) { + for (int k = 0; k < RootModeSteps; ++k) { + pIso.x = (a.x + b.x) * 0.5; pIso.y = (a.y + b.y) * 0.5; pIso.z = (a.z + b.z) * 0.5; + double mid_f = Implicit.Value(ref pIso); + if (mid_f < IsoValue) { + a = pIso; fa = mid_f; + } else { + b = pIso; fb = mid_f; + } + } + pIso = Vector3d.Lerp(a, b, 0.5); + } else { - double mu = (IsoValue - valp2) / (valp1 - valp2); - pIso.x = p2.x + mu * (p1.x - p2.x); - pIso.y = p2.y + mu * (p1.y - p2.y); - pIso.z = p2.z + mu * (p1.z - p2.z); + double mu = 0; + if (RootMode == RootfindingModes.LerpSteps) { + for (int k = 0; k < RootModeSteps; ++k) { + mu = (IsoValue - fa) / (fb - fa); + pIso.x = a.x + mu * (b.x - a.x); + pIso.y = a.y + mu * (b.y - a.y); + pIso.z = a.z + mu * (b.z - a.z); + double mid_f = Implicit.Value(ref pIso); + if (mid_f < IsoValue) { + a = pIso; fa = mid_f; + } else { + b = pIso; fb = mid_f; + } + } + } + + // final lerp + mu = (IsoValue - fa) / (fb - fa); + pIso.x = a.x + mu * (b.x - a.x); + pIso.y = a.y + mu * (b.y - a.y); + pIso.z = a.z + mu * (b.z - a.z); } } + /* * Below here are standard marching-cubes tables. */ From dc51a77569d4c0a1f830fb1420f110a3bec4d9b3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 12 Feb 2018 11:22:02 -0500 Subject: [PATCH 008/225] merge in changes from siggraph project --- core/g3Iterators.cs | 58 ++++++++++ geometry3Sharp.csproj | 1 + implicit/MarchingQuads.cs | 65 +---------- math/IndexUtil.cs | 15 +++ mesh_generators/GenCylGenerators.cs | 7 ++ mesh_generators/MeshGenerators.cs | 10 +- mesh_generators/PointsMeshGenerators.cs | 72 +++++++++++++ mesh_ops/MeshIsoCurves.cs | 137 +++++++++++++++++++++--- 8 files changed, 288 insertions(+), 77 deletions(-) create mode 100644 mesh_generators/PointsMeshGenerators.cs diff --git a/core/g3Iterators.cs b/core/g3Iterators.cs index 800f43ad..6bbc8230 100644 --- a/core/g3Iterators.cs +++ b/core/g3Iterators.cs @@ -93,5 +93,63 @@ IEnumerator IEnumerable.GetEnumerator() } + + + /// + /// IList wrapper for an Interval1i, ie sequential list of integers + /// + public struct IntSequence : IList + { + Interval1i range; + + public IntSequence(Interval1i ival) { + range = ival; + } + public IntSequence(int iStart, int iEnd) { + range = new Interval1i(iStart, iEnd); + } + + /// construct interval [0, N-1] + static public IntSequence Range(int N) { return new IntSequence(0, N - 1); } + + /// construct interval [0, N-1] + static public IntSequence RangeInclusive(int N) { return new IntSequence(0, N); } + + /// construct interval [start, start+N-1] + static public IntSequence Range(int start, int N) { return new IntSequence(start, start + N - 1); } + + + /// construct interval [a, b] + static public IntSequence FromToInclusive(int a, int b) { return new IntSequence(a, b); } + + public int this[int index] { + get { return range.a + index; } + set { throw new NotImplementedException(); } + } + public int Count { get { return range.Length+1; } } + public bool IsReadOnly { get { return true; } } + + public void Add(int item) { throw new NotImplementedException(); } + public void Clear() { throw new NotImplementedException(); } + public void Insert(int index, int item) { throw new NotImplementedException(); } + public bool Remove(int item) { throw new NotImplementedException(); } + public void RemoveAt(int index) { throw new NotImplementedException(); } + + // could be implemented... + public bool Contains(int item) { return range.Contains(item); } + public int IndexOf(int item) { throw new NotImplementedException(); } + public void CopyTo(int[] array, int arrayIndex) { throw new NotImplementedException(); } + + public IEnumerator GetEnumerator() { + return range.GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } + + + + } diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 64ccd9c5..ccd73e55 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -164,6 +164,7 @@ + diff --git a/implicit/MarchingQuads.cs b/implicit/MarchingQuads.cs index 9c1a0c65..03a324ba 100644 --- a/implicit/MarchingQuads.cs +++ b/implicit/MarchingQuads.cs @@ -5,7 +5,9 @@ namespace g3 { /// - /// Summary description for MarchingQuads. + /// 2D MarchingQuads polyline extraction from scalar field + /// [TODO] this is very, very old code. Should at minimum rewrite using current + /// vector classes/etc. /// public class MarchingQuads { @@ -111,7 +113,6 @@ public AxisAlignedBox2f GetBounds() { public void AddSeedPoint( float x, float y ) { - // [RMS TODO] does this memleak... ?? m_seedPoints.Add( new SeedPoint(x - m_fXShift, y - m_fYShift) ); } @@ -165,39 +166,6 @@ public void Polygonize( ImplicitField2d field ) { } - void LerpStep(ref float fValue1, ref float fValue2, ref float fX1, ref float fY1, ref float fX2, ref float fY2, - bool bVerticalEdge) { - - float fAlpha = 0.0f; - if ( Math.Abs(fValue1-fValue2) < 0.001 ) { - fAlpha = 0.5f; - } else { - fAlpha = (m_fIsoValue - fValue2) / (fValue1 - fValue2); - } - - float fX = 0.0f, fY = 0.0f; - if (bVerticalEdge) { - fX = fX1; - fY = fAlpha*fY1 + (1.0f-fAlpha)*fY2; - } else { - fX = fAlpha*fX1 + (1.0f-fAlpha)*fX2; - fY = fY1; - } - - float fValue = (float)m_field.Value(fX, fY); - if (fValue < m_fIsoValue) { - fValue1 = fValue; - fX1 = fX; - fY1 = fY; - } else { - fValue2 = fValue; - fX2 = fX; - fY2 = fY; - } - - } - - void SubdivideStep(ref float fValue1, ref float fValue2, ref float fX1, ref float fY1, ref float fX2, ref float fY2, bool bVerticalEdge) { @@ -249,7 +217,7 @@ int LerpAndAddStrokeVertex( float fValue1, float fValue2, int x1, int y1, int x2 float fX2 = (float)x2 * m_fCellSize + m_fXShift; float fY2 = (float)y2 * m_fCellSize + m_fYShift; - for (int i = 0; i < 5; ++i) + for (int i = 0; i < 10; ++i) SubdivideStep(ref fRefValue1, ref fRefValue2, ref fX1, ref fY1, ref fX2, ref fY2, bVerticalEdge); if ( Math.Abs(fRefValue1) < Math.Abs(fRefValue2) ) { @@ -449,30 +417,5 @@ void SetBounds( AxisAlignedBox2f bounds ) { } -/* - public void DrawTouchedCells( Graphics g, Pen pen ) { - - for (int yi = 0; yi < m_nCells; ++yi) { - for (int xi = 0; xi < m_nCells; ++xi) { - Cell cell = m_cells[yi][xi]; - int x = (int)((float)xi*m_fCellSize + m_fXShift); - int y = (int)((float)yi*m_fCellSize + m_fYShift); - if (cell.bTouched) { - g.DrawRectangle( pen, x, y, (int)m_fCellSize, (int)m_fCellSize ); - } - //if (cell.fValue == s_fValueSentinel) - //g.FillRectangle( Brushes.DarkOrange, x-2, y-2, 4, 4 ); - if (cell.fValue != s_fValueSentinel) { - if (cell.fValue < 0.0) - g.FillRectangle( Brushes.Magenta, x-2, y-2, 4, 4 ); - else - g.FillRectangle( Brushes.Cyan, x-2, y-2, 4, 4 ); - } - } - } - - } -*/ - } } diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index bff9dcb8..c2f90837 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -80,6 +80,21 @@ public static int find_tri_ordered_edge(int a, int b, int[] tri_verts) return DMesh3.InvalidID; } + /// + /// find sequence [a,b] in tri_verts (mod3) and return index of a, or InvalidID if not found + /// + public static int find_tri_ordered_edge(int a, int b, ref Index3i tri_verts) + { + if (tri_verts.a == a && tri_verts.b == b) return 0; + if (tri_verts.b == a && tri_verts.c == b) return 1; + if (tri_verts.c == a && tri_verts.a == b) return 2; + return DMesh3.InvalidID; + } + public static int find_tri_ordered_edge(int a, int b, Index3i tri_verts) + { + return find_tri_ordered_edge(a, b, ref tri_verts); + } + // find sequence [a,b] in tri_verts (mod3) then return the third **value**, or InvalidID if not found public static int find_tri_other_vtx(int a, int b, int[] tri_verts) { diff --git a/mesh_generators/GenCylGenerators.cs b/mesh_generators/GenCylGenerators.cs index c365cccc..39d5cd83 100644 --- a/mesh_generators/GenCylGenerators.cs +++ b/mesh_generators/GenCylGenerators.cs @@ -11,6 +11,13 @@ namespace g3 /// However caps are triangulated using a fan around a center vertex (which you /// can set using CapCenter). If Polygon is non-convex, this will have foldovers. /// In that case, you have to triangulate and append it yourself. + /// + /// If your profile curve does not contain the origin, use CapCenter. + /// + /// The output normals are currently set to those for a circular profile. + /// Call MeshNormals.QuickCompute() on the output DMesh to estimate proper + /// vertex normals + /// /// public class TubeGenerator : MeshGenerator { diff --git a/mesh_generators/MeshGenerators.cs b/mesh_generators/MeshGenerators.cs index abac629a..2d17dd84 100644 --- a/mesh_generators/MeshGenerators.cs +++ b/mesh_generators/MeshGenerators.cs @@ -41,17 +41,19 @@ public virtual SimpleMesh MakeSimpleMesh() public virtual void MakeMesh(DMesh3 m) { int nV = vertices.Count; - if (WantNormals) + bool bWantNormals = WantNormals && normals != null && normals.Count == vertices.Count; + if (bWantNormals) m.EnableVertexNormals(Vector3f.AxisY); - if (WantUVs) + bool bWantUVs = WantUVs && uv != null && uv.Count == vertices.Count; + if (bWantUVs) m.EnableVertexUVs(Vector2f.Zero); for (int i = 0; i < nV; ++i) { NewVertexInfo ni = new NewVertexInfo() { v = vertices[i] }; - if ( WantNormals ) { + if (bWantNormals) { ni.bHaveN = true; ni.n = normals[i]; } - if ( WantUVs ) { + if (bWantUVs) { ni.bHaveUV = true; ni.uv = uv[i]; } diff --git a/mesh_generators/PointsMeshGenerators.cs b/mesh_generators/PointsMeshGenerators.cs new file mode 100644 index 00000000..5ef34ea3 --- /dev/null +++ b/mesh_generators/PointsMeshGenerators.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace g3 +{ + /// + /// Create a mesh that contains a planar element for each point and normal + /// (currently only triangles) + /// + public class PointSplatsGenerator : MeshGenerator + { + public IEnumerable PointIndices; + public int PointIndicesCount = -1; // you can set this to avoid calling Count() on enumerable + + public Func PointF; // required + public Func NormalF; // required + public double Radius = 1.0f; + + public PointSplatsGenerator() + { + WantUVs = false; + } + + public override MeshGenerator Generate() + { + int N = (PointIndicesCount == -1) ? PointIndices.Count() : PointIndicesCount; + + vertices = new VectorArray3d(N * 3); + uv = null; + normals = new VectorArray3f(vertices.Count); + triangles = new IndexArray3i(N); + + Matrix2f matRot = new Matrix2f(120 * MathUtil.Deg2Radf); + Vector2f uva = new Vector2f(0, Radius); + Vector2f uvb = matRot * uva; + Vector2f uvc = matRot * uvb; + + int vi = 0; + int ti = 0; + foreach (int pid in PointIndices) { + Vector3d v = PointF(pid); + Vector3d n = NormalF(pid); + Frame3f f = new Frame3f(v, n); + triangles.Set(ti++, vi, vi + 1, vi + 2, Clockwise); + vertices[vi++] = f.FromPlaneUV(uva, 2); + vertices[vi++] = f.FromPlaneUV(uvb, 2); + vertices[vi++] = f.FromPlaneUV(uvc, 2); + } + + return this; + } + + + + /// + /// shortcut utility + /// + public static DMesh3 Generate(IList indices, + Func PointF, Func NormalF, + double radius) + { + var gen = new PointSplatsGenerator() { + PointIndices = indices, + PointIndicesCount = indices.Count, + PointF = PointF, NormalF = NormalF, Radius = radius + }; + return gen.Generate().MakeDMesh(); + } + + } +} diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs index e14d62e4..9ae4b0ac 100644 --- a/mesh_ops/MeshIsoCurves.cs +++ b/mesh_ops/MeshIsoCurves.cs @@ -15,6 +15,26 @@ public class MeshIsoCurves /// public Func VertexValueF = null; + /// + /// If true, then we internally precompute vertex values. + /// ***THIS COMPUTATION IS MULTI-THREADED*** + /// + public bool PrecomputeVertexValues = false; + + + public enum RootfindingModes { SingleLerp, LerpSteps, Bisection } + + /// + /// Which rootfinding method will be used to converge on surface along edges + /// + public RootfindingModes RootMode = RootfindingModes.SingleLerp; + + /// + /// number of iterations of rootfinding method (ignored for SingleLerp) + /// + public int RootModeSteps = 5; + + public DGraph3 Graph = null; public enum TriangleCase @@ -34,6 +54,9 @@ public struct GraphEdgeInfo } public DVector GraphEdges = null; + // locations of edge crossings that we found during rootfinding + Dictionary EdgeLocations = new Dictionary(); + public MeshIsoCurves(DMesh3 mesh, Func valueF) { @@ -43,7 +66,7 @@ public MeshIsoCurves(DMesh3 mesh, Func valueF) public void Compute() { - compute_full(Mesh.TriangleIndices()); + compute_full(Mesh.TriangleIndices(), true); } public void Compute(IEnumerable Triangles) { @@ -57,7 +80,7 @@ public void Compute(IEnumerable Triangles) Dictionary Vertices; - protected void compute_full(IEnumerable Triangles) + protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = false) { Graph = new DGraph3(); if (WantGraphEdgeInfo) @@ -65,6 +88,24 @@ protected void compute_full(IEnumerable Triangles) Vertices = new Dictionary(); + + // multithreaded precomputation of per-vertex values + double[] vertex_values = null; + if (PrecomputeVertexValues) { + vertex_values = new double[Mesh.MaxVertexID]; + IEnumerable verts = Mesh.VertexIndices(); + if (bIsFullMeshHint == false) { + MeshVertexSelection vertices = new MeshVertexSelection(Mesh); + vertices.SelectTriangleVertices(Triangles); + verts = vertices; + } + gParallel.ForEach(verts, (vid) => { + vertex_values[vid] = ValueF(Mesh.GetVertex(vid)); + }); + VertexValueF = (vid) => { return vertex_values[vid]; }; + } + + foreach (int tid in Triangles) { Vector3dTuple3 tv = new Vector3dTuple3(); @@ -111,6 +152,7 @@ protected void compute_full(IEnumerable Triangles) } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); int cross_vid = add_or_append_vertex(cross); + add_edge_pos(triVerts[i], triVerts[j], cross); int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); if (WantGraphEdgeInfo) @@ -128,6 +170,7 @@ protected void compute_full(IEnumerable Triangles) } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); cross_verts[ti] = add_or_append_vertex(cross); + add_edge_pos(triVerts[i], triVerts[j], cross); } int e0 = (cross_verts.a == int.MinValue) ? 1 : 0; int e1 = (cross_verts.c == int.MinValue) ? 1 : 2; @@ -192,19 +235,89 @@ void add_on_edge(int graph_eid, int mesh_tri, int mesh_edge) } + // [TODO] should convert this to a utility function Vector3d find_crossing(Vector3d a, Vector3d b, double fA, double fB) { - double t = 0.5; - if (fA < fB) { - t = (0 - fA) / (fB - fA); - t = MathUtil.Clamp(t, 0, 1); + if (fB < fA) { + Vector3d tmp = a; a = b; b = tmp; + double f = fA; fA = fB; fB = f; + } + + if (RootMode == RootfindingModes.Bisection) { + for ( int k = 0; k < RootModeSteps; ++k ) { + Vector3d c = Vector3d.Lerp(a, b, 0.5); + double f = ValueF(c); + if ( f < 0 ) { + fA = f; a = c; + } else { + fB = f; b = c; + } + } + return Vector3d.Lerp(a, b, 0.5); + + } else { + // really should check this every iteration... + if ( Math.Abs(fB-fA) < MathUtil.ZeroTolerance ) + return a; + + double t = 0; + if (RootMode == RootfindingModes.LerpSteps) { + for (int k = 0; k < RootModeSteps; ++k) { + t = MathUtil.Clamp((0 - fA) / (fB - fA), 0, 1); + Vector3d c = (1 - t)*a + (t)*b; + double f = ValueF(c); + if (f < 0) { + fA = f; a = c; + } else { + fB = f; b = c; + } + } + } + + t = MathUtil.Clamp((0 - fA) / (fB - fA), 0, 1); return (1 - t) * a + (t) * b; - } else if ( fB < fA ) { - t = (0 - fB) / (fA - fB); - t = MathUtil.Clamp(t, 0, 1); - return (1 - t) * b + (t) * a; - } else - return a; + } + } + + + + void add_edge_pos(int a, int b, Vector3d crossing_pos) + { + int eid = Mesh.FindEdge(a, b); + if (eid == DMesh3.InvalidID) + throw new Exception("MeshIsoCurves.add_edge_split: invalid edge?"); + if (EdgeLocations.ContainsKey(eid)) + return; + EdgeLocations[eid] = crossing_pos; + } + + + /// + /// Split the mesh edges at the iso-crossings, unless edge is + /// shorter than min_len, or inserted point would be within min_len or vertex + /// [TODO] do we want to return any info here?? + /// + public void SplitAtIsoCrossings(double min_len = 0) + { + foreach ( var pair in EdgeLocations ) { + int eid = pair.Key; + Vector3d pos = pair.Value; + if (!Mesh.IsEdge(eid)) + continue; + + Index2i ev = Mesh.GetEdgeV(eid); + Vector3d a = Mesh.GetVertex(ev.a); + Vector3d b = Mesh.GetVertex(ev.b); + if (a.Distance(b) < min_len) + continue; + Vector3d mid = (a + b) * 0.5; + if (a.Distance(mid) < min_len || b.Distance(mid) < min_len) + continue; + + DMesh3.EdgeSplitInfo splitInfo; + if (Mesh.SplitEdge(eid, out splitInfo) == MeshResult.Ok) + Mesh.SetVertex(splitInfo.vNew, pos); + } } From 5ad46089d7042b253cc40c6adc4b513523e59461 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 13 Feb 2018 00:14:04 -0500 Subject: [PATCH 009/225] implement functionality so that surface curves extracted from MeshIsoCurves can inherit surface orientation (eg like they are boundary curves). Seems to be working? --- curve/DCurve3.cs | 5 +++ curve/DGraph3Util.cs | 16 +++++++- mesh_ops/MeshIsoCurves.cs | 79 ++++++++++++++++++++++++++++++++------- 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index ff723b5e..9a5b3c75 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -103,6 +103,11 @@ public void RemoveVertex(int idx) Timestamp++; } + public void Reverse() { + vertices.Reverse(); + Timestamp++; + } + public Vector3d this[int key] { diff --git a/curve/DGraph3Util.cs b/curve/DGraph3Util.cs index a056a3e7..d0da17cd 100644 --- a/curve/DGraph3Util.cs +++ b/curve/DGraph3Util.cs @@ -22,7 +22,8 @@ public struct Curves /// /// Decompose graph into simple polylines and polygons. /// - public static Curves ExtractCurves(DGraph3 graph) + public static Curves ExtractCurves(DGraph3 graph, + Func CurveOrientationF = null ) { Curves c = new Curves(); c.Loops = new List(); @@ -46,6 +47,7 @@ public static Curves ExtractCurves(DGraph3 graph) int eid = graph.GetVtxEdges(vid)[0]; if (used.Contains(eid)) continue; + bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; DCurve3 path = new DCurve3() { Closed = false }; path.AppendVertex(graph.GetVertex(vid)); @@ -58,6 +60,8 @@ public static Curves ExtractCurves(DGraph3 graph) if (boundaries.Contains(vid) || junctions.Contains(vid)) break; // done! } + if (reverse) + path.Reverse(); c.Paths.Add(path); } @@ -72,6 +76,8 @@ public static Curves ExtractCurves(DGraph3 graph) int vid = start_vid; int eid = outgoing_eid; + bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; + DCurve3 path = new DCurve3() { Closed = false }; path.AppendVertex(graph.GetVertex(vid)); while (true) { @@ -88,6 +94,8 @@ public static Curves ExtractCurves(DGraph3 graph) if (vid == start_vid) { path.RemoveVertex(path.VertexCount - 1); path.Closed = true; + if (reverse) + path.Reverse(); c.Loops.Add(path); // need to mark incoming edge as used...but is it valid now? //Util.gDevAssert(eid != int.MaxValue); @@ -95,6 +103,8 @@ public static Curves ExtractCurves(DGraph3 graph) used.Add(eid); } else { + if (reverse) + path.Reverse(); c.Paths.Add(path); } } @@ -111,6 +121,8 @@ public static Curves ExtractCurves(DGraph3 graph) Index2i ev = graph.GetEdgeV(eid); int vid = ev.a; + bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; + DCurve3 poly = new DCurve3() { Closed = true }; poly.AppendVertex(graph.GetVertex(vid)); while (true) { @@ -125,6 +137,8 @@ public static Curves ExtractCurves(DGraph3 graph) break; } poly.RemoveVertex(poly.VertexCount - 1); + if (reverse) + poly.Reverse(); c.Loops.Add(poly); } diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs index 9ae4b0ac..abff31e2 100644 --- a/mesh_ops/MeshIsoCurves.cs +++ b/mesh_ops/MeshIsoCurves.cs @@ -46,15 +46,21 @@ public enum TriangleCase public bool WantGraphEdgeInfo = false; + /// + /// Information about edge of the computed Graph. + /// mesh_tri is triangle ID of crossed triangle + /// mesh_edges depends on case. EdgeEdge is [edgeid,edgeid], EdgeVertex is [edgeid,vertexid], and OnEdge is [edgeid,-1] + /// public struct GraphEdgeInfo { public TriangleCase caseType; public int mesh_tri; public Index2i mesh_edges; + public Index2i order; } public DVector GraphEdges = null; - // locations of edge crossings that we found during rootfinding + // locations of edge crossings that we found during rootfinding. key is edge id. Dictionary EdgeLocations = new Dictionary(); @@ -134,11 +140,14 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f if (f[i1] == 0 || f[i2] == 0) { // on-edge case int z1 = f[i1] == 0 ? i1 : i2; + if ( (z0+1)%3 != z1 ) { + int tmp = z0; z0 = z1; z1 = tmp; // catch reverse-orientation cases + } int e0 = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int e1 = add_or_append_vertex(Mesh.GetVertex(triVerts[z1])); int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge); if (WantGraphEdgeInfo) - add_on_edge(graph_eid, tid, triEdges[z0]); + add_on_edge(graph_eid, tid, triEdges[z0], new Index2i(e0, e1)); } else { // edge/vertex case @@ -156,24 +165,42 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); if (WantGraphEdgeInfo) - add_edge_edge(graph_eid, tid, new Index2i(triEdges[(z0+1)%3], triVerts[z0])); + add_edge_vert(graph_eid, tid, triEdges[(z0+1)%3], triVerts[z0], new Index2i(vert_vid, cross_vid)); } } else { Index3i cross_verts = Index3i.Min; - for (int ti = 0; ti < 3; ++ti) { - int i = ti, j = (ti + 1) % 3; + int less_than = 0, greater_than = 0; + for (int tei = 0; tei < 3; ++tei) { + int i = tei, j = (tei + 1) % 3; + if (f[i] < 0) + less_than++; + else + greater_than++; if (f[i] * f[j] > 0) continue; if ( triVerts[j] < triVerts[i] ) { int tmp = i; i = j; j = tmp; } Vector3d cross = find_crossing(tv[i], tv[j], f[i], f[j]); - cross_verts[ti] = add_or_append_vertex(cross); + cross_verts[tei] = add_or_append_vertex(cross); add_edge_pos(triVerts[i], triVerts[j], cross); } int e0 = (cross_verts.a == int.MinValue) ? 1 : 0; int e1 = (cross_verts.c == int.MinValue) ? 1 : 2; + if (e0 == 0 && e1 == 2) { // preserve orientation order + e0 = 2; e1 = 0; + } + + // preserving orientation does not mean we get a *consistent* orientation across faces. + // To do that, we need to assign "sides". Either we have 1 less-than-0 or 1 greater-than-0 vtx. + // Arbitrary decide that we want loops oriented like bdry loops would be if we discarded less-than side. + // In that case, when we cut off one vertex, edge orientation would flip + // (right? working for test case but is that everything ??) + if (less_than == 1) { + int tmp = e0; e0 = e1; e1 = tmp; + } + int ev0 = cross_verts[e0]; int ev1 = cross_verts[e1]; // [RMS] if function is garbage, we can end up w/ case where both crossings @@ -183,7 +210,7 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue); int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge); if (WantGraphEdgeInfo) - add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1])); + add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]), new Index2i(ev0,ev1)); } } } @@ -204,32 +231,35 @@ int add_or_append_vertex(Vector3d pos) } - void add_edge_edge(int graph_eid, int mesh_tri, Index2i mesh_edges) + void add_edge_edge(int graph_eid, int mesh_tri, Index2i mesh_edges, Index2i order) { GraphEdgeInfo einfo = new GraphEdgeInfo() { caseType = TriangleCase.EdgeEdge, mesh_edges = mesh_edges, - mesh_tri = mesh_tri + mesh_tri = mesh_tri, + order = order }; GraphEdges.insertAt(einfo, graph_eid); } - void add_edge_vert(int graph_eid, int mesh_tri, int mesh_edge, int mesh_vert) + void add_edge_vert(int graph_eid, int mesh_tri, int mesh_edge, int mesh_vert, Index2i order) { GraphEdgeInfo einfo = new GraphEdgeInfo() { caseType = TriangleCase.EdgeVertex, mesh_edges = new Index2i(mesh_edge, mesh_vert), - mesh_tri = mesh_tri + mesh_tri = mesh_tri, + order = order }; GraphEdges.insertAt(einfo, graph_eid); } - void add_on_edge(int graph_eid, int mesh_tri, int mesh_edge) + void add_on_edge(int graph_eid, int mesh_tri, int mesh_edge, Index2i order) { GraphEdgeInfo einfo = new GraphEdgeInfo() { caseType = TriangleCase.OnEdge, mesh_edges = new Index2i(mesh_edge, -1), - mesh_tri = mesh_tri + mesh_tri = mesh_tri, + order = order }; GraphEdges.insertAt(einfo, graph_eid); } @@ -322,6 +352,29 @@ public void SplitAtIsoCrossings(double min_len = 0) + + /// + /// DGraph3 edges are not oriented, which means they cannot inherit orientation from mesh. + /// This function returns true if, for a given graph_eid, the vertex pair returned by + /// Graph.GetEdgeV(graph_eid) should be reversed to be consistent with mesh orientation. + /// Mainly inteded to be passed to DGraph3Util.ExtractCurves + /// + public bool ShouldReverseGraphEdge(int graph_eid) + { + if (GraphEdges == null) + throw new Exception("MeshIsoCurves.OrientEdge: must track edge graph info to orient edge"); + + Index2i graph_ev = Graph.GetEdgeV(graph_eid); + GraphEdgeInfo einfo = GraphEdges[graph_eid]; + + if (graph_ev.b == einfo.order.a && graph_ev.a == einfo.order.b) { + return true; + } + Util.gDevAssert(graph_ev.a == einfo.order.a && graph_ev.b == einfo.order.b); + return false; + } + + } From 73a1b4151b198abcd3e41de3f168fd7212ed4a52 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 13 Feb 2018 09:57:25 -0500 Subject: [PATCH 010/225] improve comment, remove unnecessary variable --- mesh_ops/MeshIsoCurves.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs index abff31e2..d77fefa8 100644 --- a/mesh_ops/MeshIsoCurves.cs +++ b/mesh_ops/MeshIsoCurves.cs @@ -170,13 +170,11 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f } else { Index3i cross_verts = Index3i.Min; - int less_than = 0, greater_than = 0; + int less_than = 0; for (int tei = 0; tei < 3; ++tei) { int i = tei, j = (tei + 1) % 3; if (f[i] < 0) less_than++; - else - greater_than++; if (f[i] * f[j] > 0) continue; if ( triVerts[j] < triVerts[i] ) { @@ -195,8 +193,7 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f // preserving orientation does not mean we get a *consistent* orientation across faces. // To do that, we need to assign "sides". Either we have 1 less-than-0 or 1 greater-than-0 vtx. // Arbitrary decide that we want loops oriented like bdry loops would be if we discarded less-than side. - // In that case, when we cut off one vertex, edge orientation would flip - // (right? working for test case but is that everything ??) + // In that case, when we are "cutting off" one vertex, orientation would end up flipped if (less_than == 1) { int tmp = e0; e0 = e1; e1 = tmp; } From 71c6f7743a04fc4a380891f02f2120d357c54f28 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 18 Feb 2018 23:47:35 -0500 Subject: [PATCH 011/225] added a bunch of functional implicit objects and operators - halfspace, line, aabb, obb, offset, union, difference, intersection, nary union/difference, blend --- implicit/GridImplicits3d.cs | 12 +- implicit/Implicit3d.cs | 335 +++++++++++++++++++++++++++++++++++- math/AxisAlignedBox3d.cs | 6 + 3 files changed, 348 insertions(+), 5 deletions(-) diff --git a/implicit/GridImplicits3d.cs b/implicit/GridImplicits3d.cs index 70d72c75..87714bae 100644 --- a/implicit/GridImplicits3d.cs +++ b/implicit/GridImplicits3d.cs @@ -10,7 +10,7 @@ namespace g3 /// via GridOrigin, but does not support scaling or rotation. If you need those, /// you can wrap this in something that does the xform. /// - public class DenseGridTrilinearImplicit : ImplicitFunction3d + public class DenseGridTrilinearImplicit : BoundedImplicitFunction3d { public DenseGrid3f Grid; public double CellSize; @@ -28,6 +28,16 @@ public DenseGridTrilinearImplicit(DenseGrid3f grid, Vector3d gridOrigin, double } + public AxisAlignedBox3d Bounds() + { + return new AxisAlignedBox3d( + GridOrigin.x, GridOrigin.y, GridOrigin.z, + GridOrigin.x + CellSize * Grid.ni, + GridOrigin.y + CellSize * Grid.nj, + GridOrigin.z + CellSize * Grid.nk); + } + + public double Value(ref Vector3d pt) { Vector3d gridPt = new Vector3d( diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index dcbb28b1..21455289 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -1,24 +1,351 @@ using System; using System.Collections.Generic; - namespace g3 { - + /// + /// Minimalist implicit function interface + /// public interface ImplicitFunction3d { double Value(ref Vector3d pt); } + /// + /// Bounded implicit function has a bounding box within which + /// the "interesting" part of the function is contained + /// (eg the surface) + /// + public interface BoundedImplicitFunction3d : ImplicitFunction3d + { + AxisAlignedBox3d Bounds(); + } + - public class ImplicitSphere3d : ImplicitFunction3d + /// + /// Implicit sphere, where zero isocontour is at Radius + /// + public class ImplicitSphere3d : BoundedImplicitFunction3d { + public Vector3d Origin; + public double Radius; + public double Value(ref Vector3d pt) { - return pt.Length - 5.0f; + return pt.Distance(ref Origin) - Radius; } + + public AxisAlignedBox3d Bounds() + { + return new AxisAlignedBox3d(Origin, Radius); + } } + /// + /// Implicit half-space. "Inside" is opposite of Normal direction. + /// + public class ImplicitHalfSpace3d : BoundedImplicitFunction3d + { + public Vector3d Origin; + public Vector3d Normal; + + public double Value(ref Vector3d pt) + { + return (pt - Origin).Dot(Normal); + } + + public AxisAlignedBox3d Bounds() + { + return new AxisAlignedBox3d(Origin, MathUtil.Epsilon); + } + } + + + + /// + /// Implicit axis-aligned box + /// + public class ImplicitAxisAlignedBox3d : BoundedImplicitFunction3d + { + public AxisAlignedBox3d AABox; + + public double Value(ref Vector3d pt) + { + return AABox.SignedDistance(pt); + } + + public AxisAlignedBox3d Bounds() + { + return AABox; + } + } + + + + /// + /// Implicit oriented box + /// + public class ImplicitBox3d : BoundedImplicitFunction3d + { + Box3d box; + AxisAlignedBox3d local_aabb; + AxisAlignedBox3d bounds_aabb; + public Box3d Box { + get { return box; } + set { + box = value; + local_aabb = new AxisAlignedBox3d( + -Box.Extent.x, -Box.Extent.y, -Box.Extent.z, + Box.Extent.x, Box.Extent.y, Box.Extent.z); + bounds_aabb = box.ToAABB(); + } + } + + + public double Value(ref Vector3d pt) + { + double dx = (pt - Box.Center).Dot(Box.AxisX); + double dy = (pt - Box.Center).Dot(Box.AxisY); + double dz = (pt - Box.Center).Dot(Box.AxisZ); + return local_aabb.SignedDistance(new Vector3d(dx, dy, dz)); + } + + public AxisAlignedBox3d Bounds() + { + return bounds_aabb; + } + } + + + + /// + /// Implicit tube around line segment + /// + public class ImplicitLine3d : BoundedImplicitFunction3d + { + public Segment3d Segment; + public double Radius; + + public double Value(ref Vector3d pt) + { + double d = Math.Sqrt(Segment.DistanceSquared(pt)); + return d - Radius; + } + + public AxisAlignedBox3d Bounds() + { + Vector3d o = Radius * Vector3d.One, p0 = Segment.P0, p1 = Segment.P1; + AxisAlignedBox3d box = new AxisAlignedBox3d(p0 - o, p0 + o); + box.Contain(p1 - o); + box.Contain(p1 + o); + return box; + } + } + + + + + /// + /// Offset the zero-isocontour of an implicit function. + /// Assumes that negative is inside, if not, reverse offset. + /// + public class ImplicitOffset3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public double Offset; + + public double Value(ref Vector3d pt) + { + return A.Value(ref pt) - Offset; + } + + public AxisAlignedBox3d Bounds() + { + AxisAlignedBox3d box = A.Bounds(); + box.Expand(Offset); + return box; + } + } + + + + /// + /// Boolean Union of two implicit functions, A OR B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitUnion3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + public double Value(ref Vector3d pt) + { + return Math.Min(A.Value(ref pt), B.Value(ref pt)); + } + + public AxisAlignedBox3d Bounds() + { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + + /// + /// Boolean Intersection of two implicit functions, A AND B + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitIntersection3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + public double Value(ref Vector3d pt) + { + return Math.Max(A.Value(ref pt), B.Value(ref pt)); + } + + public AxisAlignedBox3d Bounds() + { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + + /// + /// Boolean Difference/Subtraction of two implicit functions A-B, A AND (NOT B) + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitDifference3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + public double Value(ref Vector3d pt) + { + return Math.Max(A.Value(ref pt), -B.Value(ref pt)); + } + + public AxisAlignedBox3d Bounds() + { + // [TODO] can actually subtract B.Bounds() here... + return A.Bounds(); + } + } + + + + + /// + /// Boolean Union of N implicit functions, A OR B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitNaryUnion3d : BoundedImplicitFunction3d + { + public List Children; + + public double Value(ref Vector3d pt) + { + double f = Children[0].Value(ref pt); + int N = Children.Count; + for (int k = 1; k < N; ++k) + f = Math.Min(f, Children[k].Value(ref pt)); + return f; + } + + public AxisAlignedBox3d Bounds() + { + var box = Children[0].Bounds(); + int N = Children.Count; + for (int k = 1; k < N; ++k) + box.Contain(Children[k].Bounds()); + return box; + } + } + + + + + + /// + /// Boolean Difference of N implicit functions, A - Union(B1..BN) + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitNaryDifference3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public List BSet; + + public double Value(ref Vector3d pt) + { + double fA = A.Value(ref pt); + int N = BSet.Count; + if (N == 0) + return fA; + double fB = BSet[0].Value(ref pt); + for (int k = 1; k < N; ++k) + fB = Math.Min(fB, BSet[k].Value(ref pt)); + return Math.Max(fA, -fB); + } + + public AxisAlignedBox3d Bounds() + { + // [TODO] could actually subtract other bounds here... + return A.Bounds(); + } + } + + + + + + /// + /// Blend of two implicit surfaces. Assumes surface is at zero iscontour. + /// Uses Pasko blend from http://www.hyperfun.org/F-rep.pdf + /// + public class ImplicitBlend3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + /// Blending power. 0 is Union, 2 is nice blend. + public double Blend { + get { return blend; } + set { blend = MathUtil.Clamp(value, 0.0, 100000); } + } + double blend = 2.0; + + public double Value(ref Vector3d pt) + { + double fA = A.Value(ref pt); + double fB = B.Value(ref pt); + double sqr_sum = fA*fA + fB*fB; + double b = blend / (1.0 + sqr_sum); + //double a = 0.5; + //return (1.0/(1.0+a)) * (fA + fB - Math.Sqrt(fA*fA + fB*fB - 2*a*fA*fB)) - b; + return 0.666666 * (fA + fB - Math.Sqrt(sqr_sum - fA*fB)) - b; + } + + public AxisAlignedBox3d Bounds() + { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + } diff --git a/math/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs index 635c227a..e39c4a6b 100644 --- a/math/AxisAlignedBox3d.cs +++ b/math/AxisAlignedBox3d.cs @@ -24,11 +24,17 @@ public AxisAlignedBox3d(double xmin, double ymin, double zmin, double xmax, doub Max = new Vector3d(xmax, ymax, zmax); } + /// + /// init box [0,size] x [0,size] x [0,size] + /// public AxisAlignedBox3d(double fCubeSize) { Min = new Vector3d(0, 0, 0); Max = new Vector3d(fCubeSize, fCubeSize, fCubeSize); } + /// + /// Init box [0,width] x [0,height] x [0,depth] + /// public AxisAlignedBox3d(double fWidth, double fHeight, double fDepth) { Min = new Vector3d(0, 0, 0); Max = new Vector3d(fWidth, fHeight, fDepth); From adfebecc913cfff8f3a800858ebf40d87d7cd098 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 20 Feb 2018 10:35:41 -0500 Subject: [PATCH 012/225] implicits improvements --- implicit/GridImplicits3d.cs | 5 +- implicit/Implicit3d.cs | 99 ++++++++++++++++++++++++++++--- spatial/MeshSignedDistanceGrid.cs | 8 ++- 3 files changed, 101 insertions(+), 11 deletions(-) diff --git a/implicit/GridImplicits3d.cs b/implicit/GridImplicits3d.cs index 87714bae..97dd08d3 100644 --- a/implicit/GridImplicits3d.cs +++ b/implicit/GridImplicits3d.cs @@ -17,8 +17,9 @@ public class DenseGridTrilinearImplicit : BoundedImplicitFunction3d public Vector3d GridOrigin; // value to return if query point is outside grid (in an SDF - // outside is usually positive...) - public double Outside = double.MaxValue; + // outside is usually positive). Need to do math with this value, + // so don't use double.MaxValue or square will overflow + public double Outside = Math.Sqrt(Math.Sqrt(double.MaxValue)); public DenseGridTrilinearImplicit(DenseGrid3f grid, Vector3d gridOrigin, double cellSize) { diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 21455289..9a407bcb 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -213,6 +213,7 @@ public double Value(ref Vector3d pt) public AxisAlignedBox3d Bounds() { + // [TODO] intersect boxes var box = A.Bounds(); box.Contain(B.Bounds()); return box; @@ -222,7 +223,7 @@ public AxisAlignedBox3d Bounds() /// - /// Boolean Difference/Subtraction of two implicit functions A-B, A AND (NOT B) + /// Boolean Difference/Subtraction of two implicit functions A-B = A AND (NOT B) /// Assumption is that both have surface at zero isocontour and /// negative is inside. /// @@ -310,12 +311,94 @@ public AxisAlignedBox3d Bounds() + /// + /// Continuous R-Function Boolean Union of two implicit functions, A OR B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitSmoothUnion3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; - /// - /// Blend of two implicit surfaces. Assumes surface is at zero iscontour. - /// Uses Pasko blend from http://www.hyperfun.org/F-rep.pdf - /// - public class ImplicitBlend3d : BoundedImplicitFunction3d + const double mul = 1.0 / 1.5; + + public double Value(ref Vector3d pt) { + double fA = A.Value(ref pt); + double fB = B.Value(ref pt); + return mul * (fA + fB - Math.Sqrt(fA*fA + fB*fB - fA*fB)); + } + + public AxisAlignedBox3d Bounds() { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + + /// + /// Continuous R-Function Boolean Intersection of two implicit functions, A-B = A AND (NOT B) + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitSmoothIntersection3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + const double mul = 1.0 / 1.5; + + public double Value(ref Vector3d pt) { + double fA = A.Value(ref pt); + double fB = B.Value(ref pt); + return mul * (fA + fB + Math.Sqrt(fA*fA + fB*fB - fA*fB)); + } + + public AxisAlignedBox3d Bounds() { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + + + /// + /// Continuous R-Function Boolean Difference of two implicit functions, A AND B + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitSmoothDifference3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + const double mul = 1.0 / 1.5; + + public double Value(ref Vector3d pt) { + double fA = A.Value(ref pt); + double fB = -B.Value(ref pt); + return mul * (fA + fB + Math.Sqrt(fA*fA + fB*fB - fA*fB)); + } + + public AxisAlignedBox3d Bounds() { + var box = A.Bounds(); + box.Contain(B.Bounds()); + return box; + } + } + + + + + /// + /// Blend of two implicit surfaces. Assumes surface is at zero iscontour. + /// Uses Pasko blend from http://www.hyperfun.org/F-rep.pdf + /// + public class ImplicitBlend3d : BoundedImplicitFunction3d { public BoundedImplicitFunction3d A; public BoundedImplicitFunction3d B; @@ -332,16 +415,18 @@ public double Value(ref Vector3d pt) double fA = A.Value(ref pt); double fB = B.Value(ref pt); double sqr_sum = fA*fA + fB*fB; + Util.gDevAssert(sqr_sum != double.PositiveInfinity); double b = blend / (1.0 + sqr_sum); //double a = 0.5; //return (1.0/(1.0+a)) * (fA + fB - Math.Sqrt(fA*fA + fB*fB - 2*a*fA*fB)) - b; - return 0.666666 * (fA + fB - Math.Sqrt(sqr_sum - fA*fB)) - b; + return 0.666666 * (fA + fB - Math.Sqrt(sqr_sum - fA*fB)) - b; } public AxisAlignedBox3d Bounds() { var box = A.Bounds(); box.Contain(B.Bounds()); + box.Expand( 0.5 * blend * box.MaxDim ); return box; } } diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 448275fc..c5a24fd7 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -46,6 +46,10 @@ public class MeshSignedDistanceGrid // (In fact this is conservative, the band is often larger locally) public int ExactBandWidth = 1; + // Bounds of grid will be expanded this much in positive and negative directions. + // Useful for if you want field to extend outwards. + public Vector3d ExpandBounds = Vector3d.Zero; + // Most of this parallelizes very well, makes a huge speed difference public bool UseParallel = true; @@ -106,8 +110,8 @@ public void Compute() AxisAlignedBox3d bounds = Mesh.CachedBounds; float fBufferWidth = 2 * ExactBandWidth * CellSize; - grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One; - Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One; + grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One - (Vector3f)ExpandBounds; + Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One + (Vector3f)ExpandBounds; int ni = (int)((max.x - grid_origin.x) / CellSize) + 1; int nj = (int)((max.y - grid_origin.y) / CellSize) + 1; int nk = (int)((max.z - grid_origin.z) / CellSize) + 1; From d27d10af9c1499e5c0a22db43c47d7ed54728a6c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 20 Feb 2018 11:02:35 -0500 Subject: [PATCH 013/225] add per-object weighting to blends --- implicit/Implicit3d.cs | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 9a407bcb..424925ff 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -403,22 +403,43 @@ public class ImplicitBlend3d : BoundedImplicitFunction3d public BoundedImplicitFunction3d A; public BoundedImplicitFunction3d B; - /// Blending power. 0 is Union, 2 is nice blend. - public double Blend { + + /// Weight on implicit A + public double WeightA { + get { return weightA; } + set { weightA = MathUtil.Clamp(value, 0.00001, 100000); } + } + double weightA = 0.01; + + /// Weight on implicit B + public double WeightB { + get { return weightB; } + set { weightB = MathUtil.Clamp(value, 0.00001, 100000); } + } + double weightB = 0.01; + + /// Blending power + public double Blend { get { return blend; } set { blend = MathUtil.Clamp(value, 0.0, 100000); } } double blend = 2.0; + + public double ExpandBounds = 0.25; + + public double Value(ref Vector3d pt) { double fA = A.Value(ref pt); double fB = B.Value(ref pt); double sqr_sum = fA*fA + fB*fB; - Util.gDevAssert(sqr_sum != double.PositiveInfinity); - double b = blend / (1.0 + sqr_sum); - //double a = 0.5; - //return (1.0/(1.0+a)) * (fA + fB - Math.Sqrt(fA*fA + fB*fB - 2*a*fA*fB)) - b; + if (sqr_sum > 1e12) + return Math.Min(fA, fB); + double wa = fA/weightA, wb = fB/weightB; + double b = blend / (1.0 + wa*wa + wb*wb); + //double a = 0.5; + //return (1.0/(1.0+a)) * (fA + fB - Math.Sqrt(fA*fA + fB*fB - 2*a*fA*fB)) - b; return 0.666666 * (fA + fB - Math.Sqrt(sqr_sum - fA*fB)) - b; } @@ -426,7 +447,7 @@ public AxisAlignedBox3d Bounds() { var box = A.Bounds(); box.Contain(B.Bounds()); - box.Expand( 0.5 * blend * box.MaxDim ); + box.Expand(ExpandBounds * box.MaxDim ); return box; } } From e29be6ab91b49987124d1d253e3fd09baa497670 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 22 Feb 2018 11:06:42 -0500 Subject: [PATCH 014/225] add some gs colors --- color/Colorf.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/color/Colorf.cs b/color/Colorf.cs index 46d2e745..b021e19c 100644 --- a/color/Colorf.cs +++ b/color/Colorf.cs @@ -222,6 +222,12 @@ public string ToString(string fmt) + // default colors + static readonly public Colorf StandardBeige = new Colorf(0.75f, 0.75f, 0.5f); + static readonly public Colorf SelectionGold = new Colorf(1.0f, 0.6f, 0.05f); + static readonly public Colorf PivotYellow = new Colorf(1.0f, 1.0f, 0.05f); + + // allow conversion to/from Vector3f public static implicit operator Vector3f(Colorf c) From baf82c11914b3fd7b497d41b55aa0fa3f51e75da Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 23 Feb 2018 10:01:48 -0500 Subject: [PATCH 015/225] documentation updates, deprecated weird-api ToPlaneUV function --- math/Frame3f.cs | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/math/Frame3f.cs b/math/Frame3f.cs index c8fea33b..07514bd2 100644 --- a/math/Frame3f.cs +++ b/math/Frame3f.cs @@ -184,6 +184,9 @@ public void ConstrainedAlignAxis(int nAxis, Vector3f vTo, Vector3f vAround) Rotate(rot); } + /// + /// 3D projection of point p onto frame-axis plane orthogonal to normal axis + /// public Vector3f ProjectToPlane(Vector3f p, int nNormal) { Vector3f d = p - origin; @@ -191,6 +194,10 @@ public Vector3f ProjectToPlane(Vector3f p, int nNormal) return origin + (d - d.Dot(n) * n); } + /// + /// map from 2D coordinates in frame-axes plane perpendicular to normal axis, to 3D + /// [TODO] check that mapping preserves orientation? + /// public Vector3f FromPlaneUV(Vector2f v, int nPlaneNormalAxis) { Vector3f dv = new Vector3f(v[0], v[1], 0); @@ -206,19 +213,38 @@ public Vector3f FromFrameP(Vector2f v, int nPlaneNormalAxis) { return FromPlaneUV(v, nPlaneNormalAxis); } - public Vector2f ToPlaneUV(Vector3f p, int nNormal = 2, int nAxis0 = 0, int nAxis1 = 1) + + /// + /// Project p onto plane axes + /// [TODO] check that mapping preserves orientation? + /// + public Vector2f ToPlaneUV(Vector3f p, int nNormal) { + int nAxis0 = 0, nAxis1 = 1; + if (nNormal == 0) + nAxis0 = 2; + else if (nNormal == 1) + nAxis1 = 2; Vector3f d = p - origin; float fu = d.Dot(GetAxis(nAxis0)); float fv = d.Dot(GetAxis(nAxis1)); return new Vector2f(fu, fv); } + [System.Obsolete("Use explicit ToPlaneUV instead")] + public Vector2f ToPlaneUV(Vector3f p, int nNormal, int nAxis0 = -1, int nAxis1 = -1) + { + if (nAxis0 != -1 || nAxis1 != -1) + throw new Exception("[RMS] was this being used?"); + return ToPlaneUV(p, nNormal); + } + /// distance from p to frame-axes-plane perpendicular to normal axis public float DistanceToPlane(Vector3f p, int nNormal) { return Math.Abs((p - origin).Dot(GetAxis(nNormal))); } + /// signed distance from p to frame-axes-plane perpendicular to normal axis public float DistanceToPlaneSigned(Vector3f p, int nNormal) { return (p - origin).Dot(GetAxis(nNormal)); @@ -309,7 +335,7 @@ public Frame3f FromFrame(Frame3f f) } - + /// Map box *into* local coordinates of Frame public Box3f ToFrame(Box3f box) { box.Center = ToFrameP(box.Center); box.AxisX = ToFrameV(box.AxisX); @@ -317,6 +343,7 @@ public Box3f ToFrame(Box3f box) { box.AxisZ = ToFrameV(box.AxisZ); return box; } + /// Map box *from* local frame coordinates into "world" coordinates public Box3f FromFrame(Box3f box) { box.Center = FromFrameP(box.Center); box.AxisX = FromFrameV(box.AxisX); @@ -324,6 +351,7 @@ public Box3f FromFrame(Box3f box) { box.AxisZ = FromFrameV(box.AxisZ); return box; } + /// Map box *into* local coordinates of Frame public Box3d ToFrame(Box3d box) { box.Center = ToFrameP(box.Center); box.AxisX = ToFrameV(box.AxisX); @@ -331,6 +359,7 @@ public Box3d ToFrame(Box3d box) { box.AxisZ = ToFrameV(box.AxisZ); return box; } + /// Map box *from* local frame coordinates into "world" coordinates public Box3d FromFrame(Box3d box) { box.Center = FromFrameP(box.Center); box.AxisX = FromFrameV(box.AxisX); @@ -358,12 +387,14 @@ public Vector3f RayPlaneIntersection(Vector3f ray_origin, Vector3f ray_direction } - - public static Frame3f Interpolate(Frame3f f1, Frame3f f2, float alpha) + /// + /// Interpolate between two frames - Lerp for origin, Slerp for rotation + /// + public static Frame3f Interpolate(Frame3f f1, Frame3f f2, float t) { return new Frame3f( - Vector3f.Lerp(f1.origin, f2.origin, alpha), - Quaternionf.Slerp(f1.rotation, f2.rotation, alpha) ); + Vector3f.Lerp(f1.origin, f2.origin, t), + Quaternionf.Slerp(f1.rotation, f2.rotation, t) ); } From 38b44ceb0535899b428fbdd3d276a05718606943 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 23 Feb 2018 11:12:17 -0500 Subject: [PATCH 016/225] util functions --- math/Interval1d.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/math/Interval1d.cs b/math/Interval1d.cs index b7c12b95..f9ebb729 100644 --- a/math/Interval1d.cs +++ b/math/Interval1d.cs @@ -91,6 +91,30 @@ public Interval1d IntersectionWith(ref Interval1d o) return new Interval1d(Math.Max(a, o.a), Math.Min(b, o.b)); } + /// + /// clamp value f to interval [a,b] + /// + public double Clamp(double f) { + return (f < a) ? a : (f > b) ? b : f; + } + + /// + /// interpolate between a and b using value t in range [0,1] + /// + public double Interpolate(double t) { + return (1 - t) * a + (t) * b; + } + + /// + /// Convert value into (clamped) t value in range [0,1] + /// + public double GetT(double value) + { + if (value <= a) return 0; + else if (value >= b) return 1; + else if (a == b) return 0.5; + else return (value-a) / (b-a); + } public void Set(Interval1d o) { a = o.a; b = o.b; From 77815a906f62670a5f297865f199784e1294feda Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 26 Feb 2018 11:04:44 -0500 Subject: [PATCH 017/225] add indexing operator to MeshNormals --- mesh/MeshNormals.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mesh/MeshNormals.cs b/mesh/MeshNormals.cs index 23727fde..af20cf7b 100644 --- a/mesh/MeshNormals.cs +++ b/mesh/MeshNormals.cs @@ -40,6 +40,11 @@ public void Compute() } + public Vector3d this[int vid] { + get { return Normals[vid]; } + } + + public void CopyTo(DMesh3 SetMesh) { if (SetMesh.MaxVertexID < Mesh.MaxVertexID) From 64d7d61c26938316e0b19bc5e7db63057d31ddb9 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 27 Feb 2018 11:11:40 -0500 Subject: [PATCH 018/225] handle null argument --- spatial/BasicProjectionTargets.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spatial/BasicProjectionTargets.cs b/spatial/BasicProjectionTargets.cs index fa731f31..c8353ee8 100644 --- a/spatial/BasicProjectionTargets.cs +++ b/spatial/BasicProjectionTargets.cs @@ -15,6 +15,8 @@ public MeshProjectionTarget(DMesh3 mesh, ISpatial spatial) { Mesh = mesh; Spatial = spatial; + if ( Spatial == null ) + Spatial = new DMeshAABBTree3(mesh, true); } public MeshProjectionTarget(DMesh3 mesh) From f5f877f46a0b34ea041a9c4f2f544e0df6552146 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 27 Feb 2018 17:20:33 -0500 Subject: [PATCH 019/225] add frame xforms to TransformSequence (can be replaced w/ rotate and translate, but this is just simpler) --- math/TransformSequence.cs | 40 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/math/TransformSequence.cs b/math/TransformSequence.cs index 2c92a73d..10d51af4 100644 --- a/math/TransformSequence.cs +++ b/math/TransformSequence.cs @@ -22,14 +22,16 @@ enum XFormType QuaterionRotation = 1, QuaternionRotateAroundPoint = 2, Scale = 3, - ScaleAroundPoint = 4 + ScaleAroundPoint = 4, + ToFrame = 5, + FromFrame = 6 } struct XForm { public XFormType type; public Vector3dTuple3 data; - + // may need to update these to handle other types... public Vector3d Translation { get { return data.V0; } @@ -43,6 +45,9 @@ public Quaternionf Quaternion { public Vector3d RotateOrigin { get { return data.V2; } } + public Frame3f Frame { + get { return new Frame3f((Vector3f)RotateOrigin, Quaternion); } + } } List Operations; @@ -103,6 +108,23 @@ public void AppendScale(Vector3d s, Vector3d aroundPt) }); } + public void AppendToFrame(Frame3f frame) + { + Quaternionf q = frame.Rotation; + Operations.Add(new XForm() { + type = XFormType.ToFrame, + data = new Vector3dTuple3(new Vector3d(q.x, q.y, q.z), new Vector3d(q.w, 0, 0), frame.Origin) + }); + } + + public void AppendFromFrame(Frame3f frame) + { + Quaternionf q = frame.Rotation; + Operations.Add(new XForm() { + type = XFormType.FromFrame, + data = new Vector3dTuple3(new Vector3d(q.x, q.y, q.z), new Vector3d(q.w, 0, 0), frame.Origin) + }); + } /// @@ -137,6 +159,14 @@ public Vector3d TransformP(Vector3d p) p += Operations[i].RotateOrigin; break; + case XFormType.ToFrame: + p = Operations[i].Frame.ToFrameP(p); + break; + + case XFormType.FromFrame: + p = Operations[i].Frame.FromFrameP(p); + break; + default: throw new NotImplementedException("TransformSequence.TransformP: unhandled type!"); } @@ -145,6 +175,12 @@ public Vector3d TransformP(Vector3d p) return p; } + /// + /// Apply transforms to point + /// + public Vector3f TransformP(Vector3f p) { + return (Vector3f)TransformP((Vector3d)p); + } From 475a351a0dfc97b0ca9e4c124ccd80eff5d4d31b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 27 Feb 2018 22:55:12 -0500 Subject: [PATCH 020/225] comments and a few minor optimizations --- mesh/DMesh3.cs | 129 +++++++++++++++++++++++++++++++++------- mesh/MeshConstraints.cs | 5 ++ mesh/MeshRefinerBase.cs | 6 +- mesh/MeshUtil.cs | 13 +++- mesh/Remesher.cs | 3 +- 5 files changed, 129 insertions(+), 27 deletions(-) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 19b90a31..128198a7 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -321,9 +321,17 @@ void updateTimeStamp(bool bShapeChange) { if (bShapeChange) shape_timestamp++; } + + /// + /// Timestamp is incremented any time any change is made to the mesh + /// public int Timestamp { get { return timestamp; } } + + /// + /// ShapeTimestamp is incremented any time any vertex position is changed or the mesh topology is modified + /// public int ShapeTimestamp { get { return shape_timestamp; } } @@ -1156,6 +1164,9 @@ public IEnumerable EdgeIndices() { } + /// + /// Enumerate ids of boundary edges + /// public IEnumerable BoundaryEdgeIndices() { foreach ( int eid in edges_refcount ) { if (edges[4 * eid + 3] == InvalidID) @@ -1164,12 +1175,19 @@ public IEnumerable BoundaryEdgeIndices() { } + /// + /// Enumerate vertices + /// public IEnumerable Vertices() { foreach (int vid in vertices_refcount) { int i = 3 * vid; yield return new Vector3d(vertices[i], vertices[i + 1], vertices[i + 2]); } } + + /// + /// Enumerate triangles + /// public IEnumerable Triangles() { foreach (int tid in triangles_refcount) { int i = 3 * tid; @@ -1177,7 +1195,9 @@ public IEnumerable Triangles() { } } - // return value is [v0,v1,t0,t1], where t1 will be InvalidID if this is a boundary edge + /// + /// Enumerage edges. return value is [v0,v1,t0,t1], where t1 will be InvalidID if this is a boundary edge + /// public IEnumerable Edges() { foreach (int eid in edges_refcount) { int i = 4 * eid; @@ -1188,21 +1208,30 @@ public IEnumerable Edges() { // queries - // linear search through edges of vA + /// + /// Find edgeid for edge [a,b] + /// public int FindEdge(int vA, int vB) { debug_check_is_vertex(vA); debug_check_is_vertex(vB); return find_edge(vA, vB); } - // faster than FindEdge - public int FindEdgeFromTri(int vA, int vB, int t) { - return find_edge_from_tri(vA, vB, t); + /// + /// Find edgeid for edge [a,b] from triangle that contains the edge. + /// This is faster than FindEdge() because it is constant-time + /// + public int FindEdgeFromTri(int vA, int vB, int tID) { + return find_edge_from_tri(vA, vB, tID); } - // [RMS] this does more work than necessary, see (??? comment never finished...) + /// + /// If edge has vertices [a,b], and is connected two triangles [a,b,c] and [a,b,d], + /// this returns [c,d], or [c,InvalidID] for a boundary edge + /// public Index2i GetEdgeOpposingV(int eID) { + // [TODO] there was a comment here saying this does more work than necessary?? // ** it is important that verts returned maintain [c,d] order!! int i = 4*eID; int a = edges[i], b = edges[i + 1]; @@ -1216,6 +1245,9 @@ public Index2i GetEdgeOpposingV(int eID) } + /// + /// Find triangle made up of any permutation of vertices [a,b,c] + /// public int FindTriangle(int a, int b, int c) { int eid = find_edge(a, b); @@ -1238,6 +1270,9 @@ public int FindTriangle(int a, int b, int c) + /// + /// Enumerate "other" vertices of edges connected to vertex (ie vertex one-ring) + /// public IEnumerable VtxVerticesItr(int vID) { if ( vertices_refcount.isValid(vID) ) { foreach ( int eid in vertex_edges.ValueItr(vID) ) @@ -1246,6 +1281,9 @@ public IEnumerable VtxVerticesItr(int vID) { } + /// + /// Enumerate edge ids connected to vertex (ie edge one-ring) + /// public IEnumerable VtxEdgesItr(int vID) { if ( vertices_refcount.isValid(vID) ) { return vertex_edges.ValueItr(vID); @@ -1280,6 +1318,7 @@ public int VtxBoundaryEdges(int vID, ref int e0, ref int e1) } /// + /// Find edge ids of boundary edges connected to vertex. /// e needs to be large enough (ie call VtxBoundaryEdges, or as large as max one-ring) /// returns count, ie number of elements of e that were filled /// @@ -1299,7 +1338,10 @@ public int VtxAllBoundaryEdges(int vID, int[] e) } - + /// + /// Get triangle one-ring at vertex. + /// bUseOrientation is more efficient but returns incorrect result if vertex is a bowtie + /// public MeshResult GetVtxTriangles(int vID, List vTriangles, bool bUseOrientation) { if (!IsVertex(vID)) @@ -1363,6 +1405,9 @@ public int GetVtxTriangleCount(int vID, bool bBruteForce = false) } + /// + /// iterate over triangle IDs of vertex one-ring + /// public IEnumerable VtxTrianglesItr(int vID) { if ( IsVertex(vID) ) { foreach (int eid in vertex_edges.ValueItr(vID)) { @@ -1379,9 +1424,10 @@ public IEnumerable VtxTrianglesItr(int vID) { } - - // from edge and vert, returns other vert, two opposing verts, and two triangles - public void GetVtxNbrhood(int eID, int vID, ref int vOther, ref int oppV1, ref int oppV2, ref int t1, ref int t2) + /// + /// from edge and vert, returns other vert, two opposing verts, and two triangles + /// + public void GetVtxNbrhood(int eID, int vID, ref int vOther, ref int oppV1, ref int oppV2, ref int t1, ref int t2) { int i = 4*eID; vOther = (edges[i] == vID) ? edges[i+1] : edges[i]; @@ -1395,6 +1441,29 @@ public void GetVtxNbrhood(int eID, int vID, ref int vOther, ref int oppV1, ref i } + /// + /// Fastest possible one-ring centroid. This is used inside many other algorithms + /// so it helps to have it be maximally efficient + /// + public void VtxOneRingCentroid(int vID, ref Vector3d centroid) + { + centroid = Vector3d.Zero; + if (vertices_refcount.isValid(vID)) { + int n = 0; + foreach (int eid in vertex_edges.ValueItr(vID)) { + int other_idx = 3 * edge_other_v(eid, vID); + centroid.x += vertices[other_idx]; + centroid.y += vertices[other_idx + 1]; + centroid.z += vertices[other_idx + 2]; + n++; + } + double d = 1.0 / n; + centroid.x *= d; centroid.y *= d; centroid.z *= d; + } + } + + + public bool tri_has_v(int tID, int vID) { int i = 3*tID; return triangles[i] == vID @@ -1493,6 +1562,9 @@ public bool IsBoundaryVertex(int vID) { } + /// + /// Returns true if any edge of triangle is a boundary edge + /// public bool IsBoundaryTriangle(int tID) { debug_check_is_triangle(tID); @@ -1541,6 +1613,9 @@ int find_edge_from_tri(int vA, int vB, int tID) // queries + /// + /// Returns true if the two triangles connected to edge have different group IDs + /// public bool IsGroupBoundaryEdge(int eID) { if ( IsEdge(eID) == false ) @@ -1557,7 +1632,9 @@ public bool IsGroupBoundaryEdge(int eID) } - // returns true if vertex has more than one tri groups in its tri nbrhood + /// + /// returns true if vertex has more than one tri group in its tri nbrhood + /// public bool IsGroupBoundaryVertex(int vID) { if (IsVertex(vID) == false) @@ -1586,7 +1663,9 @@ public bool IsGroupBoundaryVertex(int vID) - // returns true if more than two group border edges meet at vertex + /// + /// returns true if more than two group boundary edges meet at vertex (ie 3+ groups meet at this vertex) + /// public bool IsGroupJunctionVertex(int vID) { if (IsVertex(vID) == false) @@ -1615,7 +1694,7 @@ public bool IsGroupJunctionVertex(int vID) /// - /// returns up to 4 group IDs at input vid. Returns false if > 4 encountered + /// returns up to 4 group IDs at vertex. Returns false if > 4 encountered /// public bool GetVertexGroups(int vID, out Index4i groups) { @@ -1648,7 +1727,7 @@ public bool GetVertexGroups(int vID, out Index4i groups) /// - /// returns up to 4 group IDs at input vid. Returns false if > 4 encountered + /// returns all group IDs at vertex /// public bool GetAllVertexGroups(int vID, ref List groups) { @@ -1694,7 +1773,9 @@ public bool IsBowtieVertex(int vID) } - // compute vertex bounding box + /// + /// Computes bounding box of all vertices. + /// public AxisAlignedBox3d GetBounds() { double x = 0, y = 0, z = 0; @@ -1715,7 +1796,9 @@ public AxisAlignedBox3d GetBounds() AxisAlignedBox3d cached_bounds; int cached_bounds_timestamp = -1; - //! cached bounding box, lazily re-computed on access if mesh has changed + /// + /// cached bounding box, lazily re-computed on access if mesh has changed + /// public AxisAlignedBox3d CachedBounds { get { @@ -1731,7 +1814,7 @@ public AxisAlignedBox3d CachedBounds bool cached_is_closed = false; - int cached_is_closed_timstamp = -1; + int cached_is_closed_timestamp = -1; public bool IsClosed() { if (TriangleCount == 0) @@ -1752,9 +1835,9 @@ public bool IsClosed() { public bool CachedIsClosed { get { - if (cached_is_closed_timstamp != Timestamp) { + if (cached_is_closed_timestamp != Timestamp) { cached_is_closed = IsClosed(); - cached_is_closed_timstamp = Timestamp; + cached_is_closed_timestamp = Timestamp; } return cached_is_closed; } @@ -1882,9 +1965,11 @@ public SmallListSet VertexEdges { - // assumes that we have initialized vertices, triangles, and edges buffers, - // and edges refcounts. Rebuilds vertex and tri refcounts, triangle edges, - // vertex edges + /// + /// Rebuild mesh topology. + /// assumes that we have initialized vertices, triangles, and edges buffers, + /// and edges refcounts. Rebuilds vertex and tri refcounts, triangle edges, vertex edges. + /// public void RebuildFromEdgeRefcounts() { int MaxVID = vertices.Length / 3; diff --git a/mesh/MeshConstraints.cs b/mesh/MeshConstraints.cs index 490afd3e..79a856b1 100644 --- a/mesh/MeshConstraints.cs +++ b/mesh/MeshConstraints.cs @@ -170,6 +170,11 @@ public VertexConstraint GetVertexConstraint(int vid) return VertexConstraint.Unconstrained; } + public bool GetVertexConstraint(int vid, ref VertexConstraint vc) + { + return Vertices.TryGetValue(vid, out vc); + } + public void SetOrUpdateVertexConstraint(int vid, VertexConstraint vc) { Vertices[vid] = vc; diff --git a/mesh/MeshRefinerBase.cs b/mesh/MeshRefinerBase.cs index a0a8f8a9..49bdb13a 100644 --- a/mesh/MeshRefinerBase.cs +++ b/mesh/MeshRefinerBase.cs @@ -235,7 +235,11 @@ protected VertexConstraint get_vertex_constraint(int vid) return constraints.GetVertexConstraint(vid); return VertexConstraint.Unconstrained; } - + protected bool get_vertex_constraint(int vid, ref VertexConstraint vc) + { + return (constraints == null) ? false : + constraints.GetVertexConstraint(vid, ref vc); + } } } diff --git a/mesh/MeshUtil.cs b/mesh/MeshUtil.cs index d378ea9c..a6582150 100644 --- a/mesh/MeshUtil.cs +++ b/mesh/MeshUtil.cs @@ -13,9 +13,16 @@ public static class MeshUtil { public static Vector3d UniformSmooth(DMesh3 mesh, int vID, double t) { Vector3d v = mesh.GetVertex(vID); - Vector3d c = MeshWeights.OneRingCentroid(mesh, vID); - return (1-t)*v + (t)*c; - } + //Vector3d c = MeshWeights.OneRingCentroid(mesh, vID); + //return (1 - t) * v + (t) * c; + Vector3d c = Vector3d.Zero; + mesh.VtxOneRingCentroid(vID, ref c); + double s = 1.0 - t; + v.x = s * v.x + t * c.x; + v.y = s * v.y + t * c.y; + v.z = s * v.z + t * c.z; + return v; + } // t in range [0,1] public static Vector3d MeanValueSmooth(DMesh3 mesh, int vID, double t) diff --git a/mesh/Remesher.cs b/mesh/Remesher.cs index 4c06fbd5..73c867e2 100644 --- a/mesh/Remesher.cs +++ b/mesh/Remesher.cs @@ -604,7 +604,8 @@ protected virtual void ApplyVertexBuffer(bool bParallel) protected virtual Vector3d ComputeSmoothedVertexPos(int vID, Func smoothFunc, out bool bModified) { bModified = false; - VertexConstraint vConstraint = get_vertex_constraint(vID); + VertexConstraint vConstraint = VertexConstraint.Unconstrained; + get_vertex_constraint(vID, ref vConstraint); if (vConstraint.Fixed) return Mesh.GetVertex(vID); VertexControl vControl = (VertexControlF == null) ? VertexControl.AllowAll : VertexControlF(vID); From f3ba841d05a594319d6ba57b72a73ae1fb8b24e7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 27 Feb 2018 23:05:07 -0500 Subject: [PATCH 021/225] this is better? --- core/Util.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Util.cs b/core/Util.cs index 8009ded3..02e9121a 100644 --- a/core/Util.cs +++ b/core/Util.cs @@ -189,7 +189,7 @@ static public string ToSecMilli(TimeSpan t) #if G3_USING_UNITY return string.Format("{0}", t.TotalSeconds); #else - return t.ToString("ss\\.ffff"); + return string.Format("{0:F5}", t.TotalSeconds); #endif } From 73bab152c61c7c5abadf1c82f00945f1f42bfa2b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 1 Mar 2018 23:47:26 -0500 Subject: [PATCH 022/225] added byte array conversions and byte array compress/decompress --- core/BufferUtil.cs | 122 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 7 deletions(-) diff --git a/core/BufferUtil.cs b/core/BufferUtil.cs index b1511875..29936e27 100644 --- a/core/BufferUtil.cs +++ b/core/BufferUtil.cs @@ -1,14 +1,19 @@ using System; using System.Collections.Generic; using System.Linq; +using System.IO; +using System.IO.Compression; using System.Text; namespace g3 { - // - // convenience functions for setting values in an array in sets of 2/3 - // (eg for arrays that are actually a list of vectors) - // + /// + /// Convenience functions for working with arrays. + /// - Math functions on arrays of floats/doubles + /// - "automatic" conversion from IEnumerable (via introspection) + /// - byte[] conversions + /// - zlib compress/decompress byte[] buffers + /// public class BufferUtil { static public void SetVertex3(double[] v, int i, double x, double y, double z) { @@ -302,14 +307,117 @@ static public Index3i[] ToIndex3i(IEnumerable values) { + /// + /// convert byte array to int array + /// + static public int[] ToInt(byte[] buffer) + { + int sz = sizeof(int); + int Nvals = buffer.Length / sz; + int[] v = new int[Nvals]; + for (int i = 0; i < Nvals; i++) { + v[i] = BitConverter.ToInt32(buffer, i * sz); + } + return v; + } + + + /// + /// convert byte array to short array + /// + static public short[] ToShort(byte[] buffer) + { + int sz = sizeof(short); + int Nvals = buffer.Length / sz; + short[] v = new short[Nvals]; + for (int i = 0; i < Nvals; i++) { + v[i] = BitConverter.ToInt16(buffer, i * sz); + } + return v; + } + + + /// + /// convert byte array to double array + /// + static public double[] ToDouble(byte[] buffer) + { + int sz = sizeof(double); + int Nvals = buffer.Length / sz; + double[] v = new double[Nvals]; + for (int i = 0; i < Nvals; i++) { + v[i] = BitConverter.ToDouble(buffer, i * sz); + } + return v; + } + + + /// + /// convert byte array to float array + /// + static public float[] ToFloat(byte[] buffer) + { + int sz = sizeof(float); + int Nvals = buffer.Length / sz; + float[] v = new float[Nvals]; + for (int i = 0; i < Nvals; i++) { + v[i] = BitConverter.ToSingle(buffer, i * sz); + } + return v; + } + + + + /// + /// Compress a byte buffer using Deflate/ZLib compression. + /// + static public byte[] CompressZLib(byte[] buffer, bool bFast) + { + MemoryStream ms = new MemoryStream(); + DeflateStream zip = new DeflateStream(ms, (bFast) ? CompressionLevel.Fastest : CompressionLevel.Optimal, true); + zip.Write(buffer, 0, buffer.Length); + zip.Close(); + ms.Position = 0; + + byte[] compressed = new byte[ms.Length]; + ms.Read(compressed, 0, compressed.Length); + + byte[] zBuffer = new byte[compressed.Length + 4]; + Buffer.BlockCopy(compressed, 0, zBuffer, 4, compressed.Length); + Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, zBuffer, 0, 4); + return zBuffer; + } + + + /// + /// Decompress a byte buffer that has been compressed using Deflate/ZLib compression + /// + static public byte[] DecompressZLib(byte[] zBuffer) + { + MemoryStream ms = new MemoryStream(); + int msgLength = BitConverter.ToInt32(zBuffer, 0); + ms.Write(zBuffer, 4, zBuffer.Length - 4); + + byte[] buffer = new byte[msgLength]; + + ms.Position = 0; + DeflateStream zip = new DeflateStream(ms, CompressionMode.Decompress); + zip.Read(buffer, 0, buffer.Length); + + return buffer; + } + + } - // utility class for porting C++ code that uses this kind of idiom: - // T * ptr = &array[i]; - // ptr[k] = value + /// + /// utility class for porting C++ code that uses this kind of idiom: + /// T * ptr = &array[i]; + /// ptr[k] = value + /// public struct ArrayAlias { public T[] Source; From fc56ebd2097289c3f09c684e6dfd9e7e2e3f810b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 2 Mar 2018 15:53:49 -0500 Subject: [PATCH 023/225] additional byte array conversions --- core/BufferUtil.cs | 98 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/core/BufferUtil.cs b/core/BufferUtil.cs index 29936e27..1dc5a938 100644 --- a/core/BufferUtil.cs +++ b/core/BufferUtil.cs @@ -367,6 +367,104 @@ static public float[] ToFloat(byte[] buffer) } + /// + /// convert byte array to VectorArray3d + /// + static public VectorArray3d ToVectorArray3d(byte[] buffer) + { + int sz = sizeof(double); + int Nvals = buffer.Length / sz; + int Nvecs = Nvals / 3; + VectorArray3d v = new VectorArray3d(Nvecs); + for (int i = 0; i < Nvecs; i++) { + double x = BitConverter.ToDouble(buffer, (3 * i) * sz); + double y = BitConverter.ToDouble(buffer, (3 * i + 1) * sz); + double z = BitConverter.ToDouble(buffer, (3 * i + 2) * sz); + v.Set(i, x, y, z); + } + return v; + } + + + + /// + /// convert byte array to VectorArray2f + /// + static public VectorArray2f ToVectorArray2f(byte[] buffer) + { + int sz = sizeof(float); + int Nvals = buffer.Length / sz; + int Nvecs = Nvals / 2; + VectorArray2f v = new VectorArray2f(Nvecs); + for (int i = 0; i < Nvecs; i++) { + float x = BitConverter.ToSingle(buffer, (2 * i) * sz); + float y = BitConverter.ToSingle(buffer, (2 * i + 1) * sz); + v.Set(i, x, y); + } + return v; + } + + /// + /// convert byte array to VectorArray3f + /// + static public VectorArray3f ToVectorArray3f(byte[] buffer) + { + int sz = sizeof(float); + int Nvals = buffer.Length / sz; + int Nvecs = Nvals / 3; + VectorArray3f v = new VectorArray3f(Nvecs); + for (int i = 0; i < Nvecs; i++) { + float x = BitConverter.ToSingle(buffer, (3 * i) * sz); + float y = BitConverter.ToSingle(buffer, (3 * i + 1) * sz); + float z = BitConverter.ToSingle(buffer, (3 * i + 2) * sz); + v.Set(i, x, y, z); + } + return v; + } + + + + + /// + /// convert byte array to VectorArray3i + /// + static public VectorArray3i ToVectorArray3i(byte[] buffer) + { + int sz = sizeof(int); + int Nvals = buffer.Length / sz; + int Nvecs = Nvals / 3; + VectorArray3i v = new VectorArray3i(Nvecs); + for (int i = 0; i < Nvecs; i++) { + int x = BitConverter.ToInt32(buffer, (3 * i) * sz); + int y = BitConverter.ToInt32(buffer, (3 * i + 1) * sz); + int z = BitConverter.ToInt32(buffer, (3 * i + 2) * sz); + v.Set(i, x, y, z); + } + return v; + } + + + /// + /// convert byte array to IndexArray4i + /// + static public IndexArray4i ToIndexArray4i(byte[] buffer) + { + int sz = sizeof(int); + int Nvals = buffer.Length / sz; + int Nvecs = Nvals / 4; + IndexArray4i v = new IndexArray4i(Nvecs); + for (int i = 0; i < Nvecs; i++) { + int a = BitConverter.ToInt32(buffer, (4 * i) * sz); + int b = BitConverter.ToInt32(buffer, (4 * i + 1) * sz); + int c = BitConverter.ToInt32(buffer, (4 * i + 2) * sz); + int d = BitConverter.ToInt32(buffer, (4 * i + 3) * sz); + v.Set(i, a, b, c, d); + } + return v; + } + + + /// /// Compress a byte buffer using Deflate/ZLib compression. From 11b16c14d14074ff4f4a1b36c002484ab641d2e7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 4 Mar 2018 21:08:21 -0500 Subject: [PATCH 024/225] avoid infinite loop in PolySimplification2. Polygon2d.SetVertices() --- curve/PolySimplification2.cs | 29 +++++++++++++++++++++++++++-- curve/Polygon2d.cs | 14 ++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/curve/PolySimplification2.cs b/curve/PolySimplification2.cs index 9836ed3d..1b83893e 100644 --- a/curve/PolySimplification2.cs +++ b/curve/PolySimplification2.cs @@ -13,7 +13,7 @@ namespace g3 /// which is not ideal in many contexts (eg manufacturing). /// /// Strategy here is : - /// 1) runs of vertices that are very close to straight lines (default 0.01mm deviation tol) + /// 1) find runs of vertices that are very close to straight lines (default 0.01mm deviation tol) /// 2) find all straight segments longer than threshold distance (default 2mm) /// 3) discard vertices that deviate less than tolerance (default = 0.2mm) /// from sequential-points-segment, unless they are required to preserve @@ -64,6 +64,27 @@ public PolySimplification2(PolyLine2d polycurve) } + + /// + /// simplify outer and holes of a polygon solid with same thresholds + /// + public static void Simplify(GeneralPolygon2d solid, double deviationThresh) + { + PolySimplification2 simp = new PolySimplification2(solid.Outer); + simp.SimplifyDeviationThreshold = deviationThresh; + simp.Simplify(); + solid.Outer.SetVertices(simp.Result, true); + + foreach (var hole in solid.Holes) { + PolySimplification2 holesimp = new PolySimplification2(hole); + holesimp.SimplifyDeviationThreshold = deviationThresh; + holesimp.Simplify(); + hole.SetVertices(holesimp.Result, true); + } + } + + + public void Simplify() { bool[] keep_seg = new bool[Vertices.Count]; @@ -122,8 +143,12 @@ List collapse_by_deviation_tol(List input, bool[] keep_segme } result.Add(input[i1]); last_i = i1; - cur_i = i1; skip_count = 0; + if (i1 == 0) { + cur_i = NStop; + } else { + cur_i = i1; + } continue; } diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 81ad24fc..7e300f99 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -96,6 +96,20 @@ public void RemoveVertex(int idx) Timestamp++; } + + public void SetVertices(List newVertices, bool bTakeOwnership) + { + if ( bTakeOwnership) { + vertices = newVertices; + } else { + vertices.Clear(); + int N = newVertices.Count; + for (int i = 0; i < N; ++i) + vertices.Add(newVertices[i]); + } + } + + public void Reverse() { vertices.Reverse(); From b00be7e1ebd237c9e206b05963275cf0406c6c2c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 5 Mar 2018 10:38:48 -0500 Subject: [PATCH 025/225] try to handle degenerate-geometry cases --- curve/PolySimplification2.cs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/curve/PolySimplification2.cs b/curve/PolySimplification2.cs index 1b83893e..acce4bdb 100644 --- a/curve/PolySimplification2.cs +++ b/curve/PolySimplification2.cs @@ -138,8 +138,10 @@ List collapse_by_deviation_tol(List input, bool[] keep_segme if ( keep_segments[i0] ) { if (last_i != i0) { - Util.gDevAssert(input[i0].Distance(result[result.Count - 1]) > MathUtil.Epsilonf); - result.Add(input[i0]); + // skip join segment if it is degenerate + double join_dist = input[i0].Distance(result[result.Count - 1]); + if ( join_dist > MathUtil.Epsilon) + result.Add(input[i0]); } result.Add(input[i1]); last_i = i1; @@ -177,12 +179,16 @@ List collapse_by_deviation_tol(List input, bool[] keep_segme } - if ( IsLoop ) { + if ( IsLoop ) { + // if we skipped everything, rest of code doesn't work + if (result.Count < 3) + return handle_tiny_case(result, input, keep_segments, offset_threshold); + Line2d last_line = Line2d.FromPoints(input[last_i], input[cur_i % N]); bool collinear_startv = last_line.DistanceSquared(result[0]) < thresh_sqr; bool collinear_starts = last_line.DistanceSquared(result[1]) < thresh_sqr; - if (collinear_startv && collinear_starts) { - // last seg is collinaer w/ start seg, merge them + if (collinear_startv && collinear_starts && result.Count > 3) { + // last seg is collinear w/ start seg, merge them result[0] = input[last_i]; result.RemoveAt(result.Count - 1); @@ -202,5 +208,20 @@ List collapse_by_deviation_tol(List input, bool[] keep_segme } + + List handle_tiny_case(List result, List input, bool[] keep_segments, double offset_threshold) + { + int N = input.Count; + if (N == 3) + return input; // not much we can really do here... + + result.Clear(); + result.Add(input[0]); + result.Add(input[N/3]); + result.Add(input[N-N/3]); + return result; + } + + } } From f74e5a901638238ef36fb1215cfbd941a6b4b8b5 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 6 Mar 2018 11:49:06 -0500 Subject: [PATCH 026/225] early-out if size is the same --- core/DVector.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/DVector.cs b/core/DVector.cs index 3c488093..5fb3064e 100644 --- a/core/DVector.cs +++ b/core/DVector.cs @@ -149,6 +149,9 @@ public void insertAt(T value, int index) { public void resize(int count) { + if (Length == count) + return; + // figure out how many segments we need int nNumSegs = 1 + (int)count / nBlockSize; From fb14d577c9a6976058379538df1704acb4498488 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 6 Mar 2018 12:35:10 -0500 Subject: [PATCH 027/225] StandardMeshWriter now allows client to override streams that are written to. --- io/OBJWriter.cs | 122 ++++++++++++++++++++++----------------- io/StandardMeshWriter.cs | 120 ++++++++++++++++++++++++++------------ 2 files changed, 151 insertions(+), 91 deletions(-) diff --git a/io/OBJWriter.cs b/io/OBJWriter.cs index a22c0b4e..0e2c0cf1 100644 --- a/io/OBJWriter.cs +++ b/io/OBJWriter.cs @@ -15,6 +15,14 @@ namespace g3 /// public class OBJWriter : IMeshWriter { + // stream-opener. Override to write to something other than a file. + public Func OpenStreamF = (sFilename) => { + return File.Open(sFilename, FileMode.Create); + }; + public Action CloseStreamF = (stream) => { + stream.Dispose(); + }; + public string GroupNamePrefix = "mmGroup"; // default, compatible w/ meshmixer public Func GroupNameF = null; // use this to replace standard group names w/ your own @@ -233,61 +241,67 @@ void write_tri(TextWriter writer, ref Index3i t, bool bNormals, bool bUVs, ref I // write .mtl file IOWriteResult write_materials(List vMaterials, WriteOptions options) { - StreamWriter w = new StreamWriter(options.MaterialFilePath); - if (w.BaseStream == null) - return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + options.MaterialFilePath + " for writing"); - - foreach ( GenericMaterial gmat in vMaterials ) { - if ( gmat is OBJMaterial == false ) - continue; - OBJMaterial mat = gmat as OBJMaterial; - - w.WriteLine("newmtl {0}", mat.name); - if ( mat.Ka != GenericMaterial.Invalid ) - w.WriteLine("Ka {0} {1} {2}", mat.Ka.x, mat.Ka.y, mat.Ka.z); - if ( mat.Kd != GenericMaterial.Invalid) - w.WriteLine("Kd {0} {1} {2}", mat.Kd.x, mat.Kd.y, mat.Kd.z); - if ( mat.Ks != GenericMaterial.Invalid ) - w.WriteLine("Ks {0} {1} {2}", mat.Ks.x, mat.Ks.y, mat.Ks.z); - if ( mat.Ke != GenericMaterial.Invalid ) - w.WriteLine("Ke {0} {1} {2}", mat.Ke.x, mat.Ke.y, mat.Ke.z); - if ( mat.Tf != GenericMaterial.Invalid ) - w.WriteLine("Tf {0} {1} {2}", mat.Tf.x, mat.Tf.y, mat.Tf.z); - if ( mat.d != Single.MinValue ) - w.WriteLine("d {0}", mat.d); - if ( mat.Ns != Single.MinValue ) - w.WriteLine("Ns {0}", mat.Ns); - if ( mat.Ni != Single.MinValue ) - w.WriteLine("Ni {0}", mat.Ni); - if ( mat.sharpness != Single.MinValue ) - w.WriteLine("sharpness {0}", mat.sharpness); - if ( mat.illum != -1 ) - w.WriteLine("illum {0}", mat.illum); - - if ( mat.map_Ka != null && mat.map_Ka != "" ) - w.WriteLine("map_Ka {0}", mat.map_Ka); - if ( mat.map_Kd != null && mat.map_Kd != "" ) - w.WriteLine("map_Kd {0}", mat.map_Kd); - if ( mat.map_Ks != null && mat.map_Ks != "" ) - w.WriteLine("map_Ks {0}", mat.map_Ks); - if ( mat.map_Ke != null && mat.map_Ke != "" ) - w.WriteLine("map_Ke {0}", mat.map_Ke); - if ( mat.map_d != null && mat.map_d != "" ) - w.WriteLine("map_d {0}", mat.map_d); - if ( mat.map_Ns != null && mat.map_Ns != "" ) - w.WriteLine("map_Ns {0}", mat.map_Ns); + Stream stream = OpenStreamF(options.MaterialFilePath); + if (stream == null) + return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + options.MaterialFilePath + " for writing"); + + + try { + StreamWriter w = new StreamWriter(stream); + + foreach ( GenericMaterial gmat in vMaterials ) { + if ( gmat is OBJMaterial == false ) + continue; + OBJMaterial mat = gmat as OBJMaterial; + + w.WriteLine("newmtl {0}", mat.name); + if ( mat.Ka != GenericMaterial.Invalid ) + w.WriteLine("Ka {0} {1} {2}", mat.Ka.x, mat.Ka.y, mat.Ka.z); + if ( mat.Kd != GenericMaterial.Invalid) + w.WriteLine("Kd {0} {1} {2}", mat.Kd.x, mat.Kd.y, mat.Kd.z); + if ( mat.Ks != GenericMaterial.Invalid ) + w.WriteLine("Ks {0} {1} {2}", mat.Ks.x, mat.Ks.y, mat.Ks.z); + if ( mat.Ke != GenericMaterial.Invalid ) + w.WriteLine("Ke {0} {1} {2}", mat.Ke.x, mat.Ke.y, mat.Ke.z); + if ( mat.Tf != GenericMaterial.Invalid ) + w.WriteLine("Tf {0} {1} {2}", mat.Tf.x, mat.Tf.y, mat.Tf.z); + if ( mat.d != Single.MinValue ) + w.WriteLine("d {0}", mat.d); + if ( mat.Ns != Single.MinValue ) + w.WriteLine("Ns {0}", mat.Ns); + if ( mat.Ni != Single.MinValue ) + w.WriteLine("Ni {0}", mat.Ni); + if ( mat.sharpness != Single.MinValue ) + w.WriteLine("sharpness {0}", mat.sharpness); + if ( mat.illum != -1 ) + w.WriteLine("illum {0}", mat.illum); + + if ( mat.map_Ka != null && mat.map_Ka != "" ) + w.WriteLine("map_Ka {0}", mat.map_Ka); + if ( mat.map_Kd != null && mat.map_Kd != "" ) + w.WriteLine("map_Kd {0}", mat.map_Kd); + if ( mat.map_Ks != null && mat.map_Ks != "" ) + w.WriteLine("map_Ks {0}", mat.map_Ks); + if ( mat.map_Ke != null && mat.map_Ke != "" ) + w.WriteLine("map_Ke {0}", mat.map_Ke); + if ( mat.map_d != null && mat.map_d != "" ) + w.WriteLine("map_d {0}", mat.map_d); + if ( mat.map_Ns != null && mat.map_Ns != "" ) + w.WriteLine("map_Ns {0}", mat.map_Ns); - if ( mat.bump != null && mat.bump != "" ) - w.WriteLine("bump {0}", mat.bump); - if ( mat.disp != null && mat.disp != "" ) - w.WriteLine("disp {0}", mat.disp); - if ( mat.decal != null && mat.decal != "" ) - w.WriteLine("decal {0}", mat.decal); - if ( mat.refl != null && mat.refl != "" ) - w.WriteLine("refl {0}", mat.refl); - } - - w.Close(); + if ( mat.bump != null && mat.bump != "" ) + w.WriteLine("bump {0}", mat.bump); + if ( mat.disp != null && mat.disp != "" ) + w.WriteLine("disp {0}", mat.disp); + if ( mat.decal != null && mat.decal != "" ) + w.WriteLine("decal {0}", mat.decal); + if ( mat.refl != null && mat.refl != "" ) + w.WriteLine("refl {0}", mat.refl); + } + + } finally { + CloseStreamF(stream); + } return IOWriteResult.Ok; } diff --git a/io/StandardMeshWriter.cs b/io/StandardMeshWriter.cs index 70db0f75..36b76313 100644 --- a/io/StandardMeshWriter.cs +++ b/io/StandardMeshWriter.cs @@ -6,8 +6,19 @@ namespace g3 { + /// + /// Writes various mesh file formats. Format is determined from extension. Currently supports: + /// * .obj : Wavefront OBJ Format https://en.wikipedia.org/wiki/Wavefront_.obj_file + /// * .stl : ascii and binary STL formats https://en.wikipedia.org/wiki/STL_(file_format) + /// * .off : OFF format https://en.wikipedia.org/wiki/OFF_(file_format) + /// * .g3mesh : internal binary format for packed DMesh3 objects + /// + /// Each of these is implemented in a separate Writer class, eg OBJWriter, STLWriter, etc + /// + /// public class StandardMeshWriter : IDisposable { + /// /// If the mesh format we are writing is text, then the OS will write in the number style /// of the current language. So in Germany, numbers are written 1,00 instead of 1.00, for example. @@ -16,6 +27,27 @@ public class StandardMeshWriter : IDisposable public bool WriteInvariantCulture = true; + + /// + /// By default we write to files, but if you would like to write to some other + /// Stream type (eg MemoryStream), you can replace this function. + /// We also pass this function down into the XYZWriter classes + /// that need to write additional files (eg OBJ mesh) + /// + public Func OpenStreamF = (sFilename) => { + return File.Open(sFilename, FileMode.Create); + }; + + /// + /// called on Streams returned by OpenStreamF when we are done with them. + /// + public Action CloseStreamF = (stream) => { + stream.Dispose(); + }; + + + + public void Dispose() { } @@ -45,7 +77,6 @@ public IOWriteResult Write(string sFilename, List vMeshes, WriteOptio { Func, WriteOptions, IOWriteResult> writeFunc = null; - string sExtension = Path.GetExtension(sFilename); if (sExtension.Equals(".obj", StringComparison.OrdinalIgnoreCase)) writeFunc = Write_OBJ; @@ -89,64 +120,79 @@ public IOWriteResult Write(string sFilename, List vMeshes, WriteOptio IOWriteResult Write_OBJ(string sFilename, List vMeshes, WriteOptions options) { - StreamWriter w = new StreamWriter(sFilename); - if (w.BaseStream == null) + Stream stream = OpenStreamF(sFilename); + if ( stream == null ) return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - OBJWriter writer = new OBJWriter(); - var result = writer.Write(w, vMeshes, options); - w.Close(); - return result; + try { + StreamWriter w = new StreamWriter(stream); + OBJWriter writer = new OBJWriter() { + OpenStreamF = this.OpenStreamF, + CloseStreamF = this.CloseStreamF + }; + var result = writer.Write(w, vMeshes, options); + return result; + } finally { + CloseStreamF(stream); + } } IOWriteResult Write_OFF(string sFilename, List vMeshes, WriteOptions options) { - StreamWriter w = new StreamWriter(sFilename); - if (w.BaseStream == null) + Stream stream = OpenStreamF(sFilename); + if (stream == null) return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - OFFWriter writer = new OFFWriter(); - var result = writer.Write(w, vMeshes, options); - w.Close(); - return result; + try { + StreamWriter w = new StreamWriter(stream); + OFFWriter writer = new OFFWriter(); + var result = writer.Write(w, vMeshes, options); + return result; + } finally { + CloseStreamF(stream); + } } IOWriteResult Write_STL(string sFilename, List vMeshes, WriteOptions options) { - if (options.bWriteBinary) { - FileStream file_stream = File.Open(sFilename, FileMode.Create); - BinaryWriter w = new BinaryWriter(file_stream); - if (w.BaseStream == null) - return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - STLWriter writer = new STLWriter(); - var result = writer.Write(w, vMeshes, options); - w.Close(); - return result; + Stream stream = OpenStreamF(sFilename); + if (stream == null) + return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - } else { - StreamWriter w = new StreamWriter(sFilename); - if (w.BaseStream == null) - return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - STLWriter writer = new STLWriter(); - var result = writer.Write(w, vMeshes, options); - w.Close(); - return result; + try { + if (options.bWriteBinary) { + BinaryWriter w = new BinaryWriter(stream); + STLWriter writer = new STLWriter(); + var result = writer.Write(w, vMeshes, options); + return result; + } else { + StreamWriter w = new StreamWriter(stream); + STLWriter writer = new STLWriter(); + var result = writer.Write(w, vMeshes, options); + return result; + } + } finally { + CloseStreamF(stream); } } IOWriteResult Write_G3Mesh(string sFilename, List vMeshes, WriteOptions options) { - FileStream file_stream = File.Open(sFilename, FileMode.Create); - BinaryWriter w = new BinaryWriter(file_stream); - if (w.BaseStream == null) + Stream stream = OpenStreamF(sFilename); + if (stream == null) return new IOWriteResult(IOCode.FileAccessError, "Could not open file " + sFilename + " for writing"); - BinaryG3Writer writer = new BinaryG3Writer(); - var result = writer.Write(w, vMeshes, options); - w.Close(); - return result; + + try { + BinaryWriter w = new BinaryWriter(stream); + BinaryG3Writer writer = new BinaryG3Writer(); + var result = writer.Write(w, vMeshes, options); + return result; + } finally { + CloseStreamF(stream); + } } From 7a0d95f27d8e6b53b1fe736326cc21cba2bcc38f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Mar 2018 11:09:36 -0500 Subject: [PATCH 028/225] util functions --- math/AxisAlignedBox2f.cs | 17 +++++++++++++++++ math/BoundsUtil.cs | 12 ++++++++++++ 2 files changed, 29 insertions(+) diff --git a/math/AxisAlignedBox2f.cs b/math/AxisAlignedBox2f.cs index b35104e6..def2f86f 100644 --- a/math/AxisAlignedBox2f.cs +++ b/math/AxisAlignedBox2f.cs @@ -1,5 +1,9 @@ using System; +#if G3_USING_UNITY +using UnityEngine; +#endif + namespace g3 { public struct AxisAlignedBox2f @@ -256,5 +260,18 @@ public override string ToString() { } +#if G3_USING_UNITY + public static implicit operator AxisAlignedBox2f(UnityEngine.Rect b) + { + return new AxisAlignedBox2f(b.min, b.max); + } + public static implicit operator UnityEngine.Rect(AxisAlignedBox2f b) + { + Rect ub = new Rect(); + ub.min = b.Min; ub.max = b.Max; + return ub; + } +#endif + } } diff --git a/math/BoundsUtil.cs b/math/BoundsUtil.cs index d471f91c..0b9a5113 100644 --- a/math/BoundsUtil.cs +++ b/math/BoundsUtil.cs @@ -59,6 +59,18 @@ public static AxisAlignedBox3f Bounds(IEnumerable values, Func + /// compute axis-aligned bounds of set of points after transforming + /// + public static AxisAlignedBox3d Bounds(IEnumerable values, TransformSequence xform) + { + AxisAlignedBox3d box = AxisAlignedBox3d.Empty; + foreach (Vector3d v in values) + box.Contain(xform.TransformP(v)); + return box; + } + + /// /// compute axis-aligned bounds of set of points after transforming into frame f /// From d951d47f45c63937fe3c96ec412f91f17c045734 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Mar 2018 11:18:51 -0500 Subject: [PATCH 029/225] bounds utils --- math/BoundsUtil.cs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/math/BoundsUtil.cs b/math/BoundsUtil.cs index 0b9a5113..b1121a9d 100644 --- a/math/BoundsUtil.cs +++ b/math/BoundsUtil.cs @@ -43,7 +43,39 @@ public static AxisAlignedBox3d Bounds(ref AxisAlignedBox3d boxIn, Func(IEnumerable values, Func PositionF) + public static AxisAlignedBox3d Bounds(IEnumerable positions) + { + AxisAlignedBox3d box = AxisAlignedBox3d.Empty; + foreach (Vector3d v in positions) + box.Contain(v); + return box; + } + public static AxisAlignedBox3f Bounds(IEnumerable positions) + { + AxisAlignedBox3f box = AxisAlignedBox3f.Empty; + foreach (Vector3f v in positions) + box.Contain(v); + return box; + } + + + public static AxisAlignedBox2d Bounds(IEnumerable positions) + { + AxisAlignedBox2d box = AxisAlignedBox2d.Empty; + foreach (Vector2d v in positions) + box.Contain(v); + return box; + } + public static AxisAlignedBox2f Bounds(IEnumerable positions) + { + AxisAlignedBox2f box = AxisAlignedBox2f.Empty; + foreach (Vector2f v in positions) + box.Contain(v); + return box; + } + + + public static AxisAlignedBox3d Bounds(IEnumerable values, Func PositionF) { AxisAlignedBox3d box = AxisAlignedBox3d.Empty; foreach ( T t in values ) From 44af5ac842da71de55f72007bb122bb5e8004f92 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Mar 2018 15:05:41 -0500 Subject: [PATCH 030/225] initial implementation --- core/RefCountVector.cs | 48 +++++++++++++-- mesh/DMesh3.cs | 136 ++++++++++++++++++++++++++++++++++++++++- mesh/DMesh3Changes.cs | 127 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 mesh/DMesh3Changes.cs diff --git a/core/RefCountVector.cs b/core/RefCountVector.cs index a51b5746..f6f6a32e 100644 --- a/core/RefCountVector.cs +++ b/core/RefCountVector.cs @@ -76,13 +76,22 @@ public int refCount(int index) { public int allocate() { used_count++; if (free_indices.empty) { + // [RMS] do we need this branch anymore? ref_counts.push_back(1); return ref_counts.size - 1; } else { - int iFree = free_indices.back; - free_indices.pop_back(); - ref_counts[iFree] = 1; - return iFree; + int iFree = invalid; + while (iFree == invalid && free_indices.empty == false) { + iFree = free_indices.back; + free_indices.pop_back(); + } + if (iFree != invalid) { + ref_counts[iFree] = 1; + return iFree; + } else { + ref_counts.push_back(1); + return ref_counts.size - 1; + } } } @@ -106,6 +115,37 @@ public void decrement(int index, short decrement = 1) { } + + + public bool allocate_at(int index) + { + if (index >= ref_counts.size) { + int j = ref_counts.size; + while (j < index) { + ref_counts.push_back(invalid); + free_indices.push_back(j); + ++j; + } + ref_counts[index] = 1; + used_count++; + return true; + + } else { + int N = free_indices.size; + for (int i = 0; i < N; ++i) { + if ( free_indices[i] == index ) { + free_indices[i] = invalid; + ref_counts[index] = 1; + used_count++; + return true; + } + } + return false; + } + } + + + // [RMS] really should not use this!! public void set_Unsafe(int index, short count) { diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 128198a7..4b6b7f71 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -27,6 +27,14 @@ public enum MeshResult Failed_SameOrientation = 28, Failed_WouldCreateBowtie = 30, + Failed_VertexAlreadyExists = 31, + Failed_CannotAllocateVertex = 32, + + + + Failed_WouldCreateNonmanifoldEdge = 50, + Failed_TriangleAlreadyExists = 51, + Failed_CannotAllocateTriangle = 52 }; @@ -917,12 +925,19 @@ public Vector3d GetEdgePoint(int eID, double t) // mesh-building + /// + /// Append new vertex at position, returns new vid + /// public int AppendVertex(Vector3d v) { return AppendVertex(new NewVertexInfo() { v = v, bHaveC = false, bHaveUV = false, bHaveN = false }); } - public int AppendVertex(NewVertexInfo info) + + /// + /// Append new vertex at position and other fields, returns new vid + /// + public int AppendVertex(ref NewVertexInfo info) { int vid = vertices_refcount.allocate(); int i = 3*vid; @@ -956,8 +971,13 @@ public int AppendVertex(NewVertexInfo info) updateTimeStamp(true); return vid; } + public int AppendVertex(NewVertexInfo info) { + return AppendVertex(ref info); + } - // direct copy from source mesh + /// + /// copy vertex fromVID from existing source mesh, returns new vid + /// public int AppendVertex(DMesh3 from, int fromVID) { int bi = 3 * fromVID; @@ -1010,6 +1030,57 @@ public int AppendVertex(DMesh3 from, int fromVID) } + + /// + /// insert vertex at given index, assuming it is unused + /// + public MeshResult InsertVertex(int vid, ref NewVertexInfo info) + { + if (vertices_refcount.isValid(vid)) + return MeshResult.Failed_VertexAlreadyExists; + + bool bOK = vertices_refcount.allocate_at(vid); + if (bOK == false) + return MeshResult.Failed_CannotAllocateVertex; + + int i = 3 * vid; + vertices.insert(info.v[2], i + 2); + vertices.insert(info.v[1], i + 1); + vertices.insert(info.v[0], i); + + if (normals != null) { + Vector3f n = (info.bHaveN) ? info.n : Vector3f.AxisY; + normals.insert(n[2], i + 2); + normals.insert(n[1], i + 1); + normals.insert(n[0], i); + } + + if (colors != null) { + Vector3f c = (info.bHaveC) ? info.c : Vector3f.One; + colors.insert(c[2], i + 2); + colors.insert(c[1], i + 1); + colors.insert(c[0], i); + } + + if (uv != null) { + Vector2f u = (info.bHaveUV) ? info.uv : Vector2f.Zero; + int j = 2 * vid; + uv.insert(u[1], j + 1); + uv.insert(u[0], j); + } + + allocate_edges_list(vid); + + updateTimeStamp(true); + return MeshResult.Ok; + } + public MeshResult InsertVertex(int vid, NewVertexInfo info) { + return InsertVertex(vid, ref info); + } + + + + public int AppendTriangle(int v0, int v1, int v2, int gid = -1) { return AppendTriangle(new Index3i(v0, v1, v2), gid); } @@ -1071,6 +1142,67 @@ void add_tri_edge(int tid, int v0, int v1, int j, int eid) + /// + /// Insert triangle at given index, assuming it is unused + /// + public MeshResult InsertTriangle(int tid, Index3i tv, int gid = -1) + { + if (triangles_refcount.isValid(tid)) + return MeshResult.Failed_TriangleAlreadyExists; + + if (IsVertex(tv[0]) == false || IsVertex(tv[1]) == false || IsVertex(tv[2]) == false) { + Util.gDevAssert(false); + return MeshResult.Failed_NotAVertex; + } + if (tv[0] == tv[1] || tv[0] == tv[2] || tv[1] == tv[2]) { + Util.gDevAssert(false); + return MeshResult.Failed_InvalidNeighbourhood; + } + + // look up edges. if any already have two triangles, this would + // create non-manifold geometry and so we do not allow it + int e0 = find_edge(tv[0], tv[1]); + int e1 = find_edge(tv[1], tv[2]); + int e2 = find_edge(tv[2], tv[0]); + if ((e0 != InvalidID && IsBoundaryEdge(e0) == false) + || (e1 != InvalidID && IsBoundaryEdge(e1) == false) + || (e2 != InvalidID && IsBoundaryEdge(e2) == false)) { + return MeshResult.Failed_WouldCreateNonmanifoldEdge; + } + + bool bOK = triangles_refcount.allocate_at(tid); + if (bOK == false) + return MeshResult.Failed_CannotAllocateTriangle; + + // now safe to insert triangle + int i = 3 * tid; + triangles.insert(tv[2], i + 2); + triangles.insert(tv[1], i + 1); + triangles.insert(tv[0], i); + if (triangle_groups != null) { + triangle_groups.insert(gid, tid); + max_group_id = Math.Max(max_group_id, gid + 1); + } + + // increment ref counts and update/create edges + vertices_refcount.increment(tv[0]); + vertices_refcount.increment(tv[1]); + vertices_refcount.increment(tv[2]); + + add_tri_edge(tid, tv[0], tv[1], 0, e0); + add_tri_edge(tid, tv[1], tv[2], 1, e1); + add_tri_edge(tid, tv[2], tv[0], 2, e2); + + updateTimeStamp(true); + return MeshResult.Ok; + } + + + + + + + public void EnableVertexNormals(Vector3f initial_normal) { if (HasVertexNormals) diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs new file mode 100644 index 00000000..1436e910 --- /dev/null +++ b/mesh/DMesh3Changes.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace g3 +{ + + public class RemoveTrianglesMeshChange + { + public DVector RemovedV; + public DVector Positions; + public DVector Normals; + public DVector Colors; + public DVector UVs; + + public DVector RemovedT; + public DVector Triangles; + + public RemoveTrianglesMeshChange() + { + } + + public void Initialize(DMesh3 mesh, IList triangles) + { + initialize_buffers(mesh); + bool has_groups = mesh.HasTriangleGroups; + + + int N = triangles.Count; + for (int i = 0; i < triangles.Count; ++i ) { + int tid = triangles[i]; + if (!mesh.IsTriangle(tid)) + continue; + + Index3i tv = mesh.GetTriangle(tid); + bool va = save_vertex(mesh, tv.a); + bool vb = save_vertex(mesh, tv.b); + bool vc = save_vertex(mesh, tv.c); + + Index4i tri = new Index4i(tv.a, tv.b, tv.c, + has_groups ? mesh.GetTriangleGroup(tid) : DMesh3.InvalidID); + RemovedT.Add(tid); + Triangles.Add(tri); + + MeshResult result = mesh.RemoveTriangle(tid, true, false); + if (result != MeshResult.Ok) + throw new Exception("RemoveTrianglesMeshChange.Initialize: exception in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); + Util.gDevAssert(mesh.IsVertex(tv.a) == va && mesh.IsVertex(tv.b) == vb && mesh.IsVertex(tv.c) == vc); + } + } + + + public void Apply(DMesh3 mesh) + { + int N = RemovedT.size; + for ( int i = 0; i< N; ++i) { + int tid = RemovedT[i]; + MeshResult result = mesh.RemoveTriangle(RemovedT[i], true, false); + if (result != MeshResult.Ok) + throw new Exception("RemoveTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); + } + } + + + public void Revert(DMesh3 mesh) + { + int NV = RemovedV.size; + NewVertexInfo vinfo = new NewVertexInfo(Positions[0]); + for ( int i = 0; i < NV; ++i ) { + int vid = RemovedV[i]; + vinfo.v = Positions[i]; + if ( Normals != null ) { vinfo.bHaveN = true; vinfo.n = Normals[i]; } + if ( Colors != null) { vinfo.bHaveC = true; vinfo.c = Colors[i]; } + if ( UVs != null) { vinfo.bHaveUV = true; vinfo.uv = UVs[i]; } + MeshResult result = mesh.InsertVertex(vid, ref vinfo); + if ( result != MeshResult.Ok ) + throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertVertex(" + vid.ToString() + "): " + result.ToString()); + } + + int NT = RemovedT.size; + for (int i = 0; i < NT; ++i) { + int tid = RemovedT[i]; + Index4i tdata = Triangles[i]; + Index3i tri = new Index3i(tdata.a, tdata.b, tdata.c); + MeshResult result = mesh.InsertTriangle(tid, tri, tdata.d); + if (result != MeshResult.Ok) + throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertTriangle(" + tid.ToString() + "): " + result.ToString()); + } + } + + + bool save_vertex(DMesh3 mesh, int vid) + { + if ( mesh.VerticesRefCounts.refCount(vid) == 2 ) { + RemovedV.Add(vid); + Positions.Add(mesh.GetVertex(vid)); + if (Normals != null) + Normals.Add(mesh.GetVertexNormal(vid)); + if (Colors != null) + Colors.Add(mesh.GetVertexColor(vid)); + if (UVs != null) + UVs.Add(mesh.GetVertexUV(vid)); + return false; + } + return true; + } + + + void initialize_buffers(DMesh3 mesh) + { + RemovedV = new DVector(); + Positions = new DVector(); + if (mesh.HasVertexNormals) + Normals = new DVector(); + if (mesh.HasVertexColors) + Colors = new DVector(); + if (mesh.HasVertexUVs) + UVs = new DVector(); + + RemovedT = new DVector(); + Triangles = new DVector(); + } + + } + +} From fd1865d8607ac58272217822e1ba4afc59a4c970 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 8 Mar 2018 22:17:05 -0500 Subject: [PATCH 031/225] DMesh3.InsertVertex/InsertTriangle work now, and have "fast insert" mode, used in remove-triangles change. bugfix in SmallListSet.AllocateAt. Added RefCountVector.allocate_at_unsafe(), fixed allocate_at() --- core/RefCountVector.cs | 41 ++++++++++++++++++++++-- core/SmallListSet.cs | 6 ++++ geometry3Sharp.csproj | 1 + mesh/DMesh3.cs | 29 ++++++++++++++--- mesh/DMesh3Changes.cs | 73 +++++++++++++++++++++++++----------------- 5 files changed, 112 insertions(+), 38 deletions(-) diff --git a/core/RefCountVector.cs b/core/RefCountVector.cs index f6f6a32e..aad9e1b4 100644 --- a/core/RefCountVector.cs +++ b/core/RefCountVector.cs @@ -116,7 +116,13 @@ public void decrement(int index, short decrement = 1) { - + /// + /// allocate at specific index, which must either be larger than current max index, + /// or on the free list. If larger, all elements up to this one will be pushed onto + /// free list. otherwise we have to do a linear search through free list. + /// If you are doing many of these, it is likely faster to use + /// allocate_at_unsafe(), and then rebuild_free_list() after you are done. + /// public bool allocate_at(int index) { if (index >= ref_counts.size) { @@ -126,11 +132,14 @@ public bool allocate_at(int index) free_indices.push_back(j); ++j; } - ref_counts[index] = 1; + ref_counts.push_back(1); used_count++; return true; } else { + if (ref_counts[index] > 0) + return false; + int N = free_indices.size; for (int i = 0; i < N; ++i) { if ( free_indices[i] == index ) { @@ -145,6 +154,33 @@ public bool allocate_at(int index) } + /// + /// allocate at specific index, which must be free or larger than current max index. + /// However, we do not update free list. So, you probably need to do + /// rebuild_free_list() after calling this. + /// + public bool allocate_at_unsafe(int index) + { + if (index >= ref_counts.size) { + int j = ref_counts.size; + while (j < index) { + ref_counts.push_back(invalid); + ++j; + } + ref_counts.push_back(1); + used_count++; + return true; + + } else { + if (ref_counts[index] > 0) + return false; + ref_counts[index] = 1; + used_count++; + return true; + } + } + + // [RMS] really should not use this!! public void set_Unsafe(int index, short count) @@ -153,7 +189,6 @@ public void set_Unsafe(int index, short count) } // todo: - // insert // remove // clear diff --git a/core/SmallListSet.cs b/core/SmallListSet.cs index 8629fd07..568eb4e0 100644 --- a/core/SmallListSet.cs +++ b/core/SmallListSet.cs @@ -83,7 +83,13 @@ public void Resize(int new_size) public void AllocateAt(int list_index) { if (list_index >= list_heads.size) { + int j = list_heads.size; list_heads.insert(Null, list_index); + // need to set intermediate values to null! + while (j < list_index) { + list_heads[j] = Null; + j++; + } } else { if (list_heads[list_index] != Null) throw new Exception("SmallListSet: list at " + list_index + " is not empty!"); diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index ccd73e55..ef0527e4 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -145,6 +145,7 @@ + diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 4b6b7f71..c1340097 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -1033,13 +1033,16 @@ public int AppendVertex(DMesh3 from, int fromVID) /// /// insert vertex at given index, assuming it is unused + /// If bUnsafe, we use fast id allocation that does not update free list. + /// You should only be using this between BeginUnsafeVerticesInsert() / EndUnsafeVerticesInsert() calls /// - public MeshResult InsertVertex(int vid, ref NewVertexInfo info) + public MeshResult InsertVertex(int vid, ref NewVertexInfo info, bool bUnsafe = false) { if (vertices_refcount.isValid(vid)) return MeshResult.Failed_VertexAlreadyExists; - bool bOK = vertices_refcount.allocate_at(vid); + bool bOK = (bUnsafe) ? vertices_refcount.allocate_at_unsafe(vid) : + vertices_refcount.allocate_at(vid); if (bOK == false) return MeshResult.Failed_CannotAllocateVertex; @@ -1079,6 +1082,13 @@ public MeshResult InsertVertex(int vid, NewVertexInfo info) { } + public virtual void BeginUnsafeVerticesInsert() { + // do nothing... + } + public virtual void EndUnsafeVerticesInsert() { + vertices_refcount.rebuild_free_list(); + } + public int AppendTriangle(int v0, int v1, int v2, int gid = -1) { @@ -1143,9 +1153,11 @@ void add_tri_edge(int tid, int v0, int v1, int j, int eid) /// - /// Insert triangle at given index, assuming it is unused + /// Insert triangle at given index, assuming it is unused. + /// If bUnsafe, we use fast id allocation that does not update free list. + /// You should only be using this between BeginUnsafeTrianglesInsert() / EndUnsafeTrianglesInsert() calls /// - public MeshResult InsertTriangle(int tid, Index3i tv, int gid = -1) + public MeshResult InsertTriangle(int tid, Index3i tv, int gid = -1, bool bUnsafe = false) { if (triangles_refcount.isValid(tid)) return MeshResult.Failed_TriangleAlreadyExists; @@ -1170,7 +1182,8 @@ public MeshResult InsertTriangle(int tid, Index3i tv, int gid = -1) return MeshResult.Failed_WouldCreateNonmanifoldEdge; } - bool bOK = triangles_refcount.allocate_at(tid); + bool bOK = (bUnsafe) ? triangles_refcount.allocate_at_unsafe(tid) : + triangles_refcount.allocate_at(tid); if (bOK == false) return MeshResult.Failed_CannotAllocateTriangle; @@ -1198,6 +1211,12 @@ public MeshResult InsertTriangle(int tid, Index3i tv, int gid = -1) } + public virtual void BeginUnsafeTrianglesInsert() { + // do nothing... + } + public virtual void EndUnsafeTrianglesInsert() { + triangles_refcount.rebuild_free_list(); + } diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index 1436e910..1cca940b 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -5,31 +5,34 @@ namespace g3 { - + /// + /// Remove triangles from mesh and store necessary data to be able to reverse the change. + /// Vertex and Triangle IDs will be restored on Revert() + /// Currently does *not* restore the same EdgeIDs + /// public class RemoveTrianglesMeshChange { - public DVector RemovedV; - public DVector Positions; - public DVector Normals; - public DVector Colors; - public DVector UVs; - - public DVector RemovedT; - public DVector Triangles; + protected DVector RemovedV; + protected DVector Positions; + protected DVector Normals; + protected DVector Colors; + protected DVector UVs; + + protected DVector RemovedT; + protected DVector Triangles; public RemoveTrianglesMeshChange() { } - public void Initialize(DMesh3 mesh, IList triangles) + + public void Initialize(DMesh3 mesh, IEnumerable triangles) { initialize_buffers(mesh); bool has_groups = mesh.HasTriangleGroups; - int N = triangles.Count; - for (int i = 0; i < triangles.Count; ++i ) { - int tid = triangles[i]; + foreach ( int tid in triangles ) { if (!mesh.IsTriangle(tid)) continue; @@ -66,30 +69,40 @@ public void Apply(DMesh3 mesh) public void Revert(DMesh3 mesh) { int NV = RemovedV.size; - NewVertexInfo vinfo = new NewVertexInfo(Positions[0]); - for ( int i = 0; i < NV; ++i ) { - int vid = RemovedV[i]; - vinfo.v = Positions[i]; - if ( Normals != null ) { vinfo.bHaveN = true; vinfo.n = Normals[i]; } - if ( Colors != null) { vinfo.bHaveC = true; vinfo.c = Colors[i]; } - if ( UVs != null) { vinfo.bHaveUV = true; vinfo.uv = UVs[i]; } - MeshResult result = mesh.InsertVertex(vid, ref vinfo); - if ( result != MeshResult.Ok ) - throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertVertex(" + vid.ToString() + "): " + result.ToString()); + if (NV > 0) { + NewVertexInfo vinfo = new NewVertexInfo(Positions[0]); + mesh.BeginUnsafeVerticesInsert(); + for (int i = 0; i < NV; ++i) { + int vid = RemovedV[i]; + vinfo.v = Positions[i]; + if (Normals != null) { vinfo.bHaveN = true; vinfo.n = Normals[i]; } + if (Colors != null) { vinfo.bHaveC = true; vinfo.c = Colors[i]; } + if (UVs != null) { vinfo.bHaveUV = true; vinfo.uv = UVs[i]; } + MeshResult result = mesh.InsertVertex(vid, ref vinfo, true); + if (result != MeshResult.Ok) + throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertVertex(" + vid.ToString() + "): " + result.ToString()); + } + mesh.EndUnsafeVerticesInsert(); } int NT = RemovedT.size; - for (int i = 0; i < NT; ++i) { - int tid = RemovedT[i]; - Index4i tdata = Triangles[i]; - Index3i tri = new Index3i(tdata.a, tdata.b, tdata.c); - MeshResult result = mesh.InsertTriangle(tid, tri, tdata.d); - if (result != MeshResult.Ok) - throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertTriangle(" + tid.ToString() + "): " + result.ToString()); + if (NT > 0) { + mesh.BeginUnsafeTrianglesInsert(); + for (int i = 0; i < NT; ++i) { + int tid = RemovedT[i]; + Index4i tdata = Triangles[i]; + Index3i tri = new Index3i(tdata.a, tdata.b, tdata.c); + MeshResult result = mesh.InsertTriangle(tid, tri, tdata.d, true); + if (result != MeshResult.Ok) + throw new Exception("RemoveTrianglesMeshChange.Revert: error in InsertTriangle(" + tid.ToString() + "): " + result.ToString()); + } + mesh.EndUnsafeTrianglesInsert(); } } + + bool save_vertex(DMesh3 mesh, int vid) { if ( mesh.VerticesRefCounts.refCount(vid) == 2 ) { From 8c90d1756791678a22e191ae0e0ee6625ae78e84 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Mar 2018 10:37:11 -0400 Subject: [PATCH 032/225] added quaternion inverse-multiply functions --- math/Quaternionf.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/math/Quaternionf.cs b/math/Quaternionf.cs index 5f2db3b9..7e6c7872 100644 --- a/math/Quaternionf.cs +++ b/math/Quaternionf.cs @@ -118,6 +118,48 @@ public float Dot(Quaternionf q2) { } + + /// Inverse() * v + public Vector3f InverseMultiply(ref Vector3f v) + { + float norm = LengthSquared; + if (norm > 0) { + float invNorm = 1.0f / norm; + float qx = -x*invNorm, qy = -y*invNorm, qz = -z*invNorm, qw = w*invNorm; + float twoX = 2 * qx; float twoY = 2 * qy; float twoZ = 2 * qz; + float twoWX = twoX * qw; float twoWY = twoY * qw; float twoWZ = twoZ * qw; + float twoXX = twoX * qx; float twoXY = twoY * qx; float twoXZ = twoZ * qx; + float twoYY = twoY * qy; float twoYZ = twoZ * qy; float twoZZ = twoZ * qz; + return new Vector3f( + v.x * (1 - (twoYY + twoZZ)) + v.y * (twoXY - twoWZ) + v.z * (twoXZ + twoWY), + v.x * (twoXY + twoWZ) + v.y * (1 - (twoXX + twoZZ)) + v.z * (twoYZ - twoWX), + v.x * (twoXZ - twoWY) + v.y * (twoYZ + twoWX) + v.z * (1 - (twoXX + twoYY))); + } else + return Vector3f.Zero; + } + + + /// Inverse() * v + public Vector3d InverseMultiply(ref Vector3d v) + { + float norm = LengthSquared; + if (norm > 0) { + float invNorm = 1.0f / norm; + float qx = -x * invNorm, qy = -y * invNorm, qz = -z * invNorm, qw = w * invNorm; + double twoX = 2 * qx; double twoY = 2 * qy; double twoZ = 2 * qz; + double twoWX = twoX * qw; double twoWY = twoY * qw; double twoWZ = twoZ * qw; + double twoXX = twoX * qx; double twoXY = twoY * qx; double twoXZ = twoZ * qx; + double twoYY = twoY * qy; double twoYZ = twoZ * qy; double twoZZ = twoZ * qz; + return new Vector3d( + v.x * (1 - (twoYY + twoZZ)) + v.y * (twoXY - twoWZ) + v.z * (twoXZ + twoWY), + v.x * (twoXY + twoWZ) + v.y * (1 - (twoXX + twoZZ)) + v.z * (twoYZ - twoWX), + v.x * (twoXZ - twoWY) + v.y * (twoYZ + twoWX) + v.z * (1 - (twoXX + twoYY))); ; + } else + return Vector3f.Zero; + } + + + // these multiply quaternion by (1,0,0), (0,1,0), (0,0,1), respectively. // faster than full multiply, because of all the zeros public Vector3f AxisX { From 9042ec2add82f3aa0b34381b5525d6853b900be2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Mar 2018 10:38:03 -0400 Subject: [PATCH 033/225] added ref variants of existing functions to Frame3f, AABB. Frame3f now uses quaternion.InverseMultiply() instead of Inverse() * v. Added MeshMeasurements.BoundsInFrame. --- math/AxisAlignedBox3d.cs | 18 +++++ math/Frame3f.cs | 163 +++++++++++++++++++++++++-------------- mesh/MeshMeasurements.cs | 28 ++++++- 3 files changed, 146 insertions(+), 63 deletions(-) diff --git a/math/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs index e39c4a6b..96f1e02f 100644 --- a/math/AxisAlignedBox3d.cs +++ b/math/AxisAlignedBox3d.cs @@ -173,6 +173,15 @@ public void Contain(Vector3d v) { Max.z = Math.Max(Max.z, v.z); } + public void Contain(ref Vector3d v) { + Min.x = Math.Min(Min.x, v.x); + Min.y = Math.Min(Min.y, v.y); + Min.z = Math.Min(Min.z, v.z); + Max.x = Math.Max(Max.x, v.x); + Max.y = Math.Max(Max.y, v.y); + Max.z = Math.Max(Max.z, v.z); + } + public void Contain(AxisAlignedBox3d box) { Min.x = Math.Min(Min.x, box.Min.x); Min.y = Math.Min(Min.y, box.Min.y); @@ -182,6 +191,15 @@ public void Contain(AxisAlignedBox3d box) { Max.z = Math.Max(Max.z, box.Max.z); } + public void Contain(ref AxisAlignedBox3d box) { + Min.x = Math.Min(Min.x, box.Min.x); + Min.y = Math.Min(Min.y, box.Min.y); + Min.z = Math.Min(Min.z, box.Min.z); + Max.x = Math.Max(Max.x, box.Max.x); + Max.y = Math.Max(Max.y, box.Max.y); + Max.z = Math.Max(Max.z, box.Max.z); + } + public AxisAlignedBox3d Intersect(AxisAlignedBox3d box) { AxisAlignedBox3d intersect = new AxisAlignedBox3d( Math.Max(Min.x, box.Min.x), Math.Max(Min.y, box.Min.y), Math.Max(Min.z, box.Min.z), diff --git a/math/Frame3f.cs b/math/Frame3f.cs index 07514bd2..8d9e1732 100644 --- a/math/Frame3f.cs +++ b/math/Frame3f.cs @@ -252,119 +252,162 @@ public float DistanceToPlaneSigned(Vector3f p, int nNormal) /// Map point *into* local coordinates of Frame - public Vector3f ToFrameP(Vector3f v) - { - v = v - this.origin; - v = Quaternionf.Inverse(this.rotation) * v; - return v; + public Vector3f ToFrameP(Vector3f v) { + v.x -= origin.x; v.y -= origin.y; v.z -= origin.z; + return rotation.InverseMultiply(ref v); } /// Map point *into* local coordinates of Frame - public Vector3d ToFrameP(Vector3d v) - { - v = v - this.origin; - v = Quaternionf.Inverse(this.rotation) * v; - return v; + public Vector3f ToFrameP(ref Vector3f v) { + Vector3f x = new Vector3f(v.x-origin.x, v.y-origin.y, v.z-origin.z); + return rotation.InverseMultiply(ref x); + } + /// Map point *into* local coordinates of Frame + public Vector3d ToFrameP(Vector3d v) { + v.x -= origin.x; v.y -= origin.y; v.z -= origin.z; + return rotation.InverseMultiply(ref v); + } + /// Map point *into* local coordinates of Frame + public Vector3d ToFrameP(ref Vector3d v) { + Vector3d x = new Vector3d(v.x - origin.x, v.y - origin.y, v.z - origin.z); + return rotation.InverseMultiply(ref x); } /// Map point *from* local frame coordinates into "world" coordinates - public Vector3f FromFrameP(Vector3f v) - { + public Vector3f FromFrameP(Vector3f v) { return this.rotation * v + this.origin; } /// Map point *from* local frame coordinates into "world" coordinates - public Vector3d FromFrameP(Vector3d v) - { + public Vector3f FromFrameP(ref Vector3f v) { + return this.rotation * v + this.origin; + } + /// Map point *from* local frame coordinates into "world" coordinates + public Vector3d FromFrameP(Vector3d v) { + return this.rotation * v + this.origin; + } + /// Map point *from* local frame coordinates into "world" coordinates + public Vector3d FromFrameP(ref Vector3d v) { return this.rotation * v + this.origin; } /// Map vector *into* local coordinates of Frame - public Vector3f ToFrameV(Vector3f v) - { - return Quaternionf.Inverse(this.rotation) * v; + public Vector3f ToFrameV(Vector3f v) { + return rotation.InverseMultiply(ref v); } /// Map vector *into* local coordinates of Frame - public Vector3d ToFrameV(Vector3d v) - { - return Quaternionf.Inverse(this.rotation) * v; + public Vector3f ToFrameV(ref Vector3f v) { + return rotation.InverseMultiply(ref v); + } + /// Map vector *into* local coordinates of Frame + public Vector3d ToFrameV(Vector3d v) { + return rotation.InverseMultiply(ref v); + } + /// Map vector *into* local coordinates of Frame + public Vector3d ToFrameV(ref Vector3d v) { + return rotation.InverseMultiply(ref v); } /// Map vector *from* local frame coordinates into "world" coordinates - public Vector3f FromFrameV(Vector3f v) - { + public Vector3f FromFrameV(Vector3f v) { return this.rotation * v; } /// Map vector *from* local frame coordinates into "world" coordinates - public Vector3d FromFrameV(Vector3d v) - { + public Vector3f FromFrameV(ref Vector3f v) { + return this.rotation * v; + } + /// Map vector *from* local frame coordinates into "world" coordinates + public Vector3d FromFrameV(ref Vector3d v) { + return this.rotation * v; + } + /// Map vector *from* local frame coordinates into "world" coordinates + public Vector3d FromFrameV(Vector3d v) { return this.rotation * v; } /// Map quaternion *into* local coordinates of Frame - public Quaternionf ToFrame(Quaternionf q) - { + public Quaternionf ToFrame(Quaternionf q) { + return Quaternionf.Inverse(this.rotation) * q; + } + /// Map quaternion *into* local coordinates of Frame + public Quaternionf ToFrame(ref Quaternionf q) { return Quaternionf.Inverse(this.rotation) * q; } /// Map quaternion *from* local frame coordinates into "world" coordinates - public Quaternionf FromFrame(Quaternionf q) - { + public Quaternionf FromFrame(Quaternionf q) { + return this.rotation * q; + } + /// Map quaternion *from* local frame coordinates into "world" coordinates + public Quaternionf FromFrame(ref Quaternionf q) { return this.rotation * q; } /// Map ray *into* local coordinates of Frame - public Ray3f ToFrame(Ray3f r) - { - return new Ray3f(ToFrameP(r.Origin), ToFrameV(r.Direction)); + public Ray3f ToFrame(Ray3f r) { + return new Ray3f(ToFrameP(ref r.Origin), ToFrameV(ref r.Direction)); + } + /// Map ray *into* local coordinates of Frame + public Ray3f ToFrame(ref Ray3f r) { + return new Ray3f(ToFrameP(ref r.Origin), ToFrameV(ref r.Direction)); } /// Map ray *from* local frame coordinates into "world" coordinates - public Ray3f FromFrame(Ray3f r) - { - return new Ray3f(FromFrameP(r.Origin), FromFrameV(r.Direction)); + public Ray3f FromFrame(Ray3f r) { + return new Ray3f(FromFrameP(ref r.Origin), FromFrameV(ref r.Direction)); } + /// Map ray *from* local frame coordinates into "world" coordinates + public Ray3f FromFrame(ref Ray3f r) { + return new Ray3f(FromFrameP(ref r.Origin), FromFrameV(ref r.Direction)); + } + /// Map frame *into* local coordinates of Frame - public Frame3f ToFrame(Frame3f f) - { - return new Frame3f(ToFrameP(f.origin), ToFrame(f.rotation)); + public Frame3f ToFrame(Frame3f f) { + return new Frame3f(ToFrameP(ref f.origin), ToFrame(ref f.rotation)); + } + /// Map frame *into* local coordinates of Frame + public Frame3f ToFrame(ref Frame3f f) { + return new Frame3f(ToFrameP(ref f.origin), ToFrame(ref f.rotation)); } /// Map frame *from* local frame coordinates into "world" coordinates - public Frame3f FromFrame(Frame3f f) - { - return new Frame3f(FromFrameP(f.origin), FromFrame(f.rotation)); + public Frame3f FromFrame(Frame3f f) { + return new Frame3f(FromFrameP(ref f.origin), FromFrame(ref f.rotation)); + } + /// Map frame *from* local frame coordinates into "world" coordinates + public Frame3f FromFrame(ref Frame3f f) { + return new Frame3f(FromFrameP(ref f.origin), FromFrame(ref f.rotation)); } /// Map box *into* local coordinates of Frame - public Box3f ToFrame(Box3f box) { - box.Center = ToFrameP(box.Center); - box.AxisX = ToFrameV(box.AxisX); - box.AxisY = ToFrameV(box.AxisY); - box.AxisZ = ToFrameV(box.AxisZ); + public Box3f ToFrame(ref Box3f box) { + box.Center = ToFrameP(ref box.Center); + box.AxisX = ToFrameV(ref box.AxisX); + box.AxisY = ToFrameV(ref box.AxisY); + box.AxisZ = ToFrameV(ref box.AxisZ); return box; } /// Map box *from* local frame coordinates into "world" coordinates - public Box3f FromFrame(Box3f box) { - box.Center = FromFrameP(box.Center); - box.AxisX = FromFrameV(box.AxisX); - box.AxisY = FromFrameV(box.AxisY); - box.AxisZ = FromFrameV(box.AxisZ); + public Box3f FromFrame(ref Box3f box) { + box.Center = FromFrameP(ref box.Center); + box.AxisX = FromFrameV(ref box.AxisX); + box.AxisY = FromFrameV(ref box.AxisY); + box.AxisZ = FromFrameV(ref box.AxisZ); return box; } /// Map box *into* local coordinates of Frame - public Box3d ToFrame(Box3d box) { - box.Center = ToFrameP(box.Center); - box.AxisX = ToFrameV(box.AxisX); - box.AxisY = ToFrameV(box.AxisY); - box.AxisZ = ToFrameV(box.AxisZ); + public Box3d ToFrame(ref Box3d box) { + box.Center = ToFrameP(ref box.Center); + box.AxisX = ToFrameV(ref box.AxisX); + box.AxisY = ToFrameV(ref box.AxisY); + box.AxisZ = ToFrameV(ref box.AxisZ); return box; } /// Map box *from* local frame coordinates into "world" coordinates - public Box3d FromFrame(Box3d box) { - box.Center = FromFrameP(box.Center); - box.AxisX = FromFrameV(box.AxisX); - box.AxisY = FromFrameV(box.AxisY); - box.AxisZ = FromFrameV(box.AxisZ); + public Box3d FromFrame(ref Box3d box) { + box.Center = FromFrameP(ref box.Center); + box.AxisX = FromFrameV(ref box.AxisX); + box.AxisY = FromFrameV(ref box.AxisY); + box.AxisZ = FromFrameV(ref box.AxisZ); return box; } diff --git a/mesh/MeshMeasurements.cs b/mesh/MeshMeasurements.cs index 91dcfe96..350aaa84 100644 --- a/mesh/MeshMeasurements.cs +++ b/mesh/MeshMeasurements.cs @@ -200,7 +200,7 @@ public static AxisAlignedBox3d Bounds(DMesh3 mesh, Func Tran } else { foreach (Vector3d v in mesh.Vertices()) { Vector3d vT = TransformF(v); - bounds.Contain(vT); + bounds.Contain(ref vT); } } return bounds; @@ -214,7 +214,7 @@ public static AxisAlignedBox3d Bounds(IMesh mesh, Func Trans } else { foreach (int vID in mesh.VertexIndices()) { Vector3d vT = TransformF(mesh.GetVertex(vID)); - bounds.Contain(vT); + bounds.Contain(ref vT); } } return bounds; @@ -268,9 +268,31 @@ public static double AreaT(DMesh3 mesh, IEnumerable triangleIndices) } + /// + /// calculate extents of mesh along axes of frame, with optional transform + /// + public static AxisAlignedBox3d BoundsInFrame(DMesh3 mesh, Frame3f frame, Func TransformF = null) + { + AxisAlignedBox3d bounds = AxisAlignedBox3d.Empty; + if (TransformF == null) { + foreach (Vector3d v in mesh.Vertices()) { + Vector3d fv = frame.ToFrameP(v); + bounds.Contain(ref fv); + } + } else { + foreach (Vector3d v in mesh.Vertices()) { + Vector3d vT = TransformF(v); + Vector3d fv = frame.ToFrameP(ref vT); + bounds.Contain(ref fv); + } + } + return bounds; + } - + /// + /// Calculate extents of mesh along an axis, with optional transform + /// public static Interval1d ExtentsOnAxis(DMesh3 mesh, Vector3d axis, Func TransformF = null) { Interval1d extent = Interval1d.Empty; From 50bb18af1ee093acebef8bf91ec8bbb465b9efaf Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Mar 2018 10:43:28 -0400 Subject: [PATCH 034/225] added ref matrix-vector multiplies --- math/Matrix3d.cs | 14 ++++++++++++++ math/Matrix3f.cs | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/math/Matrix3d.cs b/math/Matrix3d.cs index 95f38acb..7f6f3993 100644 --- a/math/Matrix3d.cs +++ b/math/Matrix3d.cs @@ -150,6 +150,20 @@ public void ToBuffer(double[] buf) { mat.Row1.x * v.x + mat.Row1.y * v.y + mat.Row1.z * v.z, mat.Row2.x * v.x + mat.Row2.y * v.y + mat.Row2.z * v.z); } + + public Vector3d Multiply(ref Vector3d v) { + return new Vector3d( + Row0.x * v.x + Row0.y * v.y + Row0.z * v.z, + Row1.x * v.x + Row1.y * v.y + Row1.z * v.z, + Row2.x * v.x + Row2.y * v.y + Row2.z * v.z); + } + + public void Multiply(ref Vector3d v, ref Vector3d vOut) { + vOut.x = Row0.x * v.x + Row0.y * v.y + Row0.z * v.z; + vOut.y = Row1.x * v.x + Row1.y * v.y + Row1.z * v.z; + vOut.z = Row2.x * v.x + Row2.y * v.y + Row2.z * v.z; + } + public static Matrix3d operator *(Matrix3d mat1, Matrix3d mat2) { double m00 = mat1.Row0.x * mat2.Row0.x + mat1.Row0.y * mat2.Row1.x + mat1.Row0.z * mat2.Row2.x; diff --git a/math/Matrix3f.cs b/math/Matrix3f.cs index d384574b..a14b7e44 100644 --- a/math/Matrix3f.cs +++ b/math/Matrix3f.cs @@ -150,6 +150,20 @@ public void ToBuffer(float[] buf) { mat.Row1.x * v.x + mat.Row1.y * v.y + mat.Row1.z * v.z, mat.Row2.x * v.x + mat.Row2.y * v.y + mat.Row2.z * v.z); } + + public Vector3f Multiply(ref Vector3f v) { + return new Vector3f( + Row0.x * v.x + Row0.y * v.y + Row0.z * v.z, + Row1.x * v.x + Row1.y * v.y + Row1.z * v.z, + Row2.x * v.x + Row2.y * v.y + Row2.z * v.z); + } + + public void Multiply(ref Vector3f v, ref Vector3f vOut) { + vOut.x = Row0.x * v.x + Row0.y * v.y + Row0.z * v.z; + vOut.y = Row1.x * v.x + Row1.y * v.y + Row1.z * v.z; + vOut.z = Row2.x * v.x + Row2.y * v.y + Row2.z * v.z; + } + public static Matrix3f operator *(Matrix3f mat1, Matrix3f mat2) { float m00 = mat1.Row0.x * mat2.Row0.x + mat1.Row0.y * mat2.Row1.x + mat1.Row0.z * mat2.Row2.x; From 7e39a206f2f2fa78752c0e8ee382f4025c94cf75 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 11 Mar 2018 13:19:20 -0400 Subject: [PATCH 035/225] minor performance things --- math/TransformSequence.cs | 4 ++-- mesh/MeshTransforms.cs | 8 ++++---- mesh/MeshUtil.cs | 6 +++--- spatial/GridIndexing.cs | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/math/TransformSequence.cs b/math/TransformSequence.cs index 10d51af4..a08194d8 100644 --- a/math/TransformSequence.cs +++ b/math/TransformSequence.cs @@ -160,11 +160,11 @@ public Vector3d TransformP(Vector3d p) break; case XFormType.ToFrame: - p = Operations[i].Frame.ToFrameP(p); + p = Operations[i].Frame.ToFrameP(ref p); break; case XFormType.FromFrame: - p = Operations[i].Frame.FromFrameP(p); + p = Operations[i].Frame.FromFrameP(ref p); break; default: diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index e49f4546..7821e597 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -91,11 +91,11 @@ public static void ToFrame(IDeformableMesh mesh, Frame3f f) for ( int vid = 0; vid < NV; ++vid ) { if (mesh.IsVertex(vid)) { Vector3d v = mesh.GetVertex(vid); - Vector3d vf = f.ToFrameP((Vector3f)v); + Vector3d vf = f.ToFrameP(ref v); mesh.SetVertex(vid, vf); if ( bHasNormals ) { Vector3f n = mesh.GetVertexNormal(vid); - Vector3f nf = f.ToFrameV(n); + Vector3f nf = f.ToFrameV(ref n); mesh.SetVertexNormal(vid, nf); } } @@ -110,11 +110,11 @@ public static void FromFrame(IDeformableMesh mesh, Frame3f f) for ( int vid = 0; vid < NV; ++vid ) { if (mesh.IsVertex(vid)) { Vector3d vf = mesh.GetVertex(vid); - Vector3d v = f.FromFrameP((Vector3f)vf); + Vector3d v = f.FromFrameP(ref vf); mesh.SetVertex(vid, v); if ( bHasNormals ) { Vector3f n = mesh.GetVertexNormal(vid); - Vector3f nf = f.FromFrameV(n); + Vector3f nf = f.FromFrameV(ref n); mesh.SetVertexNormal(vid, nf); } } diff --git a/mesh/MeshUtil.cs b/mesh/MeshUtil.cs index a6582150..15c1b1c9 100644 --- a/mesh/MeshUtil.cs +++ b/mesh/MeshUtil.cs @@ -43,9 +43,9 @@ public static Vector3d CotanSmooth(DMesh3 mesh, int vID, double t) public static void ScaleMesh(DMesh3 mesh, Frame3f f, Vector3f vScale) { foreach ( int vid in mesh.VertexIndices() ) { - Vector3d v = mesh.GetVertex(vid); - Vector3f vScaledInF = f.ToFrameP((Vector3f)v) * vScale; - Vector3d vNew = f.FromFrameP(vScaledInF); + Vector3f v = (Vector3f)mesh.GetVertex(vid); + Vector3f vScaledInF = f.ToFrameP(ref v) * vScale; + Vector3d vNew = f.FromFrameP(ref vScaledInF); mesh.SetVertex(vid, vNew); // TODO: normals diff --git a/spatial/GridIndexing.cs b/spatial/GridIndexing.cs index 70de2004..b6e53b64 100644 --- a/spatial/GridIndexing.cs +++ b/spatial/GridIndexing.cs @@ -167,26 +167,26 @@ public FrameGridIndexer3(Frame3f frame, Vector3f cellSize) public Vector3i ToGrid(Vector3d point) { Vector3f pointf = (Vector3f)point; - pointf = GridFrame.ToFrameP(pointf); + pointf = GridFrame.ToFrameP(ref pointf); return (Vector3i)(pointf / CellSize); } public Vector3d ToGridf(Vector3d point) { - Vector3f pointf = (Vector3f)point; - pointf = GridFrame.ToFrameP(pointf); - return (pointf / CellSize); + point = GridFrame.ToFrameP(ref point); + point.x /= CellSize.x; point.y /= CellSize.y; point.z /= CellSize.z; + return point; } public Vector3d FromGrid(Vector3i gridpoint) { Vector3f pointf = CellSize * (Vector3f)gridpoint; - return (Vector3d)GridFrame.FromFrameP(pointf); + return (Vector3d)GridFrame.FromFrameP(ref pointf); } public Vector3d FromGrid(Vector3d gridpointf) { gridpointf *= CellSize; - return (Vector3d)GridFrame.FromFrameP(gridpointf); + return (Vector3d)GridFrame.FromFrameP(ref gridpointf); } } From bb162734e8ba21a8354906204c4bf6a333568924 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 13 Mar 2018 15:13:37 -0400 Subject: [PATCH 036/225] util function --- mesh/MeshMeasurements.cs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mesh/MeshMeasurements.cs b/mesh/MeshMeasurements.cs index 350aaa84..853fa885 100644 --- a/mesh/MeshMeasurements.cs +++ b/mesh/MeshMeasurements.cs @@ -310,6 +310,42 @@ public static Interval1d ExtentsOnAxis(DMesh3 mesh, Vector3d axis, Func + /// Calculate the two most extreme vertices along an axis, with optional transform + /// + public static Interval1i ExtremeVertices(DMesh3 mesh, Vector3d axis, Func TransformF = null) + { + Interval1d extent = Interval1d.Empty; + Interval1i extreme = new Interval1i(DMesh3.InvalidID, DMesh3.InvalidID); + if (TransformF == null) { + foreach (int vid in mesh.VertexIndices()) { + double t = mesh.GetVertex(vid).Dot(ref axis); + if ( t < extent.a ) { + extent.a = t; + extreme.a = vid; + } else if ( t > extent.b ) { + extent.b = t; + extreme.b = vid; + } + } + } else { + foreach (int vid in mesh.VertexIndices()) { + double t = TransformF(mesh.GetVertex(vid)).Dot(ref axis); + if (t < extent.a) { + extent.a = t; + extreme.a = vid; + } else if (t > extent.b) { + extent.b = t; + extreme.b = vid; + } + } + } + return extreme; + } + + + + public struct GenusResult { From dbb52fd49b5295a480b4ce7e7f0144727f4e24b3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 22 Mar 2018 10:39:48 -0400 Subject: [PATCH 037/225] fix for https://github.com/gradientspace/geometry3Sharp/issues/42 --- curve/CurveUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs index 042e8622..320ceb14 100644 --- a/curve/CurveUtils.cs +++ b/curve/CurveUtils.cs @@ -38,6 +38,7 @@ public static double ArcLength(IEnumerable vertices) { foreach (Vector3d v in vertices) { if (i++ > 0) sum += (v - prev).Length; + prev = v; } return sum; } From d1437a8c4b17d9460405a42f26f7c7a12fc4ce32 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 23 Mar 2018 15:12:07 -0400 Subject: [PATCH 038/225] comments --- spatial/SegmentHashGrid.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spatial/SegmentHashGrid.cs b/spatial/SegmentHashGrid.cs index a54be453..2ef1f272 100644 --- a/spatial/SegmentHashGrid.cs +++ b/spatial/SegmentHashGrid.cs @@ -126,6 +126,7 @@ public void UpdateSegmentUnsafe(T value, Vector2d old_center, Vector2d new_cente /// Find nearest segment in grid, within radius, without locking / thread-safety /// You must provided distF which returns distance between query_pt and the segment argument /// You can ignore specific segments via ignoreF lambda - return true to ignore + /// Return value is pair (nearest_index,min_dist) or (invalidValue,double.MaxValue) /// public KeyValuePair FindNearestInRadius(Vector2d query_pt, double radius, Func distF, Func ignoreF = null) { @@ -165,7 +166,8 @@ public KeyValuePair FindNearestInRadius(Vector2d query_pt, double rad /// - /// Variant of FindNearestInRadius that works with squared-distances + /// Variant of FindNearestInRadius that works with squared-distances. + /// Return value is pair (nearest_index,min_dist) or (invalidValue,double.MaxValue) /// public KeyValuePair FindNearestInSquaredRadius(Vector2d query_pt, double radiusSqr, Func distSqrF, Func ignoreF = null) { From 5f19b6fc35e71a486cd0beaca026907126762ac9 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 23 Mar 2018 22:12:37 -0400 Subject: [PATCH 039/225] added DMesh3.CompactMetric --- mesh/DMesh3.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index c1340097..28a31b80 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -1997,16 +1997,26 @@ public bool CachedIsClosed { + /// returns true if vertices, edges, and triangles are all "dense" (Count == MaxID) public bool IsCompact { get { return vertices_refcount.is_dense && edges_refcount.is_dense && triangles_refcount.is_dense; } } + + /// Returns true if vertex count == max vertex id public bool IsCompactV { get { return vertices_refcount.is_dense; } } + + /// returns true if triangle count == max triangle id public bool IsCompactT { get { return triangles_refcount.is_dense; } } + /// returns measure of compactness in range [0,1], where 1 is fully compacted + public double CompactMetric { + get { return ((double)VertexCount / (double)MaxVertexID + (double)TriangleCount / (double)MaxTriangleID) * 0.5; } + } + /// From 3a75c31f88fb919a073a8a977dcd01e278ed64d8 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 23 Mar 2018 23:38:25 -0400 Subject: [PATCH 040/225] in unity stream is not always flushed when closed !?!? --- io/StandardMeshWriter.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/io/StandardMeshWriter.cs b/io/StandardMeshWriter.cs index 36b76313..7043a0fd 100644 --- a/io/StandardMeshWriter.cs +++ b/io/StandardMeshWriter.cs @@ -131,6 +131,7 @@ IOWriteResult Write_OBJ(string sFilename, List vMeshes, WriteOptions CloseStreamF = this.CloseStreamF }; var result = writer.Write(w, vMeshes, options); + w.Flush(); return result; } finally { CloseStreamF(stream); @@ -148,6 +149,7 @@ IOWriteResult Write_OFF(string sFilename, List vMeshes, WriteOptions StreamWriter w = new StreamWriter(stream); OFFWriter writer = new OFFWriter(); var result = writer.Write(w, vMeshes, options); + w.Flush(); return result; } finally { CloseStreamF(stream); @@ -166,11 +168,13 @@ IOWriteResult Write_STL(string sFilename, List vMeshes, WriteOptions BinaryWriter w = new BinaryWriter(stream); STLWriter writer = new STLWriter(); var result = writer.Write(w, vMeshes, options); + w.Flush(); return result; } else { StreamWriter w = new StreamWriter(stream); STLWriter writer = new STLWriter(); var result = writer.Write(w, vMeshes, options); + w.Flush(); return result; } } finally { @@ -189,6 +193,7 @@ IOWriteResult Write_G3Mesh(string sFilename, List vMeshes, WriteOptio BinaryWriter w = new BinaryWriter(stream); BinaryG3Writer writer = new BinaryG3Writer(); var result = writer.Write(w, vMeshes, options); + w.Flush(); return result; } finally { CloseStreamF(stream); From 3efb5d9d87a04b75697dfb217009bcef6562c24e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 24 Mar 2018 17:13:06 -0400 Subject: [PATCH 041/225] handle degenerate case in polygon area --- curve/Polygon2d.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 7e300f99..0ae1bca6 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -201,6 +201,8 @@ public double SignedArea { get { double fArea = 0; int N = vertices.Count; + if (N == 0) + return 0; Vector2d v1 = vertices[0], v2 = Vector2d.Zero; for (int i = 0; i < N; ++i) { v2 = vertices[(i + 1) % N]; From 78a86363819dbe30f60848afde5b9e60d22c5402 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 25 Mar 2018 00:38:33 -0400 Subject: [PATCH 042/225] yikes this function was totally wrong... --- core/Units.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Units.cs b/core/Units.cs index 5a0fb234..54f48b6f 100644 --- a/core/Units.cs +++ b/core/Units.cs @@ -96,7 +96,7 @@ public static double Convert(Linear from, Linear to) if ( IsMetric(from) && IsMetric(to) ) { double pfrom = GetMetricPower(from); double pto = GetMetricPower(to); - double d = pto - pfrom; + double d = pfrom - pto; return Math.Pow(10, d); } From 47500e5c5482a545b5348417d734be0354dd33ce Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 25 Mar 2018 00:38:57 -0400 Subject: [PATCH 043/225] added MeshTransforms.Scale that takes origin --- mesh/MeshTransforms.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index 7821e597..59ec886c 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -67,17 +67,23 @@ public static void Rotate(IDeformableMesh mesh, Vector3d origin, Quaterniond rot } - public static void Scale(IDeformableMesh mesh, double sx, double sy, double sz) + public static void Scale(IDeformableMesh mesh, Vector3d scale, Vector3d origin) { int NV = mesh.MaxVertexID; - for ( int vid = 0; vid < NV; ++vid ) { + for (int vid = 0; vid < NV; ++vid) { if (mesh.IsVertex(vid)) { Vector3d v = mesh.GetVertex(vid); - v.x *= sx; v.y *= sy; v.z *= sz; + v.x -= origin.x; v.y -= origin.y; v.z -= origin.z; + v.x *= scale.x; v.y *= scale.y; v.z *= scale.z; + v.x += origin.x; v.y += origin.y; v.z += origin.z; mesh.SetVertex(vid, v); } } } + public static void Scale(IDeformableMesh mesh, double sx, double sy, double sz) + { + Scale(mesh, new Vector3d(sx, sy, sz), Vector3d.Zero); + } public static void Scale(IDeformableMesh mesh, double s) { Scale(mesh, s, s, s); From ca363f3c84379688678dacaaafc769747b33a060 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 25 Mar 2018 23:12:19 -0400 Subject: [PATCH 044/225] comparison fix, minor additions --- distance/DistRay3Segment3.cs | 2 +- mesh/DMesh3_edge_operators.cs | 8 +++++++- queries/MeshQueries.cs | 14 ++++++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/distance/DistRay3Segment3.cs b/distance/DistRay3Segment3.cs index b5a0697c..bc9314b8 100644 --- a/distance/DistRay3Segment3.cs +++ b/distance/DistRay3Segment3.cs @@ -57,7 +57,7 @@ public double Get() { public double GetSquared() { - if (DistanceSquared > 0) + if (DistanceSquared >= 0) return DistanceSquared; Vector3d diff = ray.Origin - segment.Center; diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index d7862393..fdebde8f 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -262,6 +262,8 @@ public struct EdgeSplitInfo { public int eNewBN; // new edge [vNew,vB] (original was AB) public int eNewCN; // new edge [vNew,vC] (C is "first" other vtx in ring) public int eNewDN; // new edge [vNew,vD] (D is "second" other, which doesn't exist on bdry) + public int eNewT2; + public int eNewT3; } public MeshResult SplitEdge(int vA, int vB, out EdgeSplitInfo split) { @@ -339,6 +341,8 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) split.eNewBN = efb; split.eNewCN = efc; split.eNewDN = InvalidID; + split.eNewT2 = t2; + split.eNewT3 = InvalidID; updateTimeStamp(true); return MeshResult.Ok; @@ -403,8 +407,10 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) split.eNewBN = efb; split.eNewCN = efc; split.eNewDN = edf; + split.eNewT2 = t2; + split.eNewT3 = t3; - updateTimeStamp(true); + updateTimeStamp(true); return MeshResult.Ok; } diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index aff48aea..27ca8f00 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -124,7 +124,7 @@ public static bool RayHitPointFrame(DMesh3 mesh, ISpatial spatial, Ray3d ray, ou return false; Vector3d surfPt = ray.PointAt(isect.RayParameter); if (mesh.HasVertexNormals) - hitPosFrame = SurfaceFrame(mesh, tid, surfPt); + hitPosFrame = SurfaceFrame(mesh, tid, surfPt); // TODO isect has bary-coords already!! else hitPosFrame = new Frame3f(surfPt, mesh.GetTriNormal(tid)); return true; @@ -151,7 +151,17 @@ public static Frame3f SurfaceFrame(DMesh3 mesh, int tID, Vector3d point) } - + /// + /// Get barycentric coords of point in triangle + /// + public static Vector3d BaryCoords(DMesh3 mesh, int tID, Vector3d point) + { + if (!mesh.IsTriangle(tID)) + throw new Exception("MeshQueries.SurfaceFrame: triangle " + tID + " does not exist!"); + Triangle3d tri = new Triangle3d(); + mesh.GetTriVertices(tID, ref tri.V0, ref tri.V1, ref tri.V2); + return tri.BarycentricCoords(point); + } /// From c9f69ac5ca8051f7c37f83562d2db3caa2d6e32a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 26 Mar 2018 13:58:47 -0400 Subject: [PATCH 045/225] transform sequence, per-vertex transforms --- math/TransformSequence.cs | 86 +++++++++++++++++++++++++++++++++++++++ math/VectorTuple.cs | 16 ++++++++ mesh/MeshTransforms.cs | 39 ++++++++++++++++++ 3 files changed, 141 insertions(+) diff --git a/math/TransformSequence.cs b/math/TransformSequence.cs index a08194d8..28081a9f 100644 --- a/math/TransformSequence.cs +++ b/math/TransformSequence.cs @@ -175,6 +175,49 @@ public Vector3d TransformP(Vector3d p) return p; } + + + + /// + /// Apply transforms to point + /// + public Vector3d TransformV(Vector3d v) + { + int N = Operations.Count; + for (int i = 0; i < N; ++i) { + switch (Operations[i].type) { + case XFormType.Translation: + break; + + case XFormType.QuaternionRotateAroundPoint: + case XFormType.QuaterionRotation: + v = Operations[i].Quaternion * v; + break; + + case XFormType.ScaleAroundPoint: + case XFormType.Scale: + v *= Operations[i].Scale; + break; + + case XFormType.ToFrame: + v = Operations[i].Frame.ToFrameV(ref v); + break; + + case XFormType.FromFrame: + v = Operations[i].Frame.FromFrameV(ref v); + break; + + default: + throw new NotImplementedException("TransformSequence.TransformV: unhandled type!"); + } + } + + return v; + } + + + + /// /// Apply transforms to point /// @@ -183,6 +226,49 @@ public Vector3f TransformP(Vector3f p) { } + /// + /// construct inverse transformation sequence + /// + public TransformSequence MakeInverse() + { + TransformSequence reverse = new TransformSequence(); + int N = Operations.Count; + for (int i = N-1; i >= 0; --i) { + switch (Operations[i].type) { + case XFormType.Translation: + reverse.AppendTranslation(-Operations[i].Translation); + break; + + case XFormType.QuaterionRotation: + reverse.AppendRotation(Operations[i].Quaternion.Inverse()); + break; + + case XFormType.QuaternionRotateAroundPoint: + reverse.AppendRotation(Operations[i].Quaternion.Inverse(), Operations[i].RotateOrigin); + break; + + case XFormType.Scale: + reverse.AppendScale(1.0 / Operations[i].Scale); + break; + + case XFormType.ScaleAroundPoint: + reverse.AppendScale(1.0 / Operations[i].Scale, Operations[i].RotateOrigin); + break; + + case XFormType.ToFrame: + reverse.AppendFromFrame(Operations[i].Frame); + break; + + case XFormType.FromFrame: + reverse.AppendToFrame(Operations[i].Frame); + break; + + default: + throw new NotImplementedException("TransformSequence.MakeInverse: unhandled type!"); + } + } + return reverse; + } diff --git a/math/VectorTuple.cs b/math/VectorTuple.cs index 043265e0..993646b2 100644 --- a/math/VectorTuple.cs +++ b/math/VectorTuple.cs @@ -6,6 +6,22 @@ namespace g3 // (which C# does not support, but is common in C++ code) + public struct Vector3dTuple2 + { + public Vector3d V0, V1; + + public Vector3dTuple2(Vector3d v0, Vector3d v1) + { + V0 = v0; V1 = v1; + } + + public Vector3d this[int key] { + get { return (key == 0) ? V0 : V1; } + set { if (key == 0) V0 = value; else V1 = value; } + } + } + + public struct Vector3dTuple3 { public Vector3d V0, V1, V2; diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index 59ec886c..caf92baa 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -276,6 +276,45 @@ public static void PerVertexTransform(IDeformableMesh mesh, Func + /// Apply TransformF to vertices and normals of mesh + /// + public static void PerVertexTransform(IDeformableMesh mesh, Func TransformF) + { + int NV = mesh.MaxVertexID; + for (int vid = 0; vid < NV; ++vid) { + if (mesh.IsVertex(vid)) { + Vector3dTuple2 newPN = TransformF(mesh.GetVertex(vid), mesh.GetVertexNormal(vid)); + mesh.SetVertex(vid, newPN.V0); + mesh.SetVertexNormal(vid, (Vector3f)newPN.V1); + } + } + } + + + /// + /// Apply Transform to vertices and normals of mesh + /// + public static void PerVertexTransform(IDeformableMesh mesh, TransformSequence xform) + { + int NV = mesh.MaxVertexID; + if (mesh.HasVertexNormals) { + for (int vid = 0; vid < NV; ++vid) { + if (mesh.IsVertex(vid)) { + mesh.SetVertex(vid, xform.TransformP(mesh.GetVertex(vid))); + mesh.SetVertexNormal(vid, (Vector3f)xform.TransformV(mesh.GetVertexNormal(vid))); + } + } + } else { + for (int vid = 0; vid < NV; ++vid) { + if (mesh.IsVertex(vid)) + mesh.SetVertex(vid, xform.TransformP(mesh.GetVertex(vid))); + } + } + } + + + /// /// Apply TransformF to subset of vertices of mesh /// From 8370d2372d87899107f4805df285e82f2e43c798 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 28 Mar 2018 23:12:56 -0400 Subject: [PATCH 046/225] added Vector4f. small memory optimizations. --- math/Vector4f.cs | 278 ++++++++++++++++++++++++++++++ mesh_ops/MeshInsertUVPolyCurve.cs | 15 +- 2 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 math/Vector4f.cs diff --git a/math/Vector4f.cs b/math/Vector4f.cs new file mode 100644 index 00000000..777d8049 --- /dev/null +++ b/math/Vector4f.cs @@ -0,0 +1,278 @@ +using System; +using System.Collections.Generic; +using System.Text; + +#if G3_USING_UNITY +using UnityEngine; +#endif + +namespace g3 +{ + public struct Vector4f : IComparable, IEquatable + { + public float x; + public float y; + public float z; + public float w; + + public Vector4f(float f) { x = y = z = w = f; } + public Vector4f(float x, float y, float z, float w) { this.x = x; this.y = y; this.z = z; this.w = w; } + public Vector4f(float[] v2) { x = v2[0]; y = v2[1]; z = v2[2]; w = v2[3]; } + public Vector4f(Vector4f copy) { x = copy.x; y = copy.y; z = copy.z; w = copy.w; } + + static public readonly Vector4f Zero = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f); + static public readonly Vector4f One = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f); + + public float this[int key] + { + get { return (key < 2) ? ((key == 0) ? x : y) : ((key == 2) ? z : w); } + set { + if (key < 2) { if (key == 0) x = value; else y = value; } + else { if (key == 2) z = value; else w = value; } + } + } + + public float LengthSquared + { + get { return x * x + y * y + z * z + w * w; } + } + public float Length + { + get { return (float)Math.Sqrt(LengthSquared); } + } + + public float LengthL1 + { + get { return Math.Abs(x) + Math.Abs(y) + Math.Abs(z) + Math.Abs(w); } + } + + + public float Normalize(float epsilon = MathUtil.Epsilonf) + { + float length = Length; + if (length > epsilon) { + float invLength = 1.0f / length; + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + } else { + length = 0; + x = y = z = w = 0; + } + return length; + } + public Vector4f Normalized { + get { + float length = Length; + if (length > MathUtil.Epsilon) { + float invLength = 1.0f / length; + return new Vector4f(x * invLength, y * invLength, z * invLength, w * invLength); + } else + return Vector4f.Zero; + } + } + + public bool IsNormalized { + get { return Math.Abs((x * x + y * y + z * z + w * w) - 1) < MathUtil.ZeroTolerance; } + } + + + public bool IsFinite + { + get { float f = x + y + z + w; return float.IsNaN(f) == false && float.IsInfinity(f) == false; } + } + + public void Round(int nDecimals) { + x = (float)Math.Round(x, nDecimals); + y = (float)Math.Round(y, nDecimals); + z = (float)Math.Round(z, nDecimals); + w = (float)Math.Round(w, nDecimals); + } + + + public float Dot(Vector4f v2) { + return x * v2.x + y * v2.y + z * v2.z + w * v2.w; + } + public float Dot(ref Vector4f v2) { + return x * v2.x + y * v2.y + z * v2.z + w * v2.w; + } + + public static float Dot(Vector4f v1, Vector4f v2) { + return v1.Dot(v2); + } + + + public float AngleD(Vector4f v2) + { + float fDot = MathUtil.Clamp(Dot(v2), -1, 1); + return (float)Math.Acos(fDot) * MathUtil.Rad2Degf; + } + public static float AngleD(Vector4f v1, Vector4f v2) + { + return v1.AngleD(v2); + } + public float AngleR(Vector4f v2) + { + float fDot = MathUtil.Clamp(Dot(v2), -1, 1); + return (float)Math.Acos(fDot); + } + public static float AngleR(Vector4f v1, Vector4f v2) + { + return v1.AngleR(v2); + } + + public float DistanceSquared(Vector4f v2) { + float dx = v2.x-x, dy = v2.y-y, dz = v2.z-z, dw = v2.w-w; + return dx*dx + dy*dy + dz*dz + dw*dw; + } + public float DistanceSquared(ref Vector4f v2) { + float dx = v2.x-x, dy = v2.y-y, dz = v2.z-z, dw = v2.w-w; + return dx*dx + dy*dy + dz*dz + dw*dw; + } + + public float Distance(Vector4f v2) { + float dx = v2.x-x, dy = v2.y-y, dz = v2.z-z, dw = v2.w - w; + return (float)Math.Sqrt(dx*dx + dy*dy + dz*dz + dw*dw); + } + public float Distance(ref Vector4f v2) { + float dx = v2.x-x, dy = v2.y-y, dz = v2.z-z, dw = v2.w - w; + return (float)Math.Sqrt(dx*dx + dy*dy + dz*dz + dw*dw); + } + + + public static Vector4f operator -(Vector4f v) + { + return new Vector4f(-v.x, -v.y, -v.z, -v.w); + } + + public static Vector4f operator *(float f, Vector4f v) + { + return new Vector4f(f * v.x, f * v.y, f * v.z, f * v.w); + } + public static Vector4f operator *(Vector4f v, float f) + { + return new Vector4f(f * v.x, f * v.y, f * v.z, f * v.w); + } + public static Vector4f operator /(Vector4f v, float f) + { + return new Vector4f(v.x / f, v.y / f, v.z / f, v.w / f); + } + public static Vector4f operator /(float f, Vector4f v) + { + return new Vector4f(f / v.x, f / v.y, f / v.z, f / v.w); + } + + public static Vector4f operator *(Vector4f a, Vector4f b) + { + return new Vector4f(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w); + } + public static Vector4f operator /(Vector4f a, Vector4f b) + { + return new Vector4f(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w); + } + + + public static Vector4f operator +(Vector4f v0, Vector4f v1) + { + return new Vector4f(v0.x + v1.x, v0.y + v1.y, v0.z + v1.z, v0.w + v1.w); + } + public static Vector4f operator +(Vector4f v0, float f) + { + return new Vector4f(v0.x + f, v0.y + f, v0.z + f, v0.w + f); + } + + public static Vector4f operator -(Vector4f v0, Vector4f v1) + { + return new Vector4f(v0.x - v1.x, v0.y - v1.y, v0.z - v1.z, v0.w - v1.w); + } + public static Vector4f operator -(Vector4f v0, float f) + { + return new Vector4f(v0.x - f, v0.y - f, v0.z - f, v0.w - f); + } + + + + public static bool operator ==(Vector4f a, Vector4f b) + { + return (a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w); + } + public static bool operator !=(Vector4f a, Vector4f b) + { + return (a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w); + } + public override bool Equals(object obj) + { + return this == (Vector4f)obj; + } + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + int hash = (int) 2166136261; + // Suitable nullity checks etc, of course :) + hash = (hash * 16777619) ^ x.GetHashCode(); + hash = (hash * 16777619) ^ y.GetHashCode(); + hash = (hash * 16777619) ^ z.GetHashCode(); + hash = (hash * 16777619) ^ w.GetHashCode(); + return hash; + } + } + public int CompareTo(Vector4f other) + { + if (x != other.x) + return x < other.x ? -1 : 1; + else if (y != other.y) + return y < other.y ? -1 : 1; + else if (z != other.z) + return z < other.z ? -1 : 1; + else if (w != other.w) + return w < other.w ? -1 : 1; + return 0; + } + public bool Equals(Vector4f other) + { + return (x == other.x && y == other.y && z == other.z && w == other.w); + } + + + public bool EpsilonEqual(Vector4f v2, float epsilon) { + return Math.Abs(x - v2.x) <= epsilon && + Math.Abs(y - v2.y) <= epsilon && + Math.Abs(z - v2.z) <= epsilon && + Math.Abs(w - v2.w) <= epsilon; + } + + + + public override string ToString() { + return string.Format("{0:F8} {1:F8} {2:F8} {3:F8}", x, y, z, w); + } + public string ToString(string fmt) { + return string.Format("{0} {1} {2} {3}", x.ToString(fmt), y.ToString(fmt), z.ToString(fmt), w.ToString(fmt)); + } + + + + +#if G3_USING_UNITY + public static implicit operator Vector4f(Vector4 v) + { + return new Vector4f(v.x, v.y, v.z, v.w); + } + public static implicit operator Vector4(Vector4f v) + { + return new Vector4(v.x, v.y, v.z, v.w); + } + public static implicit operator Color(Vector4f v) + { + return new Color(v.x, v.y, v.z, v.w); + } + public static implicit operator Vector4f(Color c) + { + return new Vector4f(c.r, c.g, c.b, c.a); + } +#endif + + } +} diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 145c620e..24b2f131 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -199,6 +199,10 @@ public virtual bool Apply() HashSet ZeroVertices = new HashSet(); OnCutEdges = new HashSet(); + HashSet NewEdges = new HashSet(); + HashSet NewCutVertices = new HashSet(); + sbyte[] signs = new sbyte[2 * Mesh.MaxVertexID + 2*Curve.VertexCount]; + // loop over segments, insert each one in sequence int N = (IsLoop) ? Curve.VertexCount : Curve.VertexCount - 1; for ( int si = 0; si < N; ++si ) { @@ -219,7 +223,8 @@ public virtual bool Apply() // compute edge-crossing signs // [TODO] could walk along mesh from a to b, rather than computing for entire mesh? int MaxVID = Mesh.MaxVertexID; - int[] signs = new int[MaxVID]; + if ( signs.Length < MaxVID ) + signs = new sbyte[2*MaxVID]; gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => { if (Mesh.IsVertex(vid)) { if (vid == i0_vid || vid == i1_vid) { @@ -227,18 +232,18 @@ public virtual bool Apply() } else { Vector2d v2 = PointF(vid); // tolerance defines band in which we will consider values to be zero - signs[vid] = seg.WhichSide(v2, MathUtil.ZeroTolerance); + signs[vid] = (sbyte)seg.WhichSide(v2, MathUtil.ZeroTolerance); } } else - signs[vid] = int.MaxValue; + signs[vid] = sbyte.MaxValue; }); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. // (need both in case we re-use an old edge index) int MaxEID = Mesh.MaxEdgeID; - HashSet NewEdges = new HashSet(); - HashSet NewCutVertices = new HashSet(); + NewEdges.Clear(); + NewCutVertices.Clear(); NewCutVertices.Add(i0_vid); NewCutVertices.Add(i1_vid); From fd44fe9344bcd3b1d653c5f19486fc32ed476d7e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 29 Mar 2018 14:36:18 -0400 Subject: [PATCH 047/225] small utils --- math/BoundsUtil.cs | 8 +++++++- math/IndexUtil.cs | 17 +++++++++++++++++ queries/MeshQueries.cs | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/math/BoundsUtil.cs b/math/BoundsUtil.cs index b1121a9d..33fc873f 100644 --- a/math/BoundsUtil.cs +++ b/math/BoundsUtil.cs @@ -28,7 +28,13 @@ public static AxisAlignedBox3d Bounds(ref Vector3d v0, ref Vector3d v1, ref Vect return box; } - + public static AxisAlignedBox2d Bounds(ref Vector2d v0, ref Vector2d v1, ref Vector2d v2) + { + AxisAlignedBox2d box; + MathUtil.MinMax(v0.x, v1.x, v2.x, out box.Min.x, out box.Max.x); + MathUtil.MinMax(v0.y, v1.y, v2.y, out box.Min.y, out box.Max.y); + return box; + } // AABB of transformed AABB (corners) public static AxisAlignedBox3d Bounds(ref AxisAlignedBox3d boxIn, Func TransformF) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index c2f90837..a814ff23 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -309,6 +309,23 @@ public static void Apply(int[] indices, IList map) indices[i] = map[indices[i]]; } + + + + public static void TrianglesToVertices(DMesh3 mesh, HashSet triangles, HashSet vertices) { + foreach ( int tid in triangles ) { + Index3i tv = mesh.GetTriangle(tid); + vertices.Add(tv.a); vertices.Add(tv.b); vertices.Add(tv.c); + } + } + + public static void TrianglesToEdges(DMesh3 mesh, HashSet triangles, HashSet edges) { + foreach ( int tid in triangles ) { + Index3i te = mesh.GetTriEdges(tid); + edges.Add(te.a); edges.Add(te.b); edges.Add(te.c); + } + } + } diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index 27ca8f00..0789ea6f 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -23,7 +23,7 @@ public static DistPoint3Triangle3 TriangleDistance(DMesh3 mesh, int ti, Vector3d } /// - /// Find point-normal frame at closest point to queryPoint on mesh. + /// Find point-normal(Z) frame at closest point to queryPoint on mesh. /// Returns interpolated vertex-normal frame if available, otherwise tri-normal frame. /// public static Frame3f NearestPointFrame(DMesh3 mesh, ISpatial spatial, Vector3d queryPoint) From b4a808e92ca9a5b6c71a1a931ad817567d32a0a9 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 29 Mar 2018 14:37:39 -0400 Subject: [PATCH 048/225] 2D hash table for triangles, supports containment and range queries (however each tri is stored in multiple bins, so I named it weird...) --- geometry3Sharp.csproj | 1 + spatial/TriangleBinsGrid2d.cs | 190 ++++++++++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 spatial/TriangleBinsGrid2d.cs diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index ef0527e4..353f5d95 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -297,6 +297,7 @@ + diff --git a/spatial/TriangleBinsGrid2d.cs b/spatial/TriangleBinsGrid2d.cs new file mode 100644 index 00000000..93716cf5 --- /dev/null +++ b/spatial/TriangleBinsGrid2d.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace g3 +{ + + + /// + /// This class is a spatial data structure for 2D triangles. It is intended + /// for point-containment and box-overlap queries. It does not store the + /// triangles, only indices, so you must pass in the triangle vertices to add/remove + /// functions, similar to PointHashGrid2d. + /// + /// However, unlike the hash classes, this one is based on a grid of "bins" which + /// has a fixed size, so you must provide a bounding box on construction. + /// Each triangle is inserted into every bin that it overlaps. + /// + /// [TODO] currently each triangle is inserted into every bin that it's *bounding box* + /// overlaps. Need conservative rasterization to improve this. Can implement by + /// testing each bin bbox for intersection w/ triangle + /// + public class TriangleBinsGrid2d + { + ShiftGridIndexer2 indexer; + AxisAlignedBox2d bounds; + + SmallListSet bins_list; + int bins_x, bins_y; + AxisAlignedBox2i grid_bounds; + + SpinLock spinlock; + + /// + /// "invalid" value will be returned by queries if no valid result is found (eg bounded-distance query) + /// + public TriangleBinsGrid2d(AxisAlignedBox2d bounds, int numCells) + { + this.bounds = bounds; + double cellsize = bounds.MaxDim / (double)numCells; + Vector2d origin = bounds.Min - cellsize * 0.5 * Vector2d.One; + indexer = new ShiftGridIndexer2(origin, cellsize); + + bins_x = (int)(bounds.Width / cellsize) + 2; + bins_y = (int)(bounds.Width / cellsize) + 2; + grid_bounds = new AxisAlignedBox2i(0, 0, bins_x-1, bins_y-1); + bins_list = new SmallListSet(); + bins_list.Resize(bins_x * bins_y); + } + + + /// + /// Insert triangle. This function is thread-safe, uses a SpinLock internally + /// + public void InsertTriangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c) + { + insert_triangle(triangle_id, ref a, ref b, ref c, true); + } + + /// + /// Insert triangle without locking / thread-safety + /// + public void InsertTriangleUnsafe(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c) + { + insert_triangle(triangle_id, ref a, ref b, ref c, false); + } + + + /// + /// Remove triangle. This function is thread-safe, uses a SpinLock internally + /// + public void RemoveTriangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c) + { + remove_triangle(triangle_id, ref a, ref b, ref c, true); + } + + /// + /// Remove triangle without locking / thread-safety + /// + public void RemoveTriangleUnsafe(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c) + { + remove_triangle(triangle_id, ref a, ref b, ref c, false); + } + + + /// + /// Find triangle that contains point. Not thread-safe. + /// You provide containsF(), which does the containment check. + /// If you provide ignoreF(), then tri is skipped if ignoreF(tid) == true + /// + public int FindContainingTriangle(Vector2d query_pt, Func containsF, Func ignoreF = null) + { + Vector2i grid_idx = indexer.ToGrid(query_pt); + if (grid_bounds.Contains(grid_idx) == false) + return DMesh3.InvalidID; ; + + int bin_i = grid_idx.y * bins_x + grid_idx.x; + if (ignoreF == null) { + foreach (int tid in bins_list.ValueItr(bin_i)) { + if (containsF(tid, query_pt)) + return tid; + } + } else { + foreach (int tid in bins_list.ValueItr(bin_i)) { + if (ignoreF(tid) == false && containsF(tid, query_pt)) + return tid; + } + } + + return DMesh3.InvalidID; + } + + + + + /// + /// find all triangles that overlap range + /// + public void FindTrianglesInRange(AxisAlignedBox2d range, HashSet triangles) + { + Vector2i grid_min = indexer.ToGrid(range.Min); + if (grid_bounds.Contains(grid_min) == false) + throw new Exception("TriangleBinsGrid2d.FindTrianglesInRange: range.Min is out of bounds"); + Vector2i grid_max = indexer.ToGrid(range.Max); + if (grid_bounds.Contains(grid_max) == false) + throw new Exception("TriangleBinsGrid2d.FindTrianglesInRange: range.Max is out of bounds"); + + for (int yi = grid_min.y; yi <= grid_max.y; ++yi) { + for (int xi = grid_min.x; xi <= grid_max.x; ++xi) { + int bin_i = yi * bins_x + xi; + foreach (int tid in bins_list.ValueItr(bin_i)) + triangles.Add(tid); + } + } + + } + + + + + + void insert_triangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c, bool threadsafe = true) + { + bool lockTaken = false; + while (threadsafe == true && lockTaken == false) + spinlock.Enter(ref lockTaken); + + // [TODO] actually want to conservatively rasterize triangles here, not just + // store in every cell in bbox! + + AxisAlignedBox2d bounds = BoundsUtil.Bounds(ref a, ref b, ref c); + Vector2i imin = indexer.ToGrid(bounds.Min); + Vector2i imax = indexer.ToGrid(bounds.Max); + + for ( int yi = imin.y; yi <= imax.y; ++yi ) { + for (int xi = imin.x; xi <= imax.x; ++xi) { + + // check if triangle overlaps this grid cell... + + int bin_i = yi * bins_x + xi; + bins_list.Insert(bin_i, triangle_id); + } + } + + if (lockTaken) + spinlock.Exit(); + } + + + void remove_triangle(int triangle_id, ref Vector2d a, ref Vector2d b, ref Vector2d c, bool threadsafe = true) + { + bool lockTaken = false; + while (threadsafe == true && lockTaken == false) + spinlock.Enter(ref lockTaken); + + AxisAlignedBox2d bounds = BoundsUtil.Bounds(ref a, ref b, ref c); + Vector2i imin = indexer.ToGrid(bounds.Min); + Vector2i imax = indexer.ToGrid(bounds.Max); + for (int yi = imin.y; yi <= imax.y; ++yi) { + for (int xi = imin.x; xi <= imax.x; ++xi) { + int bin_i = yi * bins_x + xi; + bins_list.Remove(bin_i, triangle_id); + } + } + + if (lockTaken) + spinlock.Exit(); + } + } +} From b12e01a66d6c82890ad6f9244e0ca3dc8d727a3b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 29 Mar 2018 14:38:15 -0400 Subject: [PATCH 049/225] use spatial data structure. ~100x speedup? --- mesh_ops/MeshInsertUVPolyCurve.cs | 142 +++++++++++++++++++++++++----- 1 file changed, 119 insertions(+), 23 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 24b2f131..679896a4 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -26,6 +26,8 @@ public class MeshInsertUVPolyCurve // the spans & loops take some compute time and can be disabled if you don't need it... public bool EnableCutSpansAndLoops = true; + // probably always makes sense to use this...maybe not for very small problems? + bool UseTriSpatial = true; // Results @@ -96,37 +98,93 @@ public virtual ValidationStatus Validate(double fDegenerateTol = MathUtil.ZeroTo } + + // we use this simple 2D bins data structure to speed up containment queries + + TriangleBinsGrid2d triSpatial; + + void spatial_add_triangle(int tid) { + if (triSpatial == null) + return; + Index3i tv = Mesh.GetTriangle(tid); + Vector2d a = PointF(tv.a), b = PointF(tv.b), c = PointF(tv.c); + triSpatial.InsertTriangleUnsafe(tid, ref a, ref b, ref c); + } + void spatial_add_triangles(int t0, int t1) { + if (triSpatial == null) + return; + spatial_add_triangle(t0); + if (t1 != DMesh3.InvalidID) + spatial_add_triangle(t1); + } + void spatial_remove_triangle(int tid) { + if (triSpatial == null) + return; + Index3i tv = Mesh.GetTriangle(tid); + Vector2d a = PointF(tv.a), b = PointF(tv.b), c = PointF(tv.c); + triSpatial.RemoveTriangleUnsafe(tid, ref a, ref b, ref c); + } + void spatial_remove_triangles(int t0, int t1) { + if (triSpatial == null) + return; + spatial_remove_triangle(t0); + if (t1 != DMesh3.InvalidID) + spatial_remove_triangle(t1); + } + + // (sequentially) find each triangle that path point lies in, and insert a vertex for // that point into mesh. void insert_corners() { PrimalQuery2d query = new PrimalQuery2d(PointF); - // [TODO] can do everythnig up to PokeTriangle in parallel, - // except if we are poking same tri w/ multiple points! + if (UseTriSpatial) { + AxisAlignedBox3d bounds3 = Mesh.CachedBounds; + AxisAlignedBox2d bounds2 = new AxisAlignedBox2d(bounds3.Min.xy, bounds3.Max.xy); + triSpatial = new TriangleBinsGrid2d(bounds2, 32); + foreach (int tid in Mesh.TriangleIndices()) + spatial_add_triangle(tid); + } + + Func inTriangleF = (tid, pos) => { + Index3i tv = Mesh.GetTriangle(tid); + int query_result = query.ToTriangleUnsigned(pos, tv.a, tv.b, tv.c); + return (query_result == -1 || query_result == 0); + }; CurveVertices = new int[Curve.VertexCount]; for ( int i = 0; i < Curve.VertexCount; ++i ) { Vector2d vInsert = Curve[i]; bool inserted = false; - foreach (int tid in Mesh.TriangleIndices()) { - Index3i tv = Mesh.GetTriangle(tid); - // [RMS] using unsigned query here because we do not need to care about tri CW/CCW orientation - // (right? otherwise we have to explicitly invert mesh. Nothing else we do depends on tri orientation) - //int query_result = query.ToTriangle(vInsert, tv.a, tv.b, tv.c); - int query_result = query.ToTriangleUnsigned(vInsert, tv.a, tv.b, tv.c); - if (query_result == -1 || query_result == 0) { - Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); - int vid = insert_corner_from_bary(i, tid, bary); - if ( vid > 0 ) { // this should be always happening.. - CurveVertices[i] = vid; - inserted = true; - - //Util.WriteDebugMesh(Mesh, string.Format("C:\\git\\geometry3SharpDemos\\geometry3Test\\test_output\\after_insert_corner_{0}.obj", i)); + int contain_tid = DMesh3.InvalidID; + if (triSpatial != null) { + contain_tid = triSpatial.FindContainingTriangle(vInsert, inTriangleF); + } else { + foreach (int tid in Mesh.TriangleIndices()) { + Index3i tv = Mesh.GetTriangle(tid); + // [RMS] using unsigned query here because we do not need to care about tri CW/CCW orientation + // (right? otherwise we have to explicitly invert mesh. Nothing else we do depends on tri orientation) + //int query_result = query.ToTriangle(vInsert, tv.a, tv.b, tv.c); + int query_result = query.ToTriangleUnsigned(vInsert, tv.a, tv.b, tv.c); + if (query_result == -1 || query_result == 0) { + contain_tid = tid; break; } - } + } + } + + if (contain_tid != DMesh3.InvalidID ) { + Index3i tv = Mesh.GetTriangle(contain_tid); + Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); + int vid = insert_corner_from_bary(i, contain_tid, bary); + if (vid > 0) { // this should be always happening.. + CurveVertices[i] = vid; + inserted = true; + } else { + throw new Exception("MeshInsertUVPolyCurve.insert_corners: failed to insert vertex " + i.ToString()); + } } if (inserted == false) { @@ -164,19 +222,32 @@ int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, double t if (split_edge >= 0) { int eid = Mesh.GetTriEdge(tid, split_edge); + Index2i ev = Mesh.GetEdgeT(eid); + spatial_remove_triangles(ev.a, ev.b); + DMesh3.EdgeSplitInfo split_info; MeshResult splitResult = Mesh.SplitEdge(eid, out split_info); if (splitResult != MeshResult.Ok) - throw new Exception("MeshInsertUVPolyCurve.insert_corner_special: edge split failed in case sum==2"); + throw new Exception("MeshInsertUVPolyCurve.insert_corner_from_bary: edge split failed in case sum==2 - " + splitResult.ToString()); SetPointF(split_info.vNew, vInsert); + + spatial_add_triangles(ev.a, ev.b); + spatial_add_triangles(split_info.eNewT2, split_info.eNewT3); + return split_info.vNew; } + spatial_remove_triangle(tid); + // otherwise corner is inside triangle DMesh3.PokeTriangleInfo pokeinfo; MeshResult result = Mesh.PokeTriangle(tid, bary_coords, out pokeinfo); if (result != MeshResult.Ok) - throw new Exception("MeshInsertUVPolyCurve.insert_corner_special: face poke failed!"); + throw new Exception("MeshInsertUVPolyCurve.insert_corner_from_bary: face poke failed - " + result.ToString()); + + spatial_add_triangle(tid); + spatial_add_triangle(pokeinfo.new_t1); + spatial_add_triangle(pokeinfo.new_t2); SetPointF(pokeinfo.new_vid, vInsert); return pokeinfo.new_vid; @@ -203,6 +274,10 @@ public virtual bool Apply() HashSet NewCutVertices = new HashSet(); sbyte[] signs = new sbyte[2 * Mesh.MaxVertexID + 2*Curve.VertexCount]; + HashSet segTriangles = new HashSet(); + HashSet segVertices = new HashSet(); + HashSet segEdges = new HashSet(); + // loop over segments, insert each one in sequence int N = (IsLoop) ? Curve.VertexCount : Curve.VertexCount - 1; for ( int si = 0; si < N; ++si ) { @@ -220,12 +295,25 @@ public virtual bool Apply() continue; } + if (triSpatial != null) { + segTriangles.Clear(); segVertices.Clear(); segEdges.Clear(); + AxisAlignedBox2d segBounds = new AxisAlignedBox2d(seg.P0); segBounds.Contain(seg.P1); + segBounds.Expand(MathUtil.ZeroTolerancef * 10); + triSpatial.FindTrianglesInRange(segBounds, segTriangles); + IndexUtil.TrianglesToVertices(Mesh, segTriangles, segVertices); + IndexUtil.TrianglesToEdges(Mesh, segTriangles, segEdges); + } + + int MaxVID = Mesh.MaxVertexID; + IEnumerable vertices = Interval1i.Range(MaxVID); + if (triSpatial != null) + vertices = segVertices; + // compute edge-crossing signs // [TODO] could walk along mesh from a to b, rather than computing for entire mesh? - int MaxVID = Mesh.MaxVertexID; if ( signs.Length < MaxVID ) signs = new sbyte[2*MaxVID]; - gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => { + gParallel.ForEach(vertices, (vid) => { if (Mesh.IsVertex(vid)) { if (vid == i0_vid || vid == i1_vid) { signs[vid] = 0; @@ -248,7 +336,10 @@ public virtual bool Apply() NewCutVertices.Add(i1_vid); // cut existing edges with segment - for (int eid = 0; eid < MaxEID; ++eid) { + IEnumerable edges = Interval1i.Range(MaxEID); + if (triSpatial != null) + edges = segEdges; + foreach ( int eid in edges ) { if (Mesh.IsEdge(eid) == false) continue; if (eid >= MaxEID || NewEdges.Contains(eid)) @@ -312,14 +403,19 @@ public virtual bool Apply() continue; } + spatial_remove_triangles(ev.a, ev.b); + // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo); if (result != MeshResult.Ok) { - throw new Exception("MeshInsertUVSegment.Cut: failed in SplitEdge"); + throw new Exception("MeshInsertUVSegment.Apply: SplitEdge failed - " + result.ToString()); //return false; } + spatial_add_triangles(ev.a, ev.b); + spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); + // move split point to intersection position SetPointF(splitInfo.vNew, x); NewCutVertices.Add(splitInfo.vNew); From 4e4d5d3c56e23941178875d61d6667f13db2347a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 30 Mar 2018 15:03:44 -0400 Subject: [PATCH 050/225] debug check for refcount limit, and test for it in dmesh splitedge --- core/RefCountVector.cs | 21 +++++++++++----- mesh/DMesh3.cs | 3 +-- mesh/DMesh3_edge_operators.cs | 47 ++++++++++++++++++++++------------- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/core/RefCountVector.cs b/core/RefCountVector.cs index aad9e1b4..a8ac5b85 100644 --- a/core/RefCountVector.cs +++ b/core/RefCountVector.cs @@ -4,16 +4,20 @@ namespace g3 { - // this class allows you to keep track of refences to indices, - // with a free list so unreferenced indices can be re-used. - // - // the enumerator iterates over valid indices - // + /// + /// RefCountedVector is used to keep track of which indices in a linear index list are in use/referenced. + /// A free list is tracked so that unreferenced indices can be re-used. + /// + /// The enumerator iterates over valid indices (ie where refcount > 0) + /// + /// **refcounts are shorts** so the maximum count is 65536. + /// No overflow checking is done in release builds. + /// + /// public class RefCountVector : System.Collections.IEnumerable { public static readonly short invalid = -1; - DVector ref_counts; DVector free_indices; int used_count; @@ -71,6 +75,9 @@ public int refCount(int index) { int n = ref_counts[index]; return (n == invalid) ? 0 : n; } + public int rawRefCount(int index) { + return ref_counts[index]; + } public int allocate() { @@ -99,6 +106,8 @@ public int allocate() { public int increment(int index, short increment = 1) { Util.gDevAssert( isValid(index) ); + // debug check for overflow... + Util.gDevAssert( (short)(ref_counts[index] + increment) > 0 ); ref_counts[index] += increment; return ref_counts[index]; } diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 28a31b80..43f98bc2 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -15,6 +15,7 @@ public enum MeshResult Failed_NotAnEdge = 3, Failed_BrokenTopology = 10, + Failed_HitValenceLimit = 11, Failed_IsBoundaryEdge = 20, Failed_FlippedEdgeExists = 21, @@ -30,8 +31,6 @@ public enum MeshResult Failed_VertexAlreadyExists = 31, Failed_CannotAllocateVertex = 32, - - Failed_WouldCreateNonmanifoldEdge = 50, Failed_TriangleAlreadyExists = 51, Failed_CannotAllocateTriangle = 52 diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index fdebde8f..1a2cd86e 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -289,24 +289,25 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) Index3i T0tv = GetTriangle(t0); int[] T0tv_array = T0tv.array; int c = IndexUtil.orient_tri_edge_and_find_other_vtx(ref a, ref b, T0tv_array); - - // create new vertex - Vector3d vNew = 0.5 * ( GetVertex(a) + GetVertex(b) ); - int f = AppendVertex( vNew ); - if (HasVertexNormals) - SetVertexNormal(f, (GetVertexNormal(a) + GetVertexNormal(b)).Normalized); - if (HasVertexColors) - SetVertexColor(f, 0.5f * (GetVertexColor(a) + GetVertexColor(b)) ); - if (HasVertexUVs) - SetVertexUV(f, 0.5f * (GetVertexUV(a) + GetVertexUV(b))); - + if (vertices_refcount.rawRefCount(c) > 32764) + return MeshResult.Failed_HitValenceLimit; // quite a bit of code is duplicated between boundary and non-boundary case, but it // is too hard to follow later if we factor it out... if ( IsBoundaryEdge(eab) ) { - // look up edge bc, which needs to be modified - Index3i T0te = GetTriEdges(t0); + // create new vertex + Vector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b)); + int f = AppendVertex(vNew); + if (HasVertexNormals) + SetVertexNormal(f, (GetVertexNormal(a) + GetVertexNormal(b)).Normalized); + if (HasVertexColors) + SetVertexColor(f, 0.5f * (GetVertexColor(a) + GetVertexColor(b))); + if (HasVertexUVs) + SetVertexUV(f, 0.5f * (GetVertexUV(a) + GetVertexUV(b))); + + // look up edge bc, which needs to be modified + Index3i T0te = GetTriEdges(t0); int ebc = T0te[ IndexUtil.find_edge_index_in_tri(b, c, T0tv_array) ]; // rewrite existing triangle @@ -354,10 +355,22 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) Index3i T1tv = GetTriangle(t1); int[] T1tv_array = T1tv.array; int d = IndexUtil.find_tri_other_vtx( a, b, T1tv_array ); - - // look up edges that we are going to need to update - // [TODO OPT] could use ordering to reduce # of compares here - Index3i T0te = GetTriEdges(t0); + if (vertices_refcount.rawRefCount(d) > 32764) + return MeshResult.Failed_HitValenceLimit; + + // create new vertex + Vector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b)); + int f = AppendVertex(vNew); + if (HasVertexNormals) + SetVertexNormal(f, (GetVertexNormal(a) + GetVertexNormal(b)).Normalized); + if (HasVertexColors) + SetVertexColor(f, 0.5f * (GetVertexColor(a) + GetVertexColor(b))); + if (HasVertexUVs) + SetVertexUV(f, 0.5f * (GetVertexUV(a) + GetVertexUV(b))); + + // look up edges that we are going to need to update + // [TODO OPT] could use ordering to reduce # of compares here + Index3i T0te = GetTriEdges(t0); int ebc = T0te[IndexUtil.find_edge_index_in_tri( b, c, T0tv_array )]; Index3i T1te = GetTriEdges(t1); int edb = T1te[IndexUtil.find_edge_index_in_tri( d, b, T1tv_array )]; From 2be7780d66e5346acaea825f06e5c76c2854c2f4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 30 Mar 2018 21:31:49 -0400 Subject: [PATCH 051/225] make edge flip tolerance configurable in MeshRefinerBase --- math/IndexUtil.cs | 7 +++++++ mesh/DMesh3_edge_operators.cs | 2 +- mesh/MeshRefinerBase.cs | 29 +++++++++++++++++++++++------ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index a814ff23..74b5b7f0 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -54,6 +54,13 @@ public static int find_tri_index(int a, Index3i tri_verts) if (tri_verts.c == a) return 2; return DMesh3.InvalidID; } + public static int find_tri_index(int a, ref Index3i tri_verts) + { + if (tri_verts.a == a) return 0; + if (tri_verts.b == a) return 1; + if (tri_verts.c == a) return 2; + return DMesh3.InvalidID; + } // return index of a in tri_verts, or InvalidID if not found public static int find_edge_index_in_tri(int a, int b, int[] tri_verts ) diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index 1a2cd86e..ada7c3e0 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -59,7 +59,7 @@ public MeshResult RemoveVertex(int vID, bool bRemoveAllTriangles = true, bool bP if ( bPreserveManifold ) { foreach ( int tid in VtxTrianglesItr(vID) ) { Index3i tri = GetTriangle(tid); - int j = IndexUtil.find_tri_index(vID, tri); + int j = IndexUtil.find_tri_index(vID, ref tri); int oa = tri[(j + 1) % 3], ob = tri[(j + 2) % 3]; int eid = find_edge(oa,ob); if (IsBoundaryEdge(eid)) diff --git a/mesh/MeshRefinerBase.cs b/mesh/MeshRefinerBase.cs index 49bdb13a..82bd2124 100644 --- a/mesh/MeshRefinerBase.cs +++ b/mesh/MeshRefinerBase.cs @@ -16,6 +16,14 @@ public class MeshRefinerBase public bool AllowCollapseFixedVertsWithSameSetID = true; + /// + /// If normals dot product is less than this, we consider it a normal flip. default = 0 + /// + public double EdgeFlipTolerance { + get { return edge_flip_tol; } + set { edge_flip_tol = MathUtil.Clamp(value, -1.0, 1.0); } + } + protected double edge_flip_tol = 0.0f; public MeshRefinerBase(DMesh3 mesh) { @@ -42,6 +50,15 @@ public void SetExternalConstraints(MeshConstraints cons) } + protected double edge_flip_metric(ref Vector3d n0, ref Vector3d n1) + { + if (edge_flip_tol == 0) { + return n0.Dot(n1); + } else { + return n0.Normalized.Dot(n1.Normalized); + } + } + /// /// check if edge collapse will create a face-normal flip. @@ -65,16 +82,16 @@ protected bool collapse_creates_flip_or_invalid(int vid, int vother, ref Vector3 double sign = 0; if (curt.a == vid) { Vector3d nnew = (vb - newv).Cross(vc - newv); - sign = ncur.Dot(ref nnew); + sign = edge_flip_metric(ref ncur, ref nnew); } else if (curt.b == vid) { Vector3d nnew = (newv - va).Cross(vc - va); - sign = ncur.Dot(ref nnew); + sign = edge_flip_metric(ref ncur, ref nnew); } else if (curt.c == vid) { Vector3d nnew = (vb - va).Cross(newv - va); - sign = ncur.Dot(ref nnew); + sign = edge_flip_metric(ref ncur, ref nnew); } else throw new Exception("should never be here!"); - if (sign <= 0.0) + if (sign <= edge_flip_tol) return true; } return false; @@ -98,10 +115,10 @@ protected bool flip_inverts_normals(int a, int b, int c, int d, int t0) Vector3d n0 = MathUtil.FastNormalDirection(ref vOA, ref vOB, ref vC); Vector3d n1 = MathUtil.FastNormalDirection(ref vOB, ref vOA, ref vD); Vector3d f0 = MathUtil.FastNormalDirection(ref vC, ref vD, ref vOB); - if (n0.Dot(f0) < 0 || n1.Dot(f0) < 0) + if ( edge_flip_metric(ref n0, ref f0) <= edge_flip_tol || edge_flip_metric(ref n1, ref f0) <= edge_flip_tol) return true; Vector3d f1 = MathUtil.FastNormalDirection(ref vD, ref vC, ref vOA); - if ( n0.Dot(f1) < 0 || n1.Dot(f1) < 0 ) + if (edge_flip_metric(ref n0, ref f1) <= edge_flip_tol || edge_flip_metric(ref n1, ref f1) <= edge_flip_tol) return true; // this only checks if output faces are pointing towards eachother, which seems From e5d45901d1b8eb896de8f2fb8a8bc9b7b22cc666 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 30 Mar 2018 21:37:02 -0400 Subject: [PATCH 052/225] add constraint setup mode --- mesh/MeshConstraintUtil.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/mesh/MeshConstraintUtil.cs b/mesh/MeshConstraintUtil.cs index 45e8aac8..5867d2cc 100644 --- a/mesh/MeshConstraintUtil.cs +++ b/mesh/MeshConstraintUtil.cs @@ -50,6 +50,26 @@ public static void FixAllBoundaryEdges_AllowCollapse(MeshConstraints cons, DMesh } + + // for all mesh boundary vertices, pin in current position, but allow splits + public static void FixAllBoundaryEdges_AllowSplit(MeshConstraints cons, DMesh3 mesh, int setID) + { + EdgeConstraint edgeCons = new EdgeConstraint(EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse); + VertexConstraint vertCons = new VertexConstraint(true, setID); + + int NE = mesh.MaxEdgeID; + for (int ei = 0; ei < NE; ++ei) { + if (mesh.IsEdge(ei) && mesh.IsBoundaryEdge(ei)) { + cons.SetOrUpdateEdgeConstraint(ei, edgeCons); + + Index2i ev = mesh.GetEdgeV(ei); + cons.SetOrUpdateVertexConstraint(ev.a, vertCons); + cons.SetOrUpdateVertexConstraint(ev.b, vertCons); + } + } + } + + // loop through submesh border edges on basemesh, map to submesh, and // pin those edges / vertices public static void FixSubmeshBoundaryEdges(MeshConstraints cons, DSubmesh3 sub) From 2a53d67b02713d2f69aa7337edea43662f5e950b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 30 Mar 2018 23:26:44 -0400 Subject: [PATCH 053/225] bugfix --- spatial/DCurveProjection.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spatial/DCurveProjection.cs b/spatial/DCurveProjection.cs index 92501f68..87b36d7a 100644 --- a/spatial/DCurveProjection.cs +++ b/spatial/DCurveProjection.cs @@ -20,8 +20,9 @@ public Vector3d Project(Vector3d vPoint, int identifier = -1) Vector3d vNearest = Vector3d.Zero; double fNearestSqr = double.MaxValue; - int N = (Curve.Closed) ? Curve.VertexCount : Curve.VertexCount - 1; - for ( int i = 0; i < N; ++i ) { + int N = Curve.VertexCount; + int NStop = (Curve.Closed) ? N : N - 1; + for ( int i = 0; i < NStop; ++i ) { Segment3d seg = new Segment3d(Curve[i], Curve[(i + 1) % N]); Vector3d pt = seg.NearestPoint(vPoint); double dsqr = pt.DistanceSquared(vPoint); From 4270ff8e598d9442303ceb9107275fb6a6dc206b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 30 Mar 2018 23:27:09 -0400 Subject: [PATCH 054/225] utility functions --- mesh/MeshConstraintUtil.cs | 45 ++++++++++++++++++++++++--- mesh_selection/MeshVertexSelection.cs | 24 ++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/mesh/MeshConstraintUtil.cs b/mesh/MeshConstraintUtil.cs index 45e8aac8..ff9b4145 100644 --- a/mesh/MeshConstraintUtil.cs +++ b/mesh/MeshConstraintUtil.cs @@ -99,17 +99,18 @@ public static void FixAllGroupBoundaryEdges(Remesher r, bool bPinVertices) // for all vertices in loopV, constrain to target // for all edges in loopV, disable flips and constrain to target - public static void ConstrainVtxLoopTo(MeshConstraints cons, DMesh3 mesh, int[] loopV, IProjectionTarget target, int setID = -1) + public static void ConstrainVtxLoopTo(MeshConstraints cons, DMesh3 mesh, IList loopV, IProjectionTarget target, int setID = -1) { VertexConstraint vc = new VertexConstraint(target); - for (int i = 0; i < loopV.Length; ++i) + int N = loopV.Count; + for (int i = 0; i < N; ++i) cons.SetOrUpdateVertexConstraint(loopV[i], vc); EdgeConstraint ec = new EdgeConstraint(EdgeRefineFlags.NoFlip, target); ec.TrackingSetID = setID; - for ( int i = 0; i < loopV.Length; ++i ) { + for ( int i = 0; i < N; ++i ) { int v0 = loopV[i]; - int v1 = loopV[(i + 1) % loopV.Length]; + int v1 = loopV[(i + 1) % N]; int eid = mesh.FindEdge(v0, v1); Debug.Assert(eid != DMesh3.InvalidID); @@ -127,6 +128,42 @@ public static void ConstrainVtxLoopTo(Remesher r, int[] loopV, IProjectionTarget + + + // for all vertices in loopV, constrain to target + // for all edges in loopV, disable flips and constrain to target + public static void ConstrainVtxSpanTo(MeshConstraints cons, DMesh3 mesh, IList spanV, IProjectionTarget target, int setID = -1) + { + VertexConstraint vc = new VertexConstraint(target); + int N = spanV.Count; + for (int i = 0; i < N; ++i) + cons.SetOrUpdateVertexConstraint(spanV[i], vc); + + EdgeConstraint ec = new EdgeConstraint(EdgeRefineFlags.NoFlip, target); + ec.TrackingSetID = setID; + for (int i = 0; i < N-1; ++i) { + int v0 = spanV[i]; + int v1 = spanV[i + 1]; + + int eid = mesh.FindEdge(v0, v1); + Debug.Assert(eid != DMesh3.InvalidID); + if (eid != DMesh3.InvalidID) + cons.SetOrUpdateEdgeConstraint(eid, ec); + } + + } + public static void ConstrainVtxSpanTo(Remesher r, int[] spanV, IProjectionTarget target, int setID = -1) + { + if (r.Constraints == null) + r.SetExternalConstraints(new MeshConstraints()); + ConstrainVtxSpanTo(r.Constraints, r.Mesh, spanV, target); + } + + + + + + public static void PreserveBoundaryLoops(MeshConstraints cons, DMesh3 mesh) { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); foreach ( EdgeLoop loop in loops ) { diff --git a/mesh_selection/MeshVertexSelection.cs b/mesh_selection/MeshVertexSelection.cs index bf1629ea..66bbbc5b 100644 --- a/mesh_selection/MeshVertexSelection.cs +++ b/mesh_selection/MeshVertexSelection.cs @@ -40,6 +40,14 @@ public MeshVertexSelection(DMesh3 mesh, MeshEdgeSelection convertE) : this(mesh) } + public HashSet ExtractSelected() + { + var ret = Selected; + Selected = new HashSet(); + return ret; + } + + public IEnumerator GetEnumerator() { return Selected.GetEnumerator(); } @@ -111,6 +119,22 @@ public void SelectTriangleVertices(MeshFaceSelection triangles) + public void SelectEdgeVertices(int[] edges) + { + for (int i = 0; i < edges.Length; ++i) { + Index2i ev = Mesh.GetEdgeV(edges[i]); + add(ev.a); add(ev.b); + } + } + public void SelectEdgeVertices(IEnumerable edges) { + foreach (int eid in edges) { + Index2i ev = Mesh.GetEdgeV(eid); + add(ev.a); add(ev.b); + } + } + + + public void Deselect(int vID) { remove(vID); } From ba416bd8cb25c5b96e12b28f4d7496cf1471a3bd Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 31 Mar 2018 23:05:12 -0400 Subject: [PATCH 055/225] utilities, bugfix to dcurve3 returned by EdgeSpan.ToCurve --- math/IndexUtil.cs | 8 ++++++++ mesh/EdgeLoop.cs | 6 ++++-- mesh/EdgeSpan.cs | 8 +++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index 74b5b7f0..a4c6937f 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -333,6 +333,14 @@ public static void TrianglesToEdges(DMesh3 mesh, HashSet triangles, HashSet } } + public static void EdgesToVertices(DMesh3 mesh, HashSet edges, HashSet vertices) + { + foreach (int eid in edges) { + Index2i ev = mesh.GetEdgeV(eid); + vertices.Add(ev.a); vertices.Add(ev.b); + } + } + } diff --git a/mesh/EdgeLoop.cs b/mesh/EdgeLoop.cs index 43d8928c..9534d748 100644 --- a/mesh/EdgeLoop.cs +++ b/mesh/EdgeLoop.cs @@ -127,9 +127,11 @@ public AxisAlignedBox3d GetBounds() } - public DCurve3 ToCurve() + public DCurve3 ToCurve(DMesh3 sourceMesh = null) { - DCurve3 curve = MeshUtil.ExtractLoopV(Mesh, Vertices); + if (sourceMesh == null) + sourceMesh = Mesh; + DCurve3 curve = MeshUtil.ExtractLoopV(sourceMesh, Vertices); curve.Closed = true; return curve; } diff --git a/mesh/EdgeSpan.cs b/mesh/EdgeSpan.cs index df7201f9..a07f6c2b 100644 --- a/mesh/EdgeSpan.cs +++ b/mesh/EdgeSpan.cs @@ -84,10 +84,12 @@ public AxisAlignedBox3d GetBounds() } - public DCurve3 ToCurve() + public DCurve3 ToCurve(DMesh3 sourceMesh = null) { - DCurve3 curve = MeshUtil.ExtractLoopV(Mesh, Vertices); - curve.Closed = true; + if (sourceMesh == null) + sourceMesh = Mesh; + DCurve3 curve = MeshUtil.ExtractLoopV(sourceMesh, Vertices); + curve.Closed = false; return curve; } From 0793cc4d7bc6a95256faf070bf0b903c443f54d6 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 31 Mar 2018 23:06:01 -0400 Subject: [PATCH 056/225] ExtractCurves can now optionally return eid lists for each span/loop, and also returns identified boundary and junction verts (we were computing these anyway) --- curve/DGraph3Util.cs | 65 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/curve/DGraph3Util.cs b/curve/DGraph3Util.cs index d0da17cd..65c94454 100644 --- a/curve/DGraph3Util.cs +++ b/curve/DGraph3Util.cs @@ -16,6 +16,12 @@ public struct Curves { public List Loops; public List Paths; + + public HashSet BoundaryV; + public HashSet JunctionV; + + public List> LoopEdges; + public List> PathEdges; } @@ -23,11 +29,16 @@ public struct Curves /// Decompose graph into simple polylines and polygons. /// public static Curves ExtractCurves(DGraph3 graph, + bool bWantLoopIndices = false, Func CurveOrientationF = null ) { Curves c = new Curves(); c.Loops = new List(); c.Paths = new List(); + if (bWantLoopIndices) { + c.LoopEdges = new List>(); + c.PathEdges = new List>(); + } HashSet used = new HashSet(); @@ -50,7 +61,10 @@ public static Curves ExtractCurves(DGraph3 graph, bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; DCurve3 path = new DCurve3() { Closed = false }; + List pathE = (bWantLoopIndices) ? new List() : null; path.AppendVertex(graph.GetVertex(vid)); + if (pathE != null) + pathE.Add(eid); while ( true ) { used.Add(eid); Index2i next = NextEdgeAndVtx(eid, vid, graph); @@ -59,14 +73,24 @@ public static Curves ExtractCurves(DGraph3 graph, path.AppendVertex(graph.GetVertex(vid)); if (boundaries.Contains(vid) || junctions.Contains(vid)) break; // done! + if (pathE != null) + pathE.Add(eid); } - if (reverse) + if (reverse) path.Reverse(); c.Paths.Add(path); + + if ( pathE != null ) { + Util.gDevAssert(pathE.Count == path.VertexCount - 1); + if (reverse) + pathE.Reverse(); + c.PathEdges.Add(pathE); + } } // ok we should be done w/ boundary verts now... - boundaries.Clear(); + //boundaries.Clear(); + c.BoundaryV = boundaries; foreach ( int start_vid in junctions ) { @@ -79,7 +103,10 @@ public static Curves ExtractCurves(DGraph3 graph, bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; DCurve3 path = new DCurve3() { Closed = false }; + List pathE = (bWantLoopIndices) ? new List() : null; path.AppendVertex(graph.GetVertex(vid)); + if (pathE != null) + pathE.Add(eid); while (true) { used.Add(eid); Index2i next = NextEdgeAndVtx(eid, vid, graph); @@ -88,6 +115,8 @@ public static Curves ExtractCurves(DGraph3 graph, path.AppendVertex(graph.GetVertex(vid)); if (eid == int.MaxValue || junctions.Contains(vid)) break; // done! + if (pathE != null) + pathE.Add(eid); } // we could end up back at our start junction vertex! @@ -97,19 +126,35 @@ public static Curves ExtractCurves(DGraph3 graph, if (reverse) path.Reverse(); c.Loops.Add(path); + + if (pathE != null) { + Util.gDevAssert(pathE.Count == path.VertexCount); + if (reverse) + pathE.Reverse(); + c.LoopEdges.Add(pathE); + } + // need to mark incoming edge as used...but is it valid now? //Util.gDevAssert(eid != int.MaxValue); - if ( eid != int.MaxValue ) + if (eid != int.MaxValue) used.Add(eid); } else { if (reverse) path.Reverse(); c.Paths.Add(path); + + if (pathE != null) { + Util.gDevAssert(pathE.Count == path.VertexCount - 1); + if (reverse) + pathE.Reverse(); + c.PathEdges.Add(pathE); + } } } } + c.JunctionV = junctions; // all that should be left are continuous loops... @@ -124,13 +169,18 @@ public static Curves ExtractCurves(DGraph3 graph, bool reverse = (CurveOrientationF != null) ? CurveOrientationF(eid) : false; DCurve3 poly = new DCurve3() { Closed = true }; + List polyE = (bWantLoopIndices) ? new List() : null; poly.AppendVertex(graph.GetVertex(vid)); + if (polyE != null) + polyE.Add(eid); while (true) { used.Add(eid); Index2i next = NextEdgeAndVtx(eid, vid, graph); eid = next.a; vid = next.b; poly.AppendVertex(graph.GetVertex(vid)); + if (polyE != null) + polyE.Add(eid); if (eid == int.MaxValue || junctions.Contains(vid)) throw new Exception("how did this happen??"); if (used.Contains(eid)) @@ -140,8 +190,15 @@ public static Curves ExtractCurves(DGraph3 graph, if (reverse) poly.Reverse(); c.Loops.Add(poly); - } + if (polyE != null) { + polyE.RemoveAt(polyE.Count - 1); + Util.gDevAssert(polyE.Count == poly.VertexCount); + if (reverse) + polyE.Reverse(); + c.LoopEdges.Add(polyE); + } + } return c; } From 888bbfa98cbbef80c138899a29329ff5c7d58a1f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 31 Mar 2018 23:06:20 -0400 Subject: [PATCH 057/225] improve edge constraint transfer in Remesher --- mesh/Remesher.cs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/mesh/Remesher.cs b/mesh/Remesher.cs index 73c867e2..f555e857 100644 --- a/mesh/Remesher.cs +++ b/mesh/Remesher.cs @@ -451,13 +451,24 @@ protected virtual void update_after_split(int edgeID, int va, int vb, ref DMesh3 bPositionFixed = true; } - // vert inherits Target if both source verts and edge have same Target - if ( ca.Target != null && ca.Target == cb.Target - && constraints.GetEdgeConstraint(edgeID).Target == ca.Target ) { - constraints.SetOrUpdateVertexConstraint(splitInfo.vNew, - new VertexConstraint(ca.Target)); - project_vertex(splitInfo.vNew, ca.Target); - bPositionFixed = true; + // vert inherits Target if: + // 1) both source verts and edge have same Target, and is same as edge target + // 2) either vert has same target as edge, and other vert is fixed + if ( ca.Target != null || cb.Target != null ) { + IProjectionTarget edge_target = constraints.GetEdgeConstraint(edgeID).Target; + IProjectionTarget set_target = null; + if (ca.Target == cb.Target && ca.Target == edge_target) + set_target = edge_target; + else if (ca.Target == edge_target && cb.Fixed) + set_target = edge_target; + else if (cb.Target == edge_target && ca.Fixed) + set_target = edge_target; + if ( set_target != null ) { + constraints.SetOrUpdateVertexConstraint(splitInfo.vNew, + new VertexConstraint(set_target)); + project_vertex(splitInfo.vNew, set_target); + bPositionFixed = true; + } } } From 45b64a2eadf49f6132fac76c29708c85f34c085e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 31 Mar 2018 23:54:26 -0400 Subject: [PATCH 058/225] constraint util --- mesh/MeshConstraintUtil.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mesh/MeshConstraintUtil.cs b/mesh/MeshConstraintUtil.cs index a9cf27c0..7e3f42ab 100644 --- a/mesh/MeshConstraintUtil.cs +++ b/mesh/MeshConstraintUtil.cs @@ -8,6 +8,22 @@ namespace g3 { public static class MeshConstraintUtil { + + // for all edges, disable flip/split/collapse + // for all vertices, pin in current position + public static void FixEdges(MeshConstraints cons, DMesh3 mesh, IEnumerable edges) + { + foreach ( int ei in edges ) { + if (mesh.IsEdge(ei)) { + cons.SetOrUpdateEdgeConstraint(ei, EdgeConstraint.FullyConstrained); + Index2i ev = mesh.GetEdgeV(ei); + cons.SetOrUpdateVertexConstraint(ev.a, VertexConstraint.Pinned); + cons.SetOrUpdateVertexConstraint(ev.b, VertexConstraint.Pinned); + } + } + } + + // for all mesh boundary edges, disable flip/split/collapse // for all mesh boundary vertices, pin in current position public static void FixAllBoundaryEdges(MeshConstraints cons, DMesh3 mesh) From 1753e1c0a38744e399f518cfb3199c86e2a3bc2c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 1 Apr 2018 12:50:37 -0400 Subject: [PATCH 059/225] bugfix for degenerate edges, fixes https://github.com/gradientspace/geometry3Sharp/issues/44 --- curve/DGraph.cs | 2 +- mesh_ops/MeshIsoCurves.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/curve/DGraph.cs b/curve/DGraph.cs index 60374f66..75e4363e 100644 --- a/curve/DGraph.cs +++ b/curve/DGraph.cs @@ -18,7 +18,7 @@ namespace g3 public abstract class DGraph { public const int InvalidID = -1; - public const int DuplicateEdgeID = -1; + public const int DuplicateEdgeID = -2; public static readonly Index2i InvalidEdgeV = new Index2i(InvalidID, InvalidID); public static readonly Index3i InvalidEdge3 = new Index3i(InvalidID, InvalidID, InvalidID); diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs index d77fefa8..d67ae3f8 100644 --- a/mesh_ops/MeshIsoCurves.cs +++ b/mesh_ops/MeshIsoCurves.cs @@ -146,7 +146,7 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f int e0 = add_or_append_vertex(Mesh.GetVertex(triVerts[z0])); int e1 = add_or_append_vertex(Mesh.GetVertex(triVerts[z1])); int graph_eid = Graph.AppendEdge(e0, e1, (int)TriangleCase.OnEdge); - if (WantGraphEdgeInfo) + if (graph_eid >= 0 && WantGraphEdgeInfo) add_on_edge(graph_eid, tid, triEdges[z0], new Index2i(e0, e1)); } else { @@ -164,7 +164,7 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f add_edge_pos(triVerts[i], triVerts[j], cross); int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); - if (WantGraphEdgeInfo) + if (graph_eid >= 0 && WantGraphEdgeInfo) add_edge_vert(graph_eid, tid, triEdges[(z0+1)%3], triVerts[z0], new Index2i(vert_vid, cross_vid)); } @@ -206,7 +206,7 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f if (ev0 != ev1) { Util.gDevAssert(ev0 != int.MinValue && ev1 != int.MinValue); int graph_eid = Graph.AppendEdge(ev0, ev1, (int)TriangleCase.EdgeEdge); - if (WantGraphEdgeInfo) + if (graph_eid >= 0 && WantGraphEdgeInfo) add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]), new Index2i(ev0,ev1)); } } From dba186b7f857479a781edff08efcb636ed70defa Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 1 Apr 2018 23:09:21 -0400 Subject: [PATCH 060/225] added FindNearestVertex --- spatial/DMeshAABBTree.cs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 90d1ac0c..06c549f6 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -174,6 +174,74 @@ protected void find_nearest_tri(int iBox, Vector3d p, ref double fNearestSqr, re + /// + /// Find the vertex closest to p, within distance fMaxDist, or return InvalidID + /// + public virtual int FindNearestVertex(Vector3d p, double fMaxDist = double.MaxValue) + { + if (mesh_timestamp != mesh.ShapeTimestamp) + throw new Exception("DMeshAABBTree3.FindNearestVertex: mesh has been modified since tree construction"); + + double fNearestSqr = (fMaxDist < double.MaxValue) ? fMaxDist * fMaxDist : double.MaxValue; + int vNearID = DMesh3.InvalidID; + find_nearest_vtx(root_index, p, ref fNearestSqr, ref vNearID); + return vNearID; + } + protected void find_nearest_vtx(int iBox, Vector3d p, ref double fNearestSqr, ref int vid) + { + int idx = box_to_index[iBox]; + if (idx < triangles_end) { // triange-list case, array is [N t1 t2 ... tN] + int num_tris = index_list[idx]; + for (int i = 1; i <= num_tris; ++i) { + int ti = index_list[idx + i]; + if (TriangleFilterF != null && TriangleFilterF(ti) == false) + continue; + Vector3i tv = mesh.GetTriangle(ti); + for ( int j = 0; j < 3; ++j ) { + double dsqr = mesh.GetVertex(tv[j]).DistanceSquared(ref p); + if ( dsqr < fNearestSqr ) { + fNearestSqr = dsqr; + vid = tv[j]; + } + } + } + + } else { // internal node, either 1 or 2 child boxes + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + double fChild1DistSqr = box_distance_sqr(iChild1, p); + if (fChild1DistSqr <= fNearestSqr) + find_nearest_vtx(iChild1, p, ref fNearestSqr, ref vid); + + } else { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = index_list[idx + 1] - 1; + + double fChild1DistSqr = box_distance_sqr(iChild1, p); + double fChild2DistSqr = box_distance_sqr(iChild2, p); + if (fChild1DistSqr < fChild2DistSqr) { + if (fChild1DistSqr < fNearestSqr) { + find_nearest_vtx(iChild1, p, ref fNearestSqr, ref vid); + if (fChild2DistSqr < fNearestSqr) + find_nearest_vtx(iChild2, p, ref fNearestSqr, ref vid); + } + } else { + if (fChild2DistSqr < fNearestSqr) { + find_nearest_vtx(iChild2, p, ref fNearestSqr, ref vid); + if (fChild1DistSqr < fNearestSqr) + find_nearest_vtx(iChild1, p, ref fNearestSqr, ref vid); + } + } + + } + } + } + + + + + /// /// Does this ISpatial implementation support ray-triangle intersection? (yes) /// From 39b02e004b968101107b02cd4dac3ad044beec87 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 1 Apr 2018 23:10:10 -0400 Subject: [PATCH 061/225] added dijkstra variant that computes until a specific node is frozen, and GetPathToSeed() that finds sequence from point back to whatever seed it came from --- core/DijkstraGraphDistance.cs | 137 ++++++++++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 16 deletions(-) diff --git a/core/DijkstraGraphDistance.cs b/core/DijkstraGraphDistance.cs index 80a17de6..6e1f20c1 100644 --- a/core/DijkstraGraphDistance.cs +++ b/core/DijkstraGraphDistance.cs @@ -14,6 +14,7 @@ namespace g3 /// Construction is somewhat complicated, but see shortcut static /// methods at end of file for common construction cases: /// - MeshVertices(mesh) - compute on vertices of mesh + /// - MeshVertices(mesh) - compute on vertices of mesh /// /// public class DijkstraGraphDistance @@ -113,10 +114,30 @@ public DijkstraGraphDistance(int nMaxID, bool bSparse, foreach (var v in seeds) AddSeed((int)v.x, (float)v.y); } + } + + /// + /// shortcut to construct graph for mesh vertices + /// + public static DijkstraGraphDistance MeshVertices(DMesh3 mesh, bool bSparse = false) + { + return (bSparse) ? + new DijkstraGraphDistance( mesh.MaxVertexID, true, + (id) => { return mesh.IsVertex(id); }, + (a, b) => { return (float)mesh.GetVertex(a).Distance(mesh.GetVertex(b)); }, + mesh.VtxVerticesItr, null) + : new DijkstraGraphDistance( mesh.MaxVertexID, false, + (id) => { return true; }, + (a, b) => { return (float)mesh.GetVertex(a).Distance(mesh.GetVertex(b)); }, + mesh.VtxVerticesItr, null); } + + /// + /// reset internal data structures/etc + /// public void Reset() { if ( SparseNodes != null ) { @@ -145,7 +166,7 @@ public void AddSeed(int id, float seed_dist) SparseQueue.Enqueue(g, seed_dist); } else { Debug.Assert(DenseQueue.Contains(id) == false); - enqueue_node_dense(id, seed_dist); + enqueue_node_dense(id, seed_dist, -1); } Seeds.Add(id); } @@ -242,6 +263,60 @@ protected void ComputeToMaxDistance_Dense(float fMaxDistance) } + + + /// + /// Compute distances until node_id is frozen, or (optional) max distance is reached + /// Terminates early, so Queue may not be empty + /// + public void ComputeToNode(int node_id, float fMaxDistance = float.MaxValue) + { + if (TrackOrder == true) + order = new List(); + + if (SparseNodes != null) + ComputeToNode_Sparse(node_id, fMaxDistance); + else + ComputeToNode_Dense(node_id, fMaxDistance); + } + protected void ComputeToNode_Sparse(int node_id, float fMaxDistance) + { + while (SparseQueue.Count > 0) { + GraphNode g = SparseQueue.Dequeue(); + max_value = Math.Max(g.priority, max_value); + if (max_value > fMaxDistance) + return; + g.frozen = true; + if (TrackOrder) + order.Add(g.id); + if (g.id == node_id) + return; + update_neighbours_sparse(g); + } + } + protected void ComputeToNode_Dense(int node_id, float fMaxDistance) + { + while (DenseQueue.Count > 0) { + float idx_priority = DenseQueue.FirstPriority; + max_value = Math.Max(idx_priority, max_value); + if (max_value > fMaxDistance) + return; + int idx = DenseQueue.Dequeue(); + GraphNodeStruct g = DenseNodes[idx]; + g.frozen = true; + if (TrackOrder) + order.Add(g.id); + g.distance = max_value; + DenseNodes[idx] = g; + if (g.id == node_id) + return; + update_neighbours_dense(g.id); + } + } + + + + /// /// Get the maximum distance encountered during the Compute() /// @@ -268,6 +343,10 @@ public float GetDistance(int id) + /// + /// Get (internal) list of frozen nodes in increasing distance-order. + /// Requries that TrackOrder=true before Compute call. + /// public List GetOrder() { if (TrackOrder == false) @@ -277,6 +356,43 @@ public List GetOrder() + /// + /// Walk from node fromv back to the (graph-)nearest seed point. + /// + public bool GetPathToSeed(int fromv, List path) + { + if ( SparseNodes != null ) { + GraphNode g = get_node(fromv); + if (g.frozen == false) + return false; + path.Add(fromv); + while (g.parent != null) { + path.Add(g.parent.id); + g = g.parent; + } + return true; + } else { + GraphNodeStruct g = DenseNodes[fromv]; + if (g.frozen == false) + return false; + path.Add(fromv); + while ( g.parent != -1 ) { + path.Add(g.parent); + g = DenseNodes[g.parent]; + } + return true; + } + } + + + + + /* + * Internals below here + */ + + + GraphNode get_node(int id) { GraphNode g = SparseNodes[id]; @@ -309,6 +425,7 @@ void update_neighbours_sparse(GraphNode parent) SparseQueue.Update(nbr, nbr_dist); } } else { + nbr.parent = parent; SparseQueue.Enqueue(nbr, nbr_dist); } } @@ -317,9 +434,9 @@ void update_neighbours_sparse(GraphNode parent) - void enqueue_node_dense(int id, float dist) + void enqueue_node_dense(int id, float dist, int parent_id) { - GraphNodeStruct g = new GraphNodeStruct(id, -1, dist); + GraphNodeStruct g = new GraphNodeStruct(id, parent_id, dist); DenseNodes[id] = g; DenseQueue.Insert(id, dist); } @@ -344,24 +461,12 @@ void update_neighbours_dense(int parent_id) DenseNodes[nbr_id] = nbr; } } else { - enqueue_node_dense(nbr_id, nbr_dist); + enqueue_node_dense(nbr_id, nbr_dist, parent_id); } } } - /// - /// shortcut to setup functions for mesh vertices - /// - public static DijkstraGraphDistance MeshVertices(DMesh3 mesh) - { - return new DijkstraGraphDistance( - mesh.MaxVertexID, false, - (id) => { return true; }, - (a, b) => { return (float)mesh.GetVertex(a).Distance(mesh.GetVertex(b)); }, - mesh.VtxVerticesItr); - } - } } From 457c8985d9572bed8763aed8a82e2c9c66fb32ec Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 1 Apr 2018 23:10:27 -0400 Subject: [PATCH 062/225] rebuild loop and span from vertices --- mesh/EdgeLoop.cs | 21 +++++++++++++++++++++ mesh/EdgeSpan.cs | 21 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/mesh/EdgeLoop.cs b/mesh/EdgeLoop.cs index 9534d748..c339fba3 100644 --- a/mesh/EdgeLoop.cs +++ b/mesh/EdgeLoop.cs @@ -74,6 +74,27 @@ public static EdgeLoop FromEdges(DMesh3 mesh, IList edges) } + /// + /// construct EdgeLoop from a list of vertices of mesh + /// + public static EdgeLoop FromVertices(DMesh3 mesh, IList vertices) + { + int NV = vertices.Count; + int[] Vertices = new int[NV]; + for (int i = 0; i < NV; ++i) + Vertices[i] = vertices[i]; + int NE = NV; + int[] Edges = new int[NE]; + for (int i = 0; i < NE; ++i) { + Edges[i] = mesh.FindEdge(Vertices[i], Vertices[(i + 1)%NE]); + if (Edges[i] == DMesh3.InvalidID) + throw new Exception("EdgeLoop.FromVertices: vertices are not connected by edge!"); + } + return new EdgeLoop(mesh, Vertices, Edges, false); + } + + + /// /// construct EdgeLoop from a list of vertices of mesh /// if loop is a boundary edge, we can correct orientation if requested diff --git a/mesh/EdgeSpan.cs b/mesh/EdgeSpan.cs index a07f6c2b..0c038aed 100644 --- a/mesh/EdgeSpan.cs +++ b/mesh/EdgeSpan.cs @@ -63,6 +63,27 @@ public static EdgeSpan FromEdges(DMesh3 mesh, IList edges) } + /// + /// construct EdgeSpan from a list of vertices of mesh + /// + public static EdgeSpan FromVertices(DMesh3 mesh, IList vertices) + { + int NV = vertices.Count; + int[] Vertices = new int[NV]; + for (int i = 0; i < NV; ++i) + Vertices[i] = vertices[i]; + int NE = NV - 1; + int[] Edges = new int[NE]; + for ( int i = 0; i < NE; ++i ) { + Edges[i] = mesh.FindEdge(Vertices[i], Vertices[i + 1]); + if (Edges[i] == DMesh3.InvalidID) + throw new Exception("EdgeSpan.FromVertices: vertices are not connected by edge!"); + } + return new EdgeSpan(mesh, Vertices, Edges, false); + } + + + public int VertexCount { get { return Vertices.Length; } } From 5eec1473c017e9631dce09e6b52a3ab1b57a28c0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 1 Apr 2018 23:45:59 -0400 Subject: [PATCH 063/225] utility fn, AddTrianglesMeshChange, and initialize variant for RemoveTrianglesMeshChange that doesnt require simultaneous apply. Renamed existing Initailize() fn (breaking change) --- math/IndexUtil.cs | 25 +++- mesh/DMesh3Changes.cs | 177 +++++++++++++++++++++++++++- mesh_selection/MeshFaceSelection.cs | 5 + 3 files changed, 200 insertions(+), 7 deletions(-) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index a4c6937f..983d6fdd 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -318,7 +318,12 @@ public static void Apply(int[] indices, IList map) - + public static void TrianglesToVertices(DMesh3 mesh, IEnumerable triangles, HashSet vertices) { + foreach ( int tid in triangles ) { + Index3i tv = mesh.GetTriangle(tid); + vertices.Add(tv.a); vertices.Add(tv.b); vertices.Add(tv.c); + } + } public static void TrianglesToVertices(DMesh3 mesh, HashSet triangles, HashSet vertices) { foreach ( int tid in triangles ) { Index3i tv = mesh.GetTriangle(tid); @@ -326,6 +331,13 @@ public static void TrianglesToVertices(DMesh3 mesh, HashSet triangles, Hash } } + + public static void TrianglesToEdges(DMesh3 mesh, IEnumerable triangles, HashSet edges) { + foreach ( int tid in triangles ) { + Index3i te = mesh.GetTriEdges(tid); + edges.Add(te.a); edges.Add(te.b); edges.Add(te.c); + } + } public static void TrianglesToEdges(DMesh3 mesh, HashSet triangles, HashSet edges) { foreach ( int tid in triangles ) { Index3i te = mesh.GetTriEdges(tid); @@ -333,8 +345,15 @@ public static void TrianglesToEdges(DMesh3 mesh, HashSet triangles, HashSet } } - public static void EdgesToVertices(DMesh3 mesh, HashSet edges, HashSet vertices) - { + + + public static void EdgesToVertices(DMesh3 mesh, IEnumerable edges, HashSet vertices) { + foreach (int eid in edges) { + Index2i ev = mesh.GetEdgeV(eid); + vertices.Add(ev.a); vertices.Add(ev.b); + } + } + public static void EdgesToVertices(DMesh3 mesh, HashSet edges, HashSet vertices) { foreach (int eid in edges) { Index2i ev = mesh.GetEdgeV(eid); vertices.Add(ev.a); vertices.Add(ev.b); diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index 1cca940b..d10f733a 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -26,12 +26,11 @@ public RemoveTrianglesMeshChange() } - public void Initialize(DMesh3 mesh, IEnumerable triangles) + public void InitializeFromApply(DMesh3 mesh, IEnumerable triangles) { initialize_buffers(mesh); bool has_groups = mesh.HasTriangleGroups; - foreach ( int tid in triangles ) { if (!mesh.IsTriangle(tid)) continue; @@ -54,6 +53,44 @@ public void Initialize(DMesh3 mesh, IEnumerable triangles) } + + public void InitializeFromExisting(DMesh3 mesh, IEnumerable remove_t) + { + initialize_buffers(mesh); + bool has_groups = mesh.HasTriangleGroups; + + HashSet triangles = new HashSet(remove_t); + HashSet vertices = new HashSet(); + IndexUtil.TrianglesToVertices(mesh, remove_t, vertices); + List save_v = new List(); + foreach ( int vid in vertices ) { + bool all_contained = true; + foreach ( int tid in mesh.VtxTrianglesItr(vid) ) { + if (triangles.Contains(tid) == false) { + all_contained = false; + break; + } + } + if (all_contained) + save_v.Add(vid); + } + + foreach (int vid in save_v) { + save_vertex(mesh, vid, true); + } + + foreach (int tid in remove_t) { + Util.gDevAssert(mesh.IsTriangle(tid)); + Index3i tv = mesh.GetTriangle(tid); + Index4i tri = new Index4i(tv.a, tv.b, tv.c, + has_groups ? mesh.GetTriangleGroup(tid) : DMesh3.InvalidID); + RemovedT.Add(tid); + Triangles.Add(tri); + } + } + + + public void Apply(DMesh3 mesh) { int N = RemovedT.size; @@ -103,9 +140,9 @@ public void Revert(DMesh3 mesh) - bool save_vertex(DMesh3 mesh, int vid) + bool save_vertex(DMesh3 mesh, int vid, bool force = false) { - if ( mesh.VerticesRefCounts.refCount(vid) == 2 ) { + if ( force || mesh.VerticesRefCounts.refCount(vid) == 2 ) { RemovedV.Add(vid); Positions.Add(mesh.GetVertex(vid)); if (Normals != null) @@ -137,4 +174,136 @@ void initialize_buffers(DMesh3 mesh) } + + + + + + + + /// + /// Add triangles from mesh and store necessary data to be able to reverse the change. + /// Vertex and Triangle IDs will be restored on Revert() + /// Currently does *not* restore the same EdgeIDs + /// + public class AddTrianglesMeshChange + { + protected DVector AddedV; + protected DVector Positions; + protected DVector Normals; + protected DVector Colors; + protected DVector UVs; + + protected DVector AddedT; + protected DVector Triangles; + + public AddTrianglesMeshChange() + { + } + + + public void InitializeFromExisting(DMesh3 mesh, IEnumerable added_v, IEnumerable added_t) + { + initialize_buffers(mesh); + bool has_groups = mesh.HasTriangleGroups; + + foreach (int vid in added_v) { + Util.gDevAssert(mesh.IsVertex(vid)); + append_vertex(mesh, vid); + } + + foreach (int tid in added_t) { + Util.gDevAssert(mesh.IsTriangle(tid)); + + Index3i tv = mesh.GetTriangle(tid); + Index4i tri = new Index4i(tv.a, tv.b, tv.c, + has_groups ? mesh.GetTriangleGroup(tid) : DMesh3.InvalidID); + AddedT.Add(tid); + Triangles.Add(tri); + } + } + + + public void Apply(DMesh3 mesh) + { + int NV = AddedV.size; + if (NV > 0) { + NewVertexInfo vinfo = new NewVertexInfo(Positions[0]); + mesh.BeginUnsafeVerticesInsert(); + for (int i = 0; i < NV; ++i) { + int vid = AddedV[i]; + vinfo.v = Positions[i]; + if (Normals != null) { vinfo.bHaveN = true; vinfo.n = Normals[i]; } + if (Colors != null) { vinfo.bHaveC = true; vinfo.c = Colors[i]; } + if (UVs != null) { vinfo.bHaveUV = true; vinfo.uv = UVs[i]; } + MeshResult result = mesh.InsertVertex(vid, ref vinfo, true); + if (result != MeshResult.Ok) + throw new Exception("AddTrianglesMeshChange.Revert: error in InsertVertex(" + vid.ToString() + "): " + result.ToString()); + } + mesh.EndUnsafeVerticesInsert(); + } + + int NT = AddedT.size; + if (NT > 0) { + mesh.BeginUnsafeTrianglesInsert(); + for (int i = 0; i < NT; ++i) { + int tid = AddedT[i]; + Index4i tdata = Triangles[i]; + Index3i tri = new Index3i(tdata.a, tdata.b, tdata.c); + MeshResult result = mesh.InsertTriangle(tid, tri, tdata.d, true); + if (result != MeshResult.Ok) + throw new Exception("AddTrianglesMeshChange.Revert: error in InsertTriangle(" + tid.ToString() + "): " + result.ToString()); + } + mesh.EndUnsafeTrianglesInsert(); + } + } + + + public void Revert(DMesh3 mesh) + { + int N = AddedT.size; + for (int i = 0; i < N; ++i) { + int tid = AddedT[i]; + MeshResult result = mesh.RemoveTriangle(AddedT[i], true, false); + if (result != MeshResult.Ok) + throw new Exception("AddTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); + } + } + + + + + void append_vertex(DMesh3 mesh, int vid) + { + AddedV.Add(vid); + Positions.Add(mesh.GetVertex(vid)); + if (Normals != null) + Normals.Add(mesh.GetVertexNormal(vid)); + if (Colors != null) + Colors.Add(mesh.GetVertexColor(vid)); + if (UVs != null) + UVs.Add(mesh.GetVertexUV(vid)); + } + + + void initialize_buffers(DMesh3 mesh) + { + AddedV = new DVector(); + Positions = new DVector(); + if (mesh.HasVertexNormals) + Normals = new DVector(); + if (mesh.HasVertexColors) + Colors = new DVector(); + if (mesh.HasVertexUVs) + UVs = new DVector(); + + AddedT = new DVector(); + Triangles = new DVector(); + } + + } + + + + } diff --git a/mesh_selection/MeshFaceSelection.cs b/mesh_selection/MeshFaceSelection.cs index 9a697f92..31f32254 100644 --- a/mesh_selection/MeshFaceSelection.cs +++ b/mesh_selection/MeshFaceSelection.cs @@ -145,6 +145,11 @@ public void Select(Func selectF) Select(temp); } + + public void SelectVertexOneRing(int vid) { + foreach (int tid in Mesh.VtxTrianglesItr(vid)) + add(tid); + } public void SelectVertexOneRings(int[] vertices) { for ( int i = 0; i < vertices.Length; ++i ) { From c5cf0adbbc27f6147a84be8d1a629e275b5fa50e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 2 Apr 2018 13:34:13 -0400 Subject: [PATCH 064/225] make dvector enumerable --- core/DVector.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/core/DVector.cs b/core/DVector.cs index 5fb3064e..76de9c56 100644 --- a/core/DVector.cs +++ b/core/DVector.cs @@ -1,7 +1,6 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace g3 { @@ -12,7 +11,7 @@ namespace g3 // - this[] operator does not check bounds, so it can write to any valid Block // - some fns discard Blocks beyond iCurBlock // - wtf... - public class DVector + public class DVector : IEnumerable { List Blocks; int iCurBlock; @@ -375,6 +374,22 @@ public static unsafe void FastGetBuffer(DVector v, int * pBuffer) + public IEnumerator GetEnumerator() { + for (int bi = 0; bi < iCurBlock; ++bi) { + T[] block = Blocks[bi]; + for (int k = 0; k < nBlockSize; ++k) + yield return block[k]; + } + T[] lastblock = Blocks[iCurBlock]; + for (int k = 0; k < iCurBlockUsed; ++k) + yield return lastblock[k]; + } + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + + // block iterator public struct DBlock { From e77194a0698c39bffceff076c2e7958c2dbcd4c0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 2 Apr 2018 13:34:47 -0400 Subject: [PATCH 065/225] add optional apply/revert actions --- mesh/DMesh3Changes.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index d10f733a..58cbdf86 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -20,7 +20,10 @@ public class RemoveTrianglesMeshChange protected DVector RemovedT; protected DVector Triangles; - + + public Action,IEnumerable> OnApplyF; + public Action, IEnumerable> OnRevertF; + public RemoveTrianglesMeshChange() { } @@ -100,6 +103,7 @@ public void Apply(DMesh3 mesh) if (result != MeshResult.Ok) throw new Exception("RemoveTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); } + OnApplyF(RemovedV, RemovedT); } @@ -135,6 +139,8 @@ public void Revert(DMesh3 mesh) } mesh.EndUnsafeTrianglesInsert(); } + + OnRevertF(RemovedV, RemovedT); } @@ -197,6 +203,10 @@ public class AddTrianglesMeshChange protected DVector AddedT; protected DVector Triangles; + public Action, IEnumerable> OnApplyF; + public Action, IEnumerable> OnRevertF; + + public AddTrianglesMeshChange() { } @@ -207,9 +217,11 @@ public void InitializeFromExisting(DMesh3 mesh, IEnumerable added_v, IEnume initialize_buffers(mesh); bool has_groups = mesh.HasTriangleGroups; - foreach (int vid in added_v) { - Util.gDevAssert(mesh.IsVertex(vid)); - append_vertex(mesh, vid); + if (added_v != null) { + foreach (int vid in added_v) { + Util.gDevAssert(mesh.IsVertex(vid)); + append_vertex(mesh, vid); + } } foreach (int tid in added_t) { @@ -256,6 +268,8 @@ public void Apply(DMesh3 mesh) } mesh.EndUnsafeTrianglesInsert(); } + + OnApplyF(AddedV, AddedT); } @@ -268,6 +282,8 @@ public void Revert(DMesh3 mesh) if (result != MeshResult.Ok) throw new Exception("AddTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); } + + OnRevertF(AddedV, AddedT); } From 026281cd952e0f25dacc8486f040de0e83b291b7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 2 Apr 2018 15:30:39 -0400 Subject: [PATCH 066/225] support for cancelling sdf and marching cubes calculations --- mesh_generators/MarchingCubes.cs | 8 +++++++ spatial/MeshSignedDistanceGrid.cs | 35 ++++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/mesh_generators/MarchingCubes.cs b/mesh_generators/MarchingCubes.cs index 316884d5..fdf850ff 100644 --- a/mesh_generators/MarchingCubes.cs +++ b/mesh_generators/MarchingCubes.cs @@ -61,6 +61,10 @@ public enum RootfindingModes { SingleLerp, LerpSteps, Bisection } /// public int RootModeSteps = 5; + + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + /* * Outputs */ @@ -207,6 +211,8 @@ void generate_parallel() GridCell cell = new GridCell(); Vector3d[] vertlist = new Vector3d[12]; for (int yi = 0; yi < CellDimensions.y; ++yi) { + if (CancelF()) + return; // compute full cell at x=0, then slide along x row, which saves half of value computes Vector3i idx = new Vector3i(0, yi, zi); initialize_cell(cell, ref idx); @@ -235,6 +241,8 @@ void generate_basic() for (int zi = 0; zi < CellDimensions.z; ++zi) { for (int yi = 0; yi < CellDimensions.y; ++yi) { + if (CancelF()) + return; // compute full cell at x=0, then slide along x row, which saves half of value computes Vector3i idx = new Vector3i(0, yi, zi); initialize_cell(cell, ref idx); diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index c5a24fd7..8c1dad64 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -87,6 +87,9 @@ public enum InsideModes // grid of per-cell crossing or parity counts public bool WantIntersectionsGrid = false; + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + public bool DebugPrint = false; @@ -196,6 +199,8 @@ void make_level_set3(Vector3f origin, float dx, double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; foreach (int tid in Mesh.TriangleIndices()) { + if (tid % 100 == 0 && CancelF()) + break; Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); // real ijk coordinates of xp/xq/xr @@ -226,20 +231,27 @@ void make_level_set3(Vector3f origin, float dx, } } } + if (CancelF()) + return; if (ComputeSigns == true) { if (DebugPrint) System.Console.WriteLine("done narrow-band"); compute_intersections(origin, dx, ni, nj, nk, intersection_count); + if (CancelF()) + return; if (DebugPrint) System.Console.WriteLine("done intersections"); if (ComputeMode == ComputeModes.FullGrid) { // and now we fill in the rest of the distances with fast sweeping - for (int pass = 0; pass < 2; ++pass) + for (int pass = 0; pass < 2; ++pass) { sweep_pass(origin, dx, distances, closest_tri); - if (DebugPrint) System.Console.WriteLine("done sweeping"); + if (CancelF()) + return; + } + if (DebugPrint) System.Console.WriteLine("done sweeping"); } else { // nothing! if (DebugPrint) System.Console.WriteLine("skipped sweeping"); @@ -248,6 +260,8 @@ void make_level_set3(Vector3f origin, float dx, // then figure out signs (inside/outside) from intersection counts compute_signs(ni, nj, nk, distances, intersection_count); + if (CancelF()) + return; if (DebugPrint) System.Console.WriteLine("done signs"); @@ -297,7 +311,13 @@ void make_level_set3_parallel(Vector3f origin, float dx, int wi = ni / 2, wj = nj / 2, wk = nk / 2; SpinLock[] grid_locks = new SpinLock[8]; + bool abort = false; gParallel.ForEach(Mesh.TriangleIndices(), (tid) => { + if (tid % 100 == 0) + abort = CancelF(); + if (abort) + return; + Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); @@ -339,19 +359,26 @@ void make_level_set3_parallel(Vector3f origin, float dx, } }); + if (CancelF()) + return; if (ComputeSigns == true) { if (DebugPrint) System.Console.WriteLine("done narrow-band"); compute_intersections(origin, dx, ni, nj, nk, intersection_count); + if (CancelF()) + return; if (DebugPrint) System.Console.WriteLine("done intersections"); if (ComputeMode == ComputeModes.FullGrid) { // and now we fill in the rest of the distances with fast sweeping - for (int pass = 0; pass < 2; ++pass) + for (int pass = 0; pass < 2; ++pass) { sweep_pass(origin, dx, distances, closest_tri); + if (CancelF()) + return; + } if (DebugPrint) System.Console.WriteLine("done sweeping"); } else { // nothing! @@ -362,6 +389,8 @@ void make_level_set3_parallel(Vector3f origin, float dx, // then figure out signs (inside/outside) from intersection counts compute_signs(ni, nj, nk, distances, intersection_count); + if (CancelF()) + return; if (WantIntersectionsGrid) intersections_grid = intersection_count; From ad2035403399d1feb9171d7141dd30a96a3f5507 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 10:37:25 -0400 Subject: [PATCH 067/225] bugfix for binsgrid, try to use more sensible grid resolution for problem size in InsertUVPolyCurve --- mesh_ops/MeshInsertUVPolyCurve.cs | 6 +++++- spatial/TriangleBinsGrid2d.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 679896a4..720de2b6 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -140,9 +140,13 @@ void insert_corners() PrimalQuery2d query = new PrimalQuery2d(PointF); if (UseTriSpatial) { + int count = Mesh.TriangleCount + Curve.VertexCount; + int bins = 32; + if (count < 25) bins = 8; + else if (count < 100) bins = 16; AxisAlignedBox3d bounds3 = Mesh.CachedBounds; AxisAlignedBox2d bounds2 = new AxisAlignedBox2d(bounds3.Min.xy, bounds3.Max.xy); - triSpatial = new TriangleBinsGrid2d(bounds2, 32); + triSpatial = new TriangleBinsGrid2d(bounds2, bins); foreach (int tid in Mesh.TriangleIndices()) spatial_add_triangle(tid); } diff --git a/spatial/TriangleBinsGrid2d.cs b/spatial/TriangleBinsGrid2d.cs index 93716cf5..99702f12 100644 --- a/spatial/TriangleBinsGrid2d.cs +++ b/spatial/TriangleBinsGrid2d.cs @@ -42,7 +42,7 @@ public TriangleBinsGrid2d(AxisAlignedBox2d bounds, int numCells) indexer = new ShiftGridIndexer2(origin, cellsize); bins_x = (int)(bounds.Width / cellsize) + 2; - bins_y = (int)(bounds.Width / cellsize) + 2; + bins_y = (int)(bounds.Height / cellsize) + 2; grid_bounds = new AxisAlignedBox2i(0, 0, bins_x-1, bins_y-1); bins_list = new SmallListSet(); bins_list.Resize(bins_x * bins_y); From 0dd0c0ac4a3b7b17cb8bacce0ea0e69a151f687a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 11:05:33 -0400 Subject: [PATCH 068/225] moved over from gsCore --- .../TriangulatedPolygonGenerator.cs | 73 ++++++++++++++++ mesh_ops/MeshInsertPolygon.cs | 84 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 mesh_generators/TriangulatedPolygonGenerator.cs create mode 100644 mesh_ops/MeshInsertPolygon.cs diff --git a/mesh_generators/TriangulatedPolygonGenerator.cs b/mesh_generators/TriangulatedPolygonGenerator.cs new file mode 100644 index 00000000..12e5b9ef --- /dev/null +++ b/mesh_generators/TriangulatedPolygonGenerator.cs @@ -0,0 +1,73 @@ +using System; + +namespace g3 +{ + /// + /// Triangulate a 2D polygon-with-holes by inserting it's edges into a meshed rectangle + /// and then removing the triangles outside the polygon. + /// + public class TriangulatedPolygonGenerator : MeshGenerator + { + public GeneralPolygon2d Polygon; + public Vector3f FixedNormal = Vector3f.AxisZ; + + public TrivialRectGenerator.UVModes UVMode = TrivialRectGenerator.UVModes.FullUVSquare; + + override public MeshGenerator Generate() + { + AxisAlignedBox2d bounds = Polygon.Bounds; + double padding = 0.1 * bounds.DiagonalLength; + bounds.Expand(padding); + + TrivialRectGenerator rectgen = new TrivialRectGenerator(); + rectgen.Width = (float)bounds.Width; + rectgen.Height = (float)bounds.Height; + rectgen.IndicesMap = new Index2i(1, 2); + rectgen.UVMode = UVMode; + rectgen.Clockwise = true; // MeshPolygonInserter assumes mesh faces are CW? (except code says CCW...) + rectgen.Generate(); + DMesh3 base_mesh = new DMesh3(); + rectgen.MakeMesh(base_mesh); + + GeneralPolygon2d shiftPolygon = new GeneralPolygon2d(Polygon); + Vector2d shift = bounds.Center; + shiftPolygon.Translate(-shift); + + MeshInsertPolygon insert = new MeshInsertPolygon() { + Mesh = base_mesh, Polygon = shiftPolygon + }; + bool bOK = insert.Insert(); + if (!bOK) + throw new Exception("TriangulatedPolygonGenerator: failed to Insert()"); + + MeshFaceSelection selected = insert.InteriorTriangles; + MeshEditor editor = new MeshEditor(base_mesh); + editor.RemoveTriangles((tid) => { return selected.IsSelected(tid) == false; }, true); + + DMesh3 compact = new DMesh3(base_mesh, true); + + int NV = compact.VertexCount; + vertices = new VectorArray3d(NV); + uv = new VectorArray2f(NV); + normals = new VectorArray3f(NV); + Vector3d shift3 = new Vector3d(shift.x, shift.y, 0); + for (int vi = 0; vi < NV; ++vi) { + vertices[vi] = compact.GetVertex(vi) + shift3; + uv[vi] = compact.GetVertexUV(vi); + normals[vi] = FixedNormal; + } + + int NT = compact.TriangleCount; + triangles = new IndexArray3i(NT); + for (int ti = 0; ti < NT; ++ti) + triangles[ti] = compact.GetTriangle(ti); + + return this; + } + } + + + + + +} diff --git a/mesh_ops/MeshInsertPolygon.cs b/mesh_ops/MeshInsertPolygon.cs new file mode 100644 index 00000000..1dbe4fa6 --- /dev/null +++ b/mesh_ops/MeshInsertPolygon.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; + +namespace g3 +{ + /// + /// Insert Polygon into Mesh. Assumption is that Mesh has 3D coordinates (u,v,0). + /// This is basically a helper/wrapper around MeshInsertUVPolyCurve. + /// Inserted edge set is avaliable as .InsertedPolygonEdges, and + /// triangles inside polygon as .InteriorTriangles + /// + public class MeshInsertPolygon + { + public DMesh3 Mesh; + public GeneralPolygon2d Polygon; + + + public bool SimplifyInsertion = true; + + public MeshInsertUVPolyCurve OuterInsert; + public List HoleInserts; + public HashSet InsertedPolygonEdges; + public MeshFaceSelection InteriorTriangles; + + public bool Insert() + { + OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer); + Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok); + OuterInsert.Apply(); + if (SimplifyInsertion) + OuterInsert.Simplify(); + + HoleInserts = new List(Polygon.Holes.Count); + for (int hi = 0; hi < Polygon.Holes.Count; ++hi) { + MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(Mesh, Polygon.Holes[hi]); + Util.gDevAssert(insert.Validate() == ValidationStatus.Ok); + insert.Apply(); + if (SimplifyInsertion) + insert.Simplify(); + HoleInserts.Add(insert); + } + + + // find a triangle connected to loop that is inside the polygon + // [TODO] maybe we could be a bit more robust about this? at least + // check if triangle is too degenerate... + int seed_tri = -1; + EdgeLoop outer_loop = OuterInsert.Loops[0]; + for (int i = 0; i < outer_loop.EdgeCount; ++i) { + if ( ! Mesh.IsEdge(outer_loop.Edges[i]) ) + continue; + + Index2i et = Mesh.GetEdgeT(outer_loop.Edges[i]); + Vector3d ca = Mesh.GetTriCentroid(et.a); + bool in_a = Polygon.Outer.Contains(ca.xy); + Vector3d cb = Mesh.GetTriCentroid(et.b); + bool in_b = Polygon.Outer.Contains(cb.xy); + if (in_a && in_b == false) { + seed_tri = et.a; + break; + } else if (in_b && in_a == false) { + seed_tri = et.b; + break; + } + } + if (seed_tri == -1) + throw new Exception("MeshPolygonsInserter: could not find seed triangle!"); + + // make list of all outer & hole edges + InsertedPolygonEdges = new HashSet(outer_loop.Edges); + foreach (var insertion in HoleInserts) { + foreach (int eid in insertion.Loops[0].Edges) + InsertedPolygonEdges.Add(eid); + } + + // flood-fill inside loop from seed triangle + InteriorTriangles = new MeshFaceSelection(Mesh); + InteriorTriangles.FloodFill(seed_tri, null, (eid) => { return InsertedPolygonEdges.Contains(eid) == false; }); + + return true; + } + + } +} From 9ac8f7b4c1901243e582340020a2b2ed7f2cf7ea Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 21:56:55 -0400 Subject: [PATCH 069/225] handle a failure case in MeshBoundaryLoops for nested-bowtie-loops, so that we don't throw by default --- mesh_selection/MeshBoundaryLoops.cs | 60 ++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/mesh_selection/MeshBoundaryLoops.cs b/mesh_selection/MeshBoundaryLoops.cs index 654d57da..029b30ed 100644 --- a/mesh_selection/MeshBoundaryLoops.cs +++ b/mesh_selection/MeshBoundaryLoops.cs @@ -25,7 +25,8 @@ public class MeshBoundaryLoops : IEnumerable public List Spans; // spans are unclosed loops public bool SawOpenSpans = false; // will be set to true if we find any open spans - + public bool FellBackToSpansOnFailure = false; // set to true if we had to add spans to recover from failure + // currently this happens if we cannot extract simple loops from a loop with bowties // What should we do if we encounter open spans. Mainly a result of EdgeFilter, but can also // happen on meshes w/ crazy bowties @@ -46,6 +47,10 @@ public enum FailureBehaviors // if enabled, only edges where this returns true are considered public Func EdgeFilterF = null; + // if we throw an exception, we will try to set FailureBowties, so that client + // can try repairing these vertices + public List FailureBowties = null; + public MeshBoundaryLoops(DMesh3 mesh, bool bAutoCompute = true) { @@ -309,9 +314,14 @@ public bool Compute() } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops - List subloops = extract_subloops(loop_verts, loop_edges, bowties); - for (int i = 0; i < subloops.Count; ++i) - Loops.Add(subloops[i]); + Subloops subloops = extract_subloops(loop_verts, loop_edges, bowties); + foreach ( var loop in subloops.Loops ) + Loops.Add(loop); + if ( subloops.Spans.Count > 0 ) { + FellBackToSpansOnFailure = true; + foreach (var span in subloops.Spans) + Spans.Add(span); + } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); @@ -391,6 +401,11 @@ int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry + struct Subloops + { + public List Loops; + public List Spans; + } // This is called when loopV contains one or more "bowtie" vertices. @@ -402,9 +417,14 @@ int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry // // Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges // arrays initialized. Perhaps to improve in future. - List extract_subloops(List loopV, List loopE, List bowties ) + // + // An unhandled case to think about is where we have a sequence [..A..B..A..B..] where + // A and B are bowties. In this case there are no A->A or B->B subloops. What should + // we do here?? + Subloops extract_subloops(List loopV, List loopE, List bowties ) { - List subs = new List(); + Subloops subs = new Subloops(); + subs.Loops = new List(); subs.Spans = new List(); // figure out which bowties we saw are actually duplicated in loopV List dupes = new List(); @@ -415,7 +435,7 @@ List extract_subloops(List loopV, List loopE, List bowt // we might not actually have any duplicates, if we got luck. Early out in that case if ( dupes.Count == 0 ) { - subs.Add(new EdgeLoop(Mesh) { + subs.Loops.Add(new EdgeLoop(Mesh) { Vertices = loopV.ToArray(), Edges = loopE.ToArray(), BowtieVertices = bowties.ToArray() }); return subs; @@ -441,9 +461,29 @@ List extract_subloops(List loopV, List loopE, List bowt } } } + + // failed to find a simple loop. Not sure what to do in this situation. + // If we don't want to throw, all we can do is convert the remaining + // loop to a span and return. + // (Or should we keep it as a loop and set flag??) if (bv_shortest == -1) { - throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: Cannot find a valid simple loop"); + if (FailureBehavior == FailureBehaviors.ThrowException) { + FailureBowties = dupes; + throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: Cannot find a valid simple loop"); + } + EdgeSpan span = new EdgeSpan(Mesh); + List verts = new List(); + for (int i = 0; i < loopV.Count; ++i) { + if (loopV[i] != -1) + verts.Add(loopV[i]); + } + span.Vertices = verts.ToArray(); + span.Edges = EdgeSpan.VerticesToEdges(Mesh, span.Vertices); + span.BowtieVertices = bowties.ToArray(); + subs.Spans.Add(span); + return subs; } + if (bv != bv_shortest) { bv = bv_shortest; // running again just to get start_i and end_i... @@ -456,7 +496,7 @@ List extract_subloops(List loopV, List loopE, List bowt loop.Vertices = extract_span(loopV, start_i, end_i, true); loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); - subs.Add(loop); + subs.Loops.Add(loop); // If there are no more duplicates of this bowtie, we can treat // it like a regular vertex now @@ -481,7 +521,7 @@ List extract_subloops(List loopV, List loopE, List bowt } loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); - subs.Add(loop); + subs.Loops.Add(loop); } return subs; From fab03e4188eda12d936185b09d9dcbe698ca9d6b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 22:02:37 -0400 Subject: [PATCH 070/225] function to repair bowtie by duplicating vtx for each disjoint triangle set --- mesh/MeshEditor.cs | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index a0079d62..d8db35f8 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -457,6 +457,66 @@ public void ReverseTriangles(IEnumerable triangles, bool bFlipVtxNormals = + /// + /// separate triangle one-ring at vertex into connected components, and + /// then duplicate vertex once for each component + /// + public void DisconnectBowtie(int vid) + { + List> sets = new List>(); + foreach ( int tid in Mesh.VtxTrianglesItr(vid)) { + Index3i nbrs = Mesh.GetTriNeighbourTris(tid); + bool found = false; + foreach ( List set in sets ) { + if ( set.Contains(nbrs.a) || set.Contains(nbrs.b) || set.Contains(nbrs.c) ) { + set.Add(tid); + found = true; + break; + } + } + if ( found == false ) { + List set = new List() { tid }; + sets.Add(set); + } + } + if (sets.Count == 1) + return; // not a bowtie! + sets.Sort(bowtie_sorter); + for ( int k = 1; k < sets.Count; ++k ) { + int copy_vid = Mesh.AppendVertex(Mesh, vid); + List tris = sets[k]; + foreach ( int tid in tris ) { + Index3i t = Mesh.GetTriangle(tid); + if (t.a == vid) t.a = copy_vid; + else if (t.b == vid) t.b = copy_vid; + else t.c = copy_vid; + Mesh.SetTriangle(tid, t, false); + } + } + } + static int bowtie_sorter(List l1, List l2) { + if (l1.Count == l2.Count) return 0; + return (l1.Count > l2.Count) ? -1 : 1; + } + + + + /// + /// Disconnect all bowtie vertices in mesh. Iterates because sometimes + /// disconnecting a bowtie creates new bowties (how??) + /// + public void DisconnectAllBowties(int nMaxIters = 10) + { + List bowties = new List(MeshIterators.BowtieVertices(Mesh)); + int iter = 0; + while (bowties.Count > 0 && iter++ < nMaxIters) { + foreach (int vid in bowties) + DisconnectBowtie(vid); + bowties = new List(MeshIterators.BowtieVertices(Mesh)); + } + } + + // in ReinsertSubmesh, a problem can arise where the mesh we are inserting has duplicate triangles of From d3d6628606dd6ad673955fdb66bd51f869e40f36 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 22:13:51 -0400 Subject: [PATCH 071/225] cutpastefix --- mesh/DMesh3.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 43f98bc2..a94bbc97 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -485,7 +485,7 @@ public bool GetVertex(int vID, ref NewVertexInfo vinfo, bool bWantNormals, bool return false; vinfo.v.Set(vertices[3 * vID], vertices[3 * vID + 1], vertices[3 * vID + 2]); vinfo.bHaveN = vinfo.bHaveUV = vinfo.bHaveC = false; - if (HasVertexColors && bWantNormals) { + if (HasVertexNormals && bWantNormals) { vinfo.bHaveN = true; vinfo.n.Set(normals[3 * vID], normals[3 * vID + 1], normals[3 * vID + 2]); } From a9cbd923bf39722509d1fc310bc7724595b11d05 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Apr 2018 23:56:53 -0400 Subject: [PATCH 072/225] utility fns --- mesh/MeshEditor.cs | 5 +++++ mesh_selection/MeshFaceSelection.cs | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index d8db35f8..d090e189 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -722,6 +722,11 @@ public void AppendBox(Frame3f frame, Vector3f size) boxgen.MakeMesh(mesh); AppendMesh(mesh, Mesh.AllocateTriangleGroup()); } + public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) + { + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(new Frame3f(pos), size); + } diff --git a/mesh_selection/MeshFaceSelection.cs b/mesh_selection/MeshFaceSelection.cs index 31f32254..c8d8dd89 100644 --- a/mesh_selection/MeshFaceSelection.cs +++ b/mesh_selection/MeshFaceSelection.cs @@ -167,6 +167,15 @@ public void SelectVertexOneRings(IEnumerable vertices) } + public void SelectEdgeTris(int eid) + { + Index2i et = Mesh.GetEdgeT(eid); + add(et.a); + if (et.b != DMesh3.InvalidID) + add(et.b); + } + + public void Deselect(int tid) { remove(tid); } From 5467244641bc3d3133134b29250c4aa49875a59a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 4 Apr 2018 00:04:34 -0400 Subject: [PATCH 073/225] detect an invalid case in DMesh3.MergeEdges --- mesh/DMesh3_edge_operators.cs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index ada7c3e0..72205841 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -838,9 +838,14 @@ public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_i merge_info.eKept = eab; merge_info.eRemoved = ecd; - // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? - - if (a != c) { + // if a/c or b/d are connected by an existing edge, we can't merge + if (a != c && find_edge(a,c) != DMesh3.InvalidID ) + return MeshResult.Failed_InvalidNeighbourhood; + if (b != d && find_edge(b, d) != DMesh3.InvalidID) + return MeshResult.Failed_InvalidNeighbourhood; + + // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? + if (a != c) { // replace c w/ a in edges and tris connected to c, and move edges to a foreach ( int eid in vertex_edges.ValueItr(c)) { if (eid == eDiscard) @@ -924,6 +929,7 @@ public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_i bool found = false; // in this loop, we compare 'other' vert_1 and vert_2 of edges around v1. // problem case is when vert_1 == vert_2 (ie two edges w/ same other vtx). + //restart_merge_loop: for (int i = 0; i < Nedges && found == false; ++i) { int edge_1 = edges_v[i]; if ( IsBoundaryEdge(edge_1) == false) @@ -944,11 +950,12 @@ public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_i merge_info.eRemovedExtra[vi] = edge_2; merge_info.eKeptExtra[vi] = edge_1; - //Nedges = edges_v.Count; // this code allows us to continue checking, ie in case we had - //i--; // multiple such edges. but I don't think it's possible. - found = true; // exit outer i loop - break; // exit inner j loop - } + //edges_v = vertex_edges_list(v1); // this code allows us to continue checking, ie in case we had + //Nedges = edges_v.Count; // multiple such edges. but I don't think it's possible. + //goto restart_merge_loop; + found = true; // exit outer i loop + break; // exit inner j loop + } } } } From 73d16cc000b5ea1a35cbf34ed4886cbdaa5f8011 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 4 Apr 2018 23:08:02 -0400 Subject: [PATCH 074/225] construct 3d curve from 2d polygon --- curve/DCurve3.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index 9a5b3c75..703ed7fa 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -50,6 +50,19 @@ public DCurve3(ISampledCurve3d icurve) Timestamp = 1; } + public DCurve3(Polygon2d poly, int ix = 0, int iy = 1) + { + int NV = poly.VertexCount; + this.vertices = new List(NV); + for (int k = 0; k < NV; ++k) { + Vector3d v = Vector3d.Zero; + v[ix] = poly[k].x; v[iy] = poly[k].y; + this.vertices.Add(v); + } + Closed = true; + Timestamp = 1; + } + public void AppendVertex(Vector3d v) { vertices.Add(v); Timestamp++; From 4a230029064b7139944301b05a2e38b0f4f052b3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 9 Apr 2018 12:03:30 -0400 Subject: [PATCH 075/225] use tasks framework in unity when using dotnet 4.6 --- core/gParallel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/gParallel.cs b/core/gParallel.cs index 827ef12e..53b4aa6b 100644 --- a/core/gParallel.cs +++ b/core/gParallel.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading; -#if !G3_USING_UNITY +#if !(NET_2_0 || NET_2_0_SUBSET) using System.Threading.Tasks; #endif @@ -20,7 +20,7 @@ public static void ForEach_Sequential(IEnumerable source, Action body) } public static void ForEach( IEnumerable source, Action body ) { -#if G3_USING_UNITY +#if G3_USING_UNITY && (NET_2_0 || NET_2_0_SUBSET) for_each(source, body); #else Parallel.ForEach(source, body); From 50ad627951b070f0b794043d605250ba0d5cbdd4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 9 Apr 2018 16:40:21 -0400 Subject: [PATCH 076/225] triangle strip fn --- mesh/MeshEditor.cs | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index d090e189..9107f8b3 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -24,6 +24,42 @@ public MeshEditor(DMesh3 mesh) + public virtual int[] AddTriangleStrip(IList frames, IList spans, int group_id = -1) + { + int N = frames.Count; + if (N != spans.Count) + throw new Exception("MeshEditor.AddTriangleStrip: spans list is not the same size!"); + int[] new_tris = new int[2*(N-1)]; + + int prev_a = -1, prev_b = -1; + int i = 0, ti = 0; + for (i = 0; i < N; ++i) { + Frame3f f = frames[i]; + Interval1d span = spans[i]; + + Vector3d va = f.Origin + (float)span.a * f.Y; + Vector3d vb = f.Origin + (float)span.b * f.Y; + + // [TODO] could compute normals here... + + int a = Mesh.AppendVertex(va); + int b = Mesh.AppendVertex(vb); + + if ( prev_a != -1 ) { + new_tris[ti++] = Mesh.AppendTriangle(prev_a, b, prev_b); + new_tris[ti++] = Mesh.AppendTriangle(prev_a, a, b); + + } + prev_a = a; prev_b = b; + } + + return new_tris; + } + + + + + public virtual int[] AddTriangleFan_OrderedVertexLoop(int center, int[] vertex_loop, int group_id = -1) { int N = vertex_loop.Length; @@ -49,7 +85,7 @@ public virtual int[] AddTriangleFan_OrderedVertexLoop(int center, int[] vertex_l // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, i) == false) - throw new Exception("MeshConstructor.AddTriangleFan_OrderedVertexLoop: failed to add fan, and also falied to back out changes."); + throw new Exception("MeshEditor.AddTriangleFan_OrderedVertexLoop: failed to add fan, and also falied to back out changes."); } return null; } @@ -86,7 +122,7 @@ public virtual int[] AddTriangleFan_OrderedEdgeLoop(int center, int[] edge_loop, // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, i-1) == false) - throw new Exception("MeshConstructor.AddTriangleFan_OrderedEdgeLoop: failed to add fan, and also failed to back out changes."); + throw new Exception("MeshEditor.AddTriangleFan_OrderedEdgeLoop: failed to add fan, and also failed to back out changes."); } return null; } @@ -134,7 +170,7 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, 2*(i-1)) == false) - throw new Exception("MeshConstructor.StitchLoop: failed to add all triangles, and also failed to back out changes."); + throw new Exception("MeshEditor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } return null; } @@ -195,7 +231,7 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, 2 * (i - 1)) == false) - throw new Exception("MeshConstructor.StitchLoop: failed to add all triangles, and also failed to back out changes."); + throw new Exception("MeshEditor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } return null; } @@ -246,7 +282,7 @@ public virtual int[] StitchSpan(int[] vspan1, int[] vspan2, int group_id = -1) // remove what we added so far if (i > 0) { if (remove_triangles(new_tris, 2*(i-1)) == false) - throw new Exception("MeshConstructor.StitchLoop: failed to add all triangles, and also failed to back out changes."); + throw new Exception("MeshEditor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } return null; } From f396244968a1a6efb9bf3b6152112173729527ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Drie=C3=ABnhuizen?= Date: Mon, 5 Mar 2018 16:43:24 +0100 Subject: [PATCH 077/225] TubeGenerator weighed UV mapping --- mesh_generators/GenCylGenerators.cs | 46 +++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/mesh_generators/GenCylGenerators.cs b/mesh_generators/GenCylGenerators.cs index 39d5cd83..dbb1fca1 100644 --- a/mesh_generators/GenCylGenerators.cs +++ b/mesh_generators/GenCylGenerators.cs @@ -40,6 +40,7 @@ public class TubeGenerator : MeshGenerator public int startCapCenterIndex = -1; public int endCapCenterIndex = -1; + public bool WeighedUV = false; public TubeGenerator() { @@ -79,11 +80,15 @@ override public MeshGenerator Generate() triangles = new IndexArray3i(nSpanTris + nCapTris); Frame3f fCur = new Frame3f(Frame); + Frame3f fNext = new Frame3f(Frame); Vector3d dv = CurveUtils.GetTangent(Vertices, 0); ; fCur.Origin = (Vector3f)Vertices[0]; fCur.AlignAxis(2, (Vector3f)dv); Frame3f fStart = new Frame3f(fCur); + float pathLength = WeighedUV ? (float)CurveUtils.ArcLength(Vertices) : 0; + float running_uv_along = 0; + // generate tube for (int ri = 0; ri < nRings; ++ri) { @@ -91,8 +96,6 @@ override public MeshGenerator Generate() if (ri != 0) { Vector3d tan = CurveUtils.GetTangent(Vertices, ri); fCur.Origin = (Vector3f)Vertices[ri]; - if (ri == 11) - dv = tan; fCur.AlignAxis(2, (Vector3f)tan); } @@ -100,17 +103,48 @@ override public MeshGenerator Generate() // generate vertices int nStartR = ri * nRingSize; - for (int j = 0; j < nRingSize; ++j) { - float uv_around = (float)j / (float)(nRings); + double circumference = 0; + + if (WeighedUV) { + // calculate ring circumference + for (int j = 0; j < nRingSize; ++j) { + Vector2d pv = Polygon.Vertices[j % Slices]; + Vector2d pvNext = Polygon.Vertices[(j + 1) % Slices]; + circumference += pv.Distance(pvNext); + } + } + + float running_uv_around = 0; + + for (int j = 0; j < nRingSize; ++j) { int k = nStartR + j; Vector2d pv = Polygon.Vertices[j % Slices]; + Vector2d pvNext = Polygon.Vertices[(j + 1) % Slices]; Vector3d v = fCur.FromPlaneUV((Vector2f)pv, 2); vertices[k] = v; - uv[k] = new Vector2f(uv_along, uv_around); + + if (WeighedUV) { + uv[k] = new Vector2f(running_uv_along, running_uv_around); + running_uv_around += (float)(pv.Distance(pvNext) / circumference); + } else { + float uv_around = (float)j / (float)(nRingSize); + uv[k] = new Vector2f(uv_along, uv_around); + } + Vector3f n = (Vector3f)(v - fCur.Origin).Normalized; normals[k] = n; } + + if (WeighedUV) { + int riNext = (ri + 1) % nRings; + Vector3d tanNext = CurveUtils.GetTangent(Vertices, riNext); + fNext.Origin = (Vector3f)Vertices[riNext]; + fNext.AlignAxis(2, (Vector3f)tanNext); + + float d = fCur.Origin.Distance(fNext.Origin); + running_uv_along += d / pathLength; + } } @@ -177,4 +211,4 @@ override public MeshGenerator Generate() return this; } } -} +} \ No newline at end of file From a0c5cee3f0144735560e475df63b9500e02213c8 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 11 Apr 2018 00:20:39 -0400 Subject: [PATCH 078/225] utility fn, added some files to csproj --- curve/CurveUtils.cs | 37 +++++++++++++++++++++++++------------ curve/PolyLine2d.cs | 6 ++++++ curve/Polygon2d.cs | 6 ++++++ geometry3Sharp.csproj | 3 +++ 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs index 320ceb14..87897553 100644 --- a/curve/CurveUtils.cs +++ b/curve/CurveUtils.cs @@ -8,27 +8,40 @@ namespace g3 public class CurveUtils { - public static Vector3d GetTangent(List vertices, int i) + public static Vector3d GetTangent(List vertices, int i, bool bLoop = false) { - if (i == 0) - return (vertices[1] - vertices[0]).Normalized; - else if (i == vertices.Count - 1) - return (vertices[vertices.Count - 1] - vertices[vertices.Count - 2]).Normalized; - else - return (vertices[i + 1] - vertices[i - 1]).Normalized; + if (bLoop) { + int NV = vertices.Count; + if (i == 0) + return (vertices[1] - vertices[NV-1]).Normalized; + else + return (vertices[(i+1)%NV] - vertices[i-1]).Normalized; + } else { + if (i == 0) + return (vertices[1] - vertices[0]).Normalized; + else if (i == vertices.Count - 1) + return (vertices[vertices.Count - 1] - vertices[vertices.Count - 2]).Normalized; + else + return (vertices[i + 1] - vertices[i - 1]).Normalized; + } } - public static double ArcLength(List vertices) { + public static double ArcLength(List vertices, bool bLoop = false) { double sum = 0; - for (int i = 1; i < vertices.Count; ++i) - sum += (vertices[i] - vertices[i - 1]).Length; + int NV = vertices.Count; + for (int i = 1; i < NV; ++i) + sum += vertices[i].Distance(vertices[i-1]); + if (bLoop) + sum += vertices[NV-1].Distance(vertices[0]); return sum; } - public static double ArcLength(Vector3d[] vertices) { + public static double ArcLength(Vector3d[] vertices, bool bLoop = false) { double sum = 0; for (int i = 1; i < vertices.Length ; ++i) - sum += (vertices[i] - vertices[i - 1]).Length; + sum += vertices[i].Distance(vertices[i-1]); + if (bLoop) + sum += vertices[vertices.Length-1].Distance(vertices[0]); return sum; } public static double ArcLength(IEnumerable vertices) { diff --git a/curve/PolyLine2d.cs b/curve/PolyLine2d.cs index 5364974c..5ff1aac5 100644 --- a/curve/PolyLine2d.cs +++ b/curve/PolyLine2d.cs @@ -34,6 +34,12 @@ public PolyLine2d(IList copy) Timestamp = 0; } + public PolyLine2d(IEnumerable copy) + { + vertices = new List(copy); + Timestamp = 0; + } + public PolyLine2d(Vector2d[] v) { vertices = new List(v); diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 0ae1bca6..2056bc90 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -29,6 +29,12 @@ public Polygon2d(IList copy) Timestamp = 0; } + public Polygon2d(IEnumerable copy) + { + vertices = new List(copy); + Timestamp = 0; + } + public Polygon2d(Vector2d[] v) { vertices = new List(v); diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 353f5d95..93aa12bf 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -144,6 +144,7 @@ + @@ -181,9 +182,11 @@ + + From accb164fa789a91ece6dbf66e2eceb026adb5778 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 11 Apr 2018 00:23:01 -0400 Subject: [PATCH 079/225] improvements --- mesh_generators/GenCylGenerators.cs | 96 ++++++++++++----------------- 1 file changed, 41 insertions(+), 55 deletions(-) diff --git a/mesh_generators/GenCylGenerators.cs b/mesh_generators/GenCylGenerators.cs index dbb1fca1..230d5753 100644 --- a/mesh_generators/GenCylGenerators.cs +++ b/mesh_generators/GenCylGenerators.cs @@ -12,7 +12,8 @@ namespace g3 /// can set using CapCenter). If Polygon is non-convex, this will have foldovers. /// In that case, you have to triangulate and append it yourself. /// - /// If your profile curve does not contain the origin, use CapCenter. + /// If your profile curve does not contain the polygon bbox center, + /// set OverrideCapCenter=true and set CapCenter to a suitable center point. /// /// The output normals are currently set to those for a circular profile. /// Call MeshNormals.QuickCompute() on the output DMesh to estimate proper @@ -27,6 +28,7 @@ public class TubeGenerator : MeshGenerator public bool Capped = true; // center of endcap triangle fan, relative to Polygon + public bool OverrideCapCenter = false; public Vector2d CapCenter = Vector2d.Zero; public bool ClosedLoop = false; @@ -40,8 +42,6 @@ public class TubeGenerator : MeshGenerator public int startCapCenterIndex = -1; public int endCapCenterIndex = -1; - public bool WeighedUV = false; - public TubeGenerator() { } @@ -55,7 +55,15 @@ public TubeGenerator(Polygon2d tubePath, Frame3f pathPlane, Polygon2d tubeShape, ClosedLoop = true; Capped = false; } - + public TubeGenerator(PolyLine2d tubePath, Frame3f pathPlane, Polygon2d tubeShape, int nPlaneNormal = 2) + { + Vertices = new List(); + foreach (Vector2d v in tubePath.Vertices) + Vertices.Add(pathPlane.FromPlaneUV((Vector2f)v, nPlaneNormal)); + Polygon = new Polygon2d(tubeShape); + ClosedLoop = false; + Capped = true; + } override public MeshGenerator Generate() @@ -63,8 +71,9 @@ override public MeshGenerator Generate() if (Polygon == null) Polygon = Polygon2d.MakeCircle(1.0f, 8); + int NV = Vertices.Count; int Slices = Polygon.VertexCount; - int nRings = Vertices.Count; + int nRings = (ClosedLoop && NoSharedVertices) ? NV + 1 : NV; int nRingSize = (NoSharedVertices) ? Slices + 1 : Slices; int nCapVertices = (NoSharedVertices) ? Slices + 1 : 1; if (Capped == false || ClosedLoop == true) @@ -74,49 +83,34 @@ override public MeshGenerator Generate() uv = new VectorArray2f(vertices.Count); normals = new VectorArray3f(vertices.Count); - int quad_strips = ClosedLoop ? (nRings) : (nRings-1); + int quad_strips = (ClosedLoop) ? NV : NV-1; int nSpanTris = quad_strips * (2 * Slices); int nCapTris = (Capped && ClosedLoop == false) ? 2 * Slices : 0; triangles = new IndexArray3i(nSpanTris + nCapTris); Frame3f fCur = new Frame3f(Frame); - Frame3f fNext = new Frame3f(Frame); - Vector3d dv = CurveUtils.GetTangent(Vertices, 0); ; + Vector3d dv = CurveUtils.GetTangent(Vertices, 0, ClosedLoop); fCur.Origin = (Vector3f)Vertices[0]; fCur.AlignAxis(2, (Vector3f)dv); Frame3f fStart = new Frame3f(fCur); - float pathLength = WeighedUV ? (float)CurveUtils.ArcLength(Vertices) : 0; - float running_uv_along = 0; + double circumference = Polygon.ArcLength; + double pathLength = CurveUtils.ArcLength(Vertices, ClosedLoop); + double accum_path_u = 0; // generate tube for (int ri = 0; ri < nRings; ++ri) { + int vi = ri % NV; // propagate frame - if (ri != 0) { - Vector3d tan = CurveUtils.GetTangent(Vertices, ri); - fCur.Origin = (Vector3f)Vertices[ri]; - fCur.AlignAxis(2, (Vector3f)tan); - } - - float uv_along = (float)ri / (float)(nRings - 1); + Vector3d tangent = CurveUtils.GetTangent(Vertices, vi, ClosedLoop); + fCur.Origin = (Vector3f)Vertices[vi]; + fCur.AlignAxis(2, (Vector3f)tangent); // generate vertices int nStartR = ri * nRingSize; - double circumference = 0; - - if (WeighedUV) { - // calculate ring circumference - for (int j = 0; j < nRingSize; ++j) { - Vector2d pv = Polygon.Vertices[j % Slices]; - Vector2d pvNext = Polygon.Vertices[(j + 1) % Slices]; - circumference += pv.Distance(pvNext); - } - } - - float running_uv_around = 0; - + double accum_ring_v = 0; for (int j = 0; j < nRingSize; ++j) { int k = nStartR + j; Vector2d pv = Polygon.Vertices[j % Slices]; @@ -124,59 +118,50 @@ override public MeshGenerator Generate() Vector3d v = fCur.FromPlaneUV((Vector2f)pv, 2); vertices[k] = v; - if (WeighedUV) { - uv[k] = new Vector2f(running_uv_along, running_uv_around); - running_uv_around += (float)(pv.Distance(pvNext) / circumference); - } else { - float uv_around = (float)j / (float)(nRingSize); - uv[k] = new Vector2f(uv_along, uv_around); - } + uv[k] = new Vector2f(accum_path_u, accum_ring_v); + accum_ring_v += (pv.Distance(pvNext) / circumference); Vector3f n = (Vector3f)(v - fCur.Origin).Normalized; normals[k] = n; } - if (WeighedUV) { - int riNext = (ri + 1) % nRings; - Vector3d tanNext = CurveUtils.GetTangent(Vertices, riNext); - fNext.Origin = (Vector3f)Vertices[riNext]; - fNext.AlignAxis(2, (Vector3f)tanNext); - - float d = fCur.Origin.Distance(fNext.Origin); - running_uv_along += d / pathLength; - } + int viNext = (ri + 1) % NV; + double d = Vertices[vi].Distance(Vertices[viNext]); + accum_path_u += d / pathLength; } // generate triangles int ti = 0; - int nStop = (ClosedLoop) ? nRings : (nRings - 1); + int nStop = (ClosedLoop && NoSharedVertices == false) ? nRings : (nRings - 1); for (int ri = 0; ri < nStop; ++ri) { int r0 = ri * nRingSize; int r1 = r0 + nRingSize; - if (ClosedLoop && ri == nStop - 1) + if (ClosedLoop && ri == nStop - 1 && NoSharedVertices == false) r1 = 0; for (int k = 0; k < nRingSize - 1; ++k) { triangles.Set(ti++, r0 + k, r0 + k + 1, r1 + k + 1, Clockwise); triangles.Set(ti++, r0 + k, r1 + k + 1, r1 + k, Clockwise); } - if (NoSharedVertices == false) { // close disc if we went all the way - triangles.Set(ti++, r1 - 1, r0, r1, Clockwise); - triangles.Set(ti++, r1 - 1, r1, r1 + nRingSize - 1, Clockwise); + if (NoSharedVertices == false) { // last quad if we aren't sharing vertices + int M = nRingSize-1; + triangles.Set(ti++, r0 + M, r0, r1, Clockwise); + triangles.Set(ti++, r0 + M, r1, r1 + M, Clockwise); } } if (Capped && ClosedLoop == false) { + Vector2d c = (OverrideCapCenter) ? CapCenter : Polygon.Bounds.Center; // add endcap verts int nBottomC = nRings * nRingSize; - vertices[nBottomC] = fStart.FromPlaneUV((Vector2f)CapCenter,2); + vertices[nBottomC] = fStart.FromPlaneUV((Vector2f)c,2); uv[nBottomC] = new Vector2f(0.5f, 0.5f); normals[nBottomC] = -fStart.Z; startCapCenterIndex = nBottomC; int nTopC = nBottomC + 1; - vertices[nTopC] = fCur.FromPlaneUV((Vector2f)CapCenter, 2); + vertices[nTopC] = fCur.FromPlaneUV((Vector2f)c, 2); uv[nTopC] = new Vector2f(0.5f, 0.5f); normals[nTopC] = fCur.Z; endCapCenterIndex = nTopC; @@ -187,7 +172,8 @@ override public MeshGenerator Generate() int nStartB = nTopC + 1; for (int k = 0; k < Slices; ++k) { vertices[nStartB + k] = vertices[nExistingB + k]; - uv[nStartB + k] = (Vector2f)Polygon.Vertices[k].Normalized; + Vector2d vuv = ((Polygon[k] - c).Normalized + Vector2d.One) * 0.5; + uv[nStartB + k] = (Vector2f)vuv; normals[nStartB + k] = normals[nBottomC]; } append_disc(Slices, nBottomC, nStartB, true, Clockwise, ref ti); @@ -197,7 +183,7 @@ override public MeshGenerator Generate() int nStartT = nStartB + Slices; for (int k = 0; k < Slices; ++k) { vertices[nStartT + k] = vertices[nExistingT + k]; - uv[nStartT + k] = (Vector2f)Polygon.Vertices[k].Normalized; + uv[nStartT + k] = uv[nStartB + k]; normals[nStartT + k] = normals[nTopC]; } append_disc(Slices, nTopC, nStartT, true, !Clockwise, ref ti); From cdaea2368edfa7556e7041994427fa2650901f4b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 11 Apr 2018 15:43:35 -0400 Subject: [PATCH 080/225] dcurve3 resampling that produces better 3D tube meshes, and tubegen constructor --- curve/DCurve3.cs | 102 +++++++++++++++++++++------- mesh_generators/GenCylGenerators.cs | 8 +++ 2 files changed, 87 insertions(+), 23 deletions(-) diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index 703ed7fa..d46c4349 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -5,6 +5,10 @@ namespace g3 { + /// + /// DCurve3 is a 3D polyline, either open or closed (via .Closed) + /// Despite the D prefix, it is *not* dynamic + /// public class DCurve3 : ISampledCurve3d { // [TODO] use dvector? or double-indirection indexing? @@ -152,45 +156,62 @@ public IEnumerable SegmentItr() } - public AxisAlignedBox3d GetBoundingBox() - { - // [RMS] problem w/ readonly because vector is a class... - //AxisAlignedBox3d box = AxisAlignedBox3d.Empty; - AxisAlignedBox3d box = new AxisAlignedBox3d(false); + public AxisAlignedBox3d GetBoundingBox() { + AxisAlignedBox3d box = AxisAlignedBox3d.Empty; foreach (Vector3d v in vertices) box.Contain(v); return box; } public double ArcLength { - get { - double dLen = 0; - for (int i = 1; i < vertices.Count; ++i) - dLen += (vertices[i] - vertices[i - 1]).Length; - return dLen; - } + get { return CurveUtils.ArcLength(vertices, Closed); } } - public Vector3d Tangent(int i) - { - if (i == 0) - return (vertices[1] - vertices[0]).Normalized; - else if (i == vertices.Count - 1) - return (vertices.Last() - vertices[vertices.Count - 2]).Normalized; - else - return (vertices[i + 1] - vertices[i - 1]).Normalized; + public Vector3d Tangent(int i) { + return CurveUtils.GetTangent(vertices, i, Closed); } public Vector3d Centroid(int i) { - if (i == 0 || i == vertices.Count - 1) - return vertices[i]; - else - return 0.5 * (vertices[i + 1] + vertices[i - 1]); + if (Closed) { + int NV = vertices.Count; + if (i == 0) + return 0.5 * (vertices[1] + vertices[NV - 1]); + else + return 0.5 * (vertices[(i+1)%NV] + vertices[i-1]); + } else { + if (i == 0 || i == vertices.Count - 1) + return vertices[i]; + else + return 0.5 * (vertices[i + 1] + vertices[i - 1]); + } } + /// + /// Compute opening angle at vertex i in degrees + /// + public double OpeningAngleDeg(int i) + { + int prev = i - 1, next = i + 1; + if ( Closed ) { + int NV = vertices.Count; + prev = (i == 0) ? NV - 1 : prev; + next = next % NV; + } else { + if (i == 0 || i == vertices.Count - 1) + return 180; + } + Vector3d e1 = (vertices[prev] - vertices[i]); + Vector3d e2 = (vertices[next] - vertices[i]); + e1.Normalize(); e2.Normalize(); + return Vector3d.AngleD(e1, e2); + } + + /// + /// Find nearest vertex to point p + /// public int NearestVertex(Vector3d p) { double nearSqr = double.MaxValue; @@ -207,6 +228,9 @@ public int NearestVertex(Vector3d p) } + /// + /// find squared distance from p to nearest segment on polyline + /// public double DistanceSquared(Vector3d p, out int iNearSeg, out double fNearSegT) { iNearSeg = -1; @@ -238,5 +262,37 @@ public double DistanceSquared(Vector3d p) { return DistanceSquared(p, out iseg, out segt); } + + + /// + /// Resample curve so that: + /// - if opening angle at vertex is > sharp_thresh, we emit two more vertices at +/- corner_t, where the t is used in prev/next lerps + /// - if opening angle is > flat_thresh, we skip the vertex entirely (simplification) + /// This is mainly useful to get nicer polylines to use as the basis for (eg) creating 3D tubes, rendering, etc + /// + /// [TODO] skip tiny segments? + /// + public DCurve3 ResampleSharpTurns(double sharp_thresh = 90, double flat_thresh = 189, double corner_t = 0.01) + { + int NV = vertices.Count; + DCurve3 resampled = new DCurve3() { Closed = this.Closed }; + double prev_t = 1.0 - corner_t; + for (int k = 0; k < NV; ++k) { + double open_angle = Math.Abs(OpeningAngleDeg(k)); + if (open_angle > flat_thresh && k > 0) { + // ignore skip this vertex + } else if (open_angle > sharp_thresh) { + resampled.AppendVertex(vertices[k]); + } else { + Vector3d n = vertices[(k + 1) % NV]; + Vector3d p = vertices[k == 0 ? NV - 1 : k - 1]; + resampled.AppendVertex(Vector3d.Lerp(p, vertices[k], prev_t)); + resampled.AppendVertex(vertices[k]); + resampled.AppendVertex(Vector3d.Lerp(vertices[k], n, corner_t)); + } + } + return resampled; + } + } } diff --git a/mesh_generators/GenCylGenerators.cs b/mesh_generators/GenCylGenerators.cs index 230d5753..49c5996a 100644 --- a/mesh_generators/GenCylGenerators.cs +++ b/mesh_generators/GenCylGenerators.cs @@ -64,6 +64,14 @@ public TubeGenerator(PolyLine2d tubePath, Frame3f pathPlane, Polygon2d tubeShape ClosedLoop = false; Capped = true; } + public TubeGenerator(DCurve3 tubePath, Polygon2d tubeShape) + { + Vertices = new List(tubePath.Vertices); + Polygon = new Polygon2d(tubeShape); + ClosedLoop = tubePath.Closed; + Capped = ! ClosedLoop; + } + override public MeshGenerator Generate() From 575181b85d366665e85d51fcba24bf8caa7e7fd0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 11 Apr 2018 16:47:58 -0400 Subject: [PATCH 081/225] bugfixes --- mesh/DMesh3Changes.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index 58cbdf86..694a78b6 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -103,7 +103,9 @@ public void Apply(DMesh3 mesh) if (result != MeshResult.Ok) throw new Exception("RemoveTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); } - OnApplyF(RemovedV, RemovedT); + + if ( OnApplyF != null ) + OnApplyF(RemovedV, RemovedT); } @@ -140,7 +142,8 @@ public void Revert(DMesh3 mesh) mesh.EndUnsafeTrianglesInsert(); } - OnRevertF(RemovedV, RemovedT); + if (OnRevertF != null) + OnRevertF(RemovedV, RemovedT); } @@ -269,7 +272,8 @@ public void Apply(DMesh3 mesh) mesh.EndUnsafeTrianglesInsert(); } - OnApplyF(AddedV, AddedT); + if (OnApplyF != null) + OnApplyF(AddedV, AddedT); } @@ -283,7 +287,8 @@ public void Revert(DMesh3 mesh) throw new Exception("AddTrianglesMeshChange.Apply: error in RemoveTriangle(" + tid.ToString() + "): " + result.ToString()); } - OnRevertF(AddedV, AddedT); + if ( OnRevertF != null ) + OnRevertF(AddedV, AddedT); } From 8c7ca5307fa5fb273f71a37a7b5e070109718378 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:14:38 -0400 Subject: [PATCH 082/225] utility bits --- math/Matrix2d.cs | 8 ++++++++ mesh/MeshEditor.cs | 12 ++++++++++++ mesh_selection/MeshEdgeSelection.cs | 14 ++++++++++++++ mesh_selection/MeshVertexSelection.cs | 10 ++++++++++ spatial/BasicProjectionTargets.cs | 14 ++++++++++++++ 5 files changed, 58 insertions(+) diff --git a/math/Matrix2d.cs b/math/Matrix2d.cs index e35a6426..76c9c0ca 100644 --- a/math/Matrix2d.cs +++ b/math/Matrix2d.cs @@ -112,6 +112,14 @@ public double ExtractAngle () { } + public Vector2d Row(int i) { + return (i == 0) ? new Vector2d(m00, m01) : new Vector2d(m10, m11); + } + public Vector2d Column(int i) { + return (i == 0) ? new Vector2d(m00, m10) : new Vector2d(m01, m11); + } + + public void Orthonormalize () { // Algorithm uses Gram-Schmidt orthogonalization. If 'this' matrix is diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 9107f8b3..3bdc43f8 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -763,6 +763,18 @@ public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) MeshEditor editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos), size); } + public static void AppendBox(DMesh3 mesh, Vector3d pos, Vector3d normal, float size) + { + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(new Frame3f(pos, normal), size); + } + public static void AppendLine(DMesh3 mesh, Segment3d seg, float size) + { + Frame3f f = new Frame3f(seg.Center); + f.AlignAxis(2, (Vector3f)seg.Direction); + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(f, new Vector3f(size, size, seg.Extent)); + } diff --git a/mesh_selection/MeshEdgeSelection.cs b/mesh_selection/MeshEdgeSelection.cs index 10bdde83..958826e9 100644 --- a/mesh_selection/MeshEdgeSelection.cs +++ b/mesh_selection/MeshEdgeSelection.cs @@ -169,6 +169,20 @@ public void SelectTriangleEdges(IEnumerable triangles) } + public void SelectBoundaryTriEdges(MeshFaceSelection triangles) + { + foreach ( int tid in triangles ) { + Index3i te = Mesh.GetTriEdges(tid); + for ( int j = 0; j < 3; ++j ) { + Index2i et = Mesh.GetEdgeT(te[j]); + int other_tid = (et.a == tid) ? et.b : et.a; + if (triangles.IsSelected(other_tid) == false) + add(te[j]); + } + } + } + + public void Deselect(int tid) { remove(tid); } diff --git a/mesh_selection/MeshVertexSelection.cs b/mesh_selection/MeshVertexSelection.cs index 66bbbc5b..45bf1178 100644 --- a/mesh_selection/MeshVertexSelection.cs +++ b/mesh_selection/MeshVertexSelection.cs @@ -146,6 +146,16 @@ public void Deselect(IEnumerable vertices) { foreach ( int vid in vertices ) remove(vid); } + public void DeselectEdge(int eid) { + Index2i ev = Mesh.GetEdgeV(eid); + remove(ev.a); remove(ev.b); + } + public void DeselectEdges(IEnumerable edges) { + foreach ( int eid in edges ) { + Index2i ev = Mesh.GetEdgeV(eid); + remove(ev.a); remove(ev.b); + } + } public int[] ToArray() { diff --git a/spatial/BasicProjectionTargets.cs b/spatial/BasicProjectionTargets.cs index c8353ee8..a5bb6168 100644 --- a/spatial/BasicProjectionTargets.cs +++ b/spatial/BasicProjectionTargets.cs @@ -42,6 +42,20 @@ public static MeshProjectionTarget Auto(DMesh3 mesh, bool bForceCopy = true) else return new MeshProjectionTarget(mesh); } + + + /// + /// Automatically construct fastest projection target for region of mesh + /// + public static MeshProjectionTarget Auto(DMesh3 mesh, IEnumerable triangles, int nExpandRings = 5) + { + MeshFaceSelection targetRegion = new MeshFaceSelection(mesh); + targetRegion.Select(triangles); + targetRegion.ExpandToOneRingNeighbours(nExpandRings); + DSubmesh3 submesh = new DSubmesh3(mesh, targetRegion); + return new MeshProjectionTarget(submesh.SubMesh); + } + } From 3181ae0163454dc1838a9c8f7525cd5a79e1229b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:15:07 -0400 Subject: [PATCH 083/225] added spherical fibonacci point set implementation. Using in NormalHistogram (breaking api change) --- comp_geom/SphericalFibonacciPointSet.cs | 129 ++++++++++++++++++++++++ geometry3Sharp.csproj | 1 + spatial/NormalHistogram.cs | 85 +++++++++------- 3 files changed, 178 insertions(+), 37 deletions(-) create mode 100644 comp_geom/SphericalFibonacciPointSet.cs diff --git a/comp_geom/SphericalFibonacciPointSet.cs b/comp_geom/SphericalFibonacciPointSet.cs new file mode 100644 index 00000000..fe9e8015 --- /dev/null +++ b/comp_geom/SphericalFibonacciPointSet.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + + +namespace g3 +{ + /// + /// A Spherical Fibonacci Point Set is a set of points that are roughly evenly distributed on + /// a sphere. Basically the points lie on a spiral, see pdf below. + /// The i-th SF point of an N-point set can be calculated directly. + /// For a given (normalized) point P, finding the nearest SF point (ie mapping back to i) + /// can be done in constant time. + /// + /// math from http://lgdv.cs.fau.de/uploads/publications/spherical_fibonacci_mapping_opt.pdf + /// + public class SphericalFibonacciPointSet + { + public int N = 64; + + public SphericalFibonacciPointSet(int n = 64) { + N = n; + } + + + public int Count { get { return N; } } + + + /// + /// Compute i'th spherical point + /// + public Vector3d Point(int i) + { + Util.gDevAssert(i < N); + double div = (double)i / PHI; + double phi = MathUtil.TwoPI * (div - Math.Floor(div)); + double cos_phi = Math.Cos(phi), sin_phi = Math.Sin(phi); + + double z = 1.0 - (2.0 * (double)i + 1.0) / (double)N; + double theta = Math.Acos(z); + double sin_theta = Math.Sin(theta); + + return new Vector3d(cos_phi * sin_theta, sin_phi * sin_theta, z); + } + public Vector3d this[int i] { + get { return Point(i); } + } + + + /// + /// Find index of nearest point-set point for input arbitrary point + /// + public int NearestPoint(Vector3d p, bool bIsNormalized = false) + { + if (bIsNormalized) + return inverseSF(ref p); + p.Normalize(); + return inverseSF(ref p); + } + + + + + static readonly double PHI = (Math.Sqrt(5.0) + 1.0) / 2.0; + + double madfrac(double a, double b) + { + //#define madfrac(A,B) mad((A),(B),-floor((A)*(B))) + return a * b + -Math.Floor(a * b); + } + + /// + /// This computes mapping from p to i. Note that the code in the original PDF is HLSL shader code. + /// I have ported here to comparable C# functions. *However* the PDF also explains some assumptions + /// made about what certain operators return in different cases (particularly NaN handling). + /// I have not yet tested these cases to make sure C# behavior is the same (not sure when they happen). + /// + int inverseSF(ref Vector3d p) + { + double phi = Math.Min(Math.Atan2(p.y, p.x), Math.PI); + double cosTheta = p.z; + double k = Math.Max(2.0, Math.Floor( + Math.Log(N * Math.PI * Math.Sqrt(5.0) * (1.0 - cosTheta*cosTheta)) / Math.Log(PHI*PHI))); + double Fk = Math.Pow(PHI, k) / Math.Sqrt(5.0); + + //double F0 = round(Fk), F1 = round(Fk * PHI); + double F0 = Math.Round(Fk), F1 = Math.Round(Fk * PHI); + + Matrix2d B = new Matrix2d( + 2 * Math.PI * madfrac(F0 + 1, PHI - 1) - 2 * Math.PI * (PHI - 1), + 2 * Math.PI * madfrac(F1 + 1, PHI - 1) - 2 * Math.PI * (PHI - 1), + -2 * F0 / N, -2 * F1 / N); + Matrix2d invB = B.Inverse(); + + //Vector2d c = floor(mul(invB, double2(phi, cosTheta - (1 - 1.0/N)))); + Vector2d c = new Vector2d(phi, cosTheta - (1 - 1.0 / N)); + c = invB * c; + c.x = Math.Floor(c.x); c.y = Math.Floor(c.y); + + double d = double.PositiveInfinity, j = 0; + for (uint s = 0; s < 4; ++s) { + Vector2d cosTheta_second = new Vector2d(s%2, s/2) + c; + cosTheta = B.Row(1).Dot(cosTheta_second) + (1-1.0/N); + cosTheta = MathUtil.Clamp(cosTheta, -1.0, +1.0)*2.0 - cosTheta; + double i = Math.Floor(N*0.5 - cosTheta*N*0.5); + phi = 2.0 * Math.PI * madfrac(i, PHI - 1); + cosTheta = 1.0 - (2.0 * i + 1.0) * (1.0 / N); // rcp(n); + double sinTheta = Math.Sqrt(1.0 - cosTheta * cosTheta); + Vector3d q = new Vector3d( + Math.Cos(phi) * sinTheta, + Math.Sin(phi) * sinTheta, + cosTheta); + double squaredDistance = Vector3d.Dot(q - p, q - p); + if (squaredDistance < d) { + d = squaredDistance; + j = i; + } + } + + // [TODO] should we be clamping this?? + return (int)j; + } + + + + + } +} diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 93aa12bf..eaf4a697 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -53,6 +53,7 @@ + diff --git a/spatial/NormalHistogram.cs b/spatial/NormalHistogram.cs index 3c0d3826..77ab6cf0 100644 --- a/spatial/NormalHistogram.cs +++ b/spatial/NormalHistogram.cs @@ -6,64 +6,75 @@ namespace g3 { /// - /// Construct "histogram" of normals of mesh. Basically each normal is scaled up - /// and then rounded to int. This is not a great strategy, but it works for - /// finding planes/etc. - /// - /// [TODO] variant that bins normals based on semi-regular mesh of sphere + /// Construct spherical histogram of normals of mesh. + /// Binning is done using a Spherical Fibonacci point set. /// public class NormalHistogram { - public DMesh3 Mesh; + public int Bins = 1024; + public SphericalFibonacciPointSet Points; + public double[] Counts; - public int IntScale = 256; - public bool UseAreaWeighting = true; - public Dictionary Histogram; + public HashSet UsedBins; + public NormalHistogram(int bins, bool bTrackUsed = false) + { + Bins = bins; + Points = new SphericalFibonacciPointSet(bins); + Counts = new double[bins]; + if (bTrackUsed) + UsedBins = new HashSet(); + } - public NormalHistogram(DMesh3 mesh) + /// + /// legacy API + /// + public NormalHistogram(DMesh3 mesh, bool bWeightByArea = true, int bins = 1024) : this(bins) { - Mesh = mesh; - Histogram = new Dictionary(); - build(); + CountFaceNormals(mesh, bWeightByArea); } /// - /// return (rounded) normal associated w/ maximum weight/area + /// bin and count point, and optionally normalize /// - public Vector3d FindMaxNormal() + public void Count(Vector3d pt, double weight = 1.0, bool bIsNormalized = false) { + int bin = Points.NearestPoint(pt, bIsNormalized); + Counts[bin] += weight; + if (UsedBins != null) + UsedBins.Add(bin); + } + + /// + /// Count all input mesh face normals + /// + public void CountFaceNormals(DMesh3 mesh, bool bWeightByArea = true) { - Vector3i maxN = Vector3i.AxisY; double maxArea = 0; - foreach (var pair in Histogram) { - if (pair.Value > maxArea) { - maxArea = pair.Value; - maxN = pair.Key; + foreach (int tid in mesh.TriangleIndices()) { + if (bWeightByArea) { + Vector3d n, c; double area; + mesh.GetTriInfo(tid, out n, out area, out c); + Count(n, area, true); + } else { + Count(mesh.GetTriNormal(tid), 1.0, true); } } - Vector3d n = new Vector3d(maxN.x, maxN.y, maxN.z); - n.Normalize(); - return n; } - - - void build() + /// + /// return (quantized) normal associated w/ maximum weight/area + /// + public Vector3d FindMaxNormal() { - foreach (int tid in Mesh.TriangleIndices()) { - double w = (UseAreaWeighting) ? Mesh.GetTriArea(tid) : 1.0; - - Vector3d n = Mesh.GetTriNormal(tid); - - Vector3i up = new Vector3i((int)(n.x * IntScale), (int)(n.y * IntScale), (int)(n.z * IntScale)); - - if (Histogram.ContainsKey(up)) - Histogram[up] += w; - else - Histogram[up] = w; + int max_i = 0; + for ( int k = 1; k < Bins; ++k ) { + if (Counts[k] > Counts[max_i]) + max_i = k; } + return Points[max_i]; } + } } From 17c6a7a8292a3c7d025844211c90c674db244124 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:16:16 -0400 Subject: [PATCH 084/225] added option to enable/disable merging along the fill boundary. useful as workaround if fill simplification fails (then merge may be garbage) --- mesh_ops/PlanarHoleFiller.cs | 47 +++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/mesh_ops/PlanarHoleFiller.cs b/mesh_ops/PlanarHoleFiller.cs index 74d8db56..0d7f5c24 100644 --- a/mesh_ops/PlanarHoleFiller.cs +++ b/mesh_ops/PlanarHoleFiller.cs @@ -18,6 +18,12 @@ public class PlanarHoleFiller /// public double FillTargetEdgeLen = double.MaxValue; + /// + /// in some cases fill can succeed but we can't merge w/o creating holes. In + /// such cases it might be better to not merge at all... + /// + public bool MergeFillBoundary = true; + // these will be computed if you don't set them Vector3d PlaneX, PlaneY; @@ -185,28 +191,29 @@ public bool Fill() // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); - for ( int pi = 0; pi < polys.Count; ++pi ) { - if (polyVertices[pi] == null) - continue; - int[] fillLoopVerts = polyVertices[pi]; - int NV = fillLoopVerts.Length; - - PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; - int loopi = ElemToLoopMap[sourceElem]; - EdgeLoop sourceLoop = Loops[loopi].edgeLoop; - - if (sourceLoop.VertexCount != NV) { - failed_merges.Add(new Index2i(fi, pi)); - continue; - } + if (MergeFillBoundary) { + for (int pi = 0; pi < polys.Count; ++pi) { + if (polyVertices[pi] == null) + continue; + int[] fillLoopVerts = polyVertices[pi]; + int NV = fillLoopVerts.Length; + + PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; + int loopi = ElemToLoopMap[sourceElem]; + EdgeLoop sourceLoop = Loops[loopi].edgeLoop; + + if (sourceLoop.VertexCount != NV) { + failed_merges.Add(new Index2i(fi, pi)); + continue; + } - for ( int k = 0; k < NV; ++k ) { - Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); - Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); - if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) - mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; + for (int k = 0; k < NV; ++k) { + Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); + Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); + if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) + mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; + } } - } // append this fill to input mesh From 46502e4a90bff929cb45b8f30f07f23500a5e001 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:18:12 -0400 Subject: [PATCH 085/225] extended LaplacianMeshSmoother.RegionSmooth to optionally include additional boundary rings around input region, which increases continuity, and also optionally pin those exterior vertices in place (ie don't change mesh outside input ROI). Also handled some cases where constraints were possibly being replaced. --- mesh_ops/LaplacianMeshSmoother.cs | 59 +++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/mesh_ops/LaplacianMeshSmoother.cs b/mesh_ops/LaplacianMeshSmoother.cs index d3dadbea..0ef409d6 100644 --- a/mesh_ops/LaplacianMeshSmoother.cs +++ b/mesh_ops/LaplacianMeshSmoother.cs @@ -350,28 +350,52 @@ public bool SolveAndUpdateMesh() /// Apply LaplacianMeshSmoother to subset of mesh triangles. /// border of subset always has soft constraint with borderWeight, /// but is then snapped back to original vtx pos after solve. - /// nConstrainLoops inner loops are also soft-constrained, with weight falloff via square roots. + /// nConstrainLoops inner loops are also soft-constrained, with weight falloff via square roots (defines continuity) /// interiorWeight is soft constraint added to all vertices /// - public static void RegionSmooth(DMesh3 mesh, IEnumerable triangles, int nConstrainLoops, + public static void RegionSmooth(DMesh3 mesh, IEnumerable triangles, + int nConstrainLoops, + int nIncludeExteriorRings, + bool bPreserveExteriorRings, double borderWeight = 10.0, double interiorWeight = 0.0) { + HashSet fixedVerts = new HashSet(); + if ( nIncludeExteriorRings > 0 ) { + MeshFaceSelection expandTris = new MeshFaceSelection(mesh); + expandTris.Select(triangles); + if (bPreserveExteriorRings) { + MeshEdgeSelection bdryEdges = new MeshEdgeSelection(mesh); + bdryEdges.SelectBoundaryTriEdges(expandTris); + expandTris.ExpandToOneRingNeighbours(nIncludeExteriorRings); + MeshVertexSelection startVerts = new MeshVertexSelection(mesh); + startVerts.SelectTriangleVertices(triangles); + startVerts.DeselectEdges(bdryEdges); + MeshVertexSelection expandVerts = new MeshVertexSelection(mesh, expandTris); + foreach (int vid in expandVerts) { + if (startVerts.IsSelected(vid) == false) + fixedVerts.Add(vid); + } + } else { + expandTris.ExpandToOneRingNeighbours(nIncludeExteriorRings); + } + triangles = expandTris; + } + RegionOperator region = new RegionOperator(mesh, triangles); DSubmesh3 submesh = region.Region; DMesh3 smoothMesh = submesh.SubMesh; LaplacianMeshSmoother smoother = new LaplacianMeshSmoother(smoothMesh); - // soft constraint on all interior vertices, if requested - if (interiorWeight > 0) { - foreach (int vid in smoothMesh.VertexIndices()) - smoother.SetConstraint(vid, smoothMesh.GetVertex(vid), interiorWeight, false); - } + // map fixed verts to submesh + HashSet subFixedVerts = new HashSet(); + foreach (int base_vid in fixedVerts) + subFixedVerts.Add(submesh.MapVertexToSubmesh(base_vid)); - // now constrain borders + // constrain borders double w = borderWeight; - HashSet constrained = (region.Region.BaseBorderV.Count > 0) ? new HashSet() : null; - foreach (int base_vid in region.Region.BaseBorderV) { + HashSet constrained = (submesh.BaseBorderV.Count > 0) ? new HashSet() : null; + foreach (int base_vid in submesh.BaseBorderV) { int sub_vid = submesh.BaseToSubV[base_vid]; smoother.SetConstraint(sub_vid, smoothMesh.GetVertex(sub_vid), w, true); if (constrained != null) @@ -386,7 +410,8 @@ public static void RegionSmooth(DMesh3 mesh, IEnumerable triangles, int nCo foreach (int sub_vid in constrained) { foreach (int nbr_vid in smoothMesh.VtxVerticesItr(sub_vid)) { if (constrained.Contains(nbr_vid) == false) { - smoother.SetConstraint(nbr_vid, smoothMesh.GetVertex(nbr_vid), w, false); + if ( smoother.IsConstrained(nbr_vid) == false ) + smoother.SetConstraint(nbr_vid, smoothMesh.GetVertex(nbr_vid), w, subFixedVerts.Contains(nbr_vid)); next_layer.Add(nbr_vid); } } @@ -397,6 +422,18 @@ public static void RegionSmooth(DMesh3 mesh, IEnumerable triangles, int nCo } } + // soft constraint on all interior vertices, if requested + if (interiorWeight > 0) { + foreach (int vid in smoothMesh.VertexIndices()) { + if ( smoother.IsConstrained(vid) == false ) + smoother.SetConstraint(vid, smoothMesh.GetVertex(vid), interiorWeight, subFixedVerts.Contains(vid)); + } + } else if ( subFixedVerts.Count > 0 ) { + foreach (int vid in subFixedVerts) { + if (smoother.IsConstrained(vid) == false) + smoother.SetConstraint(vid, smoothMesh.GetVertex(vid), 0, true); + } + } smoother.SolveAndUpdateMesh(); region.BackPropropagateVertices(true); From ac7003b1ac46f293092ec3487a9e6c37cce986c1 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:40:27 -0400 Subject: [PATCH 086/225] refactored this class so that it is possible to get access to the MeshInsertPolygon if desired --- .../TriangulatedPolygonGenerator.cs | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/mesh_generators/TriangulatedPolygonGenerator.cs b/mesh_generators/TriangulatedPolygonGenerator.cs index 12e5b9ef..9123fe7b 100644 --- a/mesh_generators/TriangulatedPolygonGenerator.cs +++ b/mesh_generators/TriangulatedPolygonGenerator.cs @@ -13,7 +13,41 @@ public class TriangulatedPolygonGenerator : MeshGenerator public TrivialRectGenerator.UVModes UVMode = TrivialRectGenerator.UVModes.FullUVSquare; + override public MeshGenerator Generate() + { + MeshInsertPolygon insert; + DMesh3 base_mesh = ComputeResult(out insert); + + DMesh3 compact = new DMesh3(base_mesh, true); + + int NV = compact.VertexCount; + vertices = new VectorArray3d(NV); + uv = new VectorArray2f(NV); + normals = new VectorArray3f(NV); + for (int vi = 0; vi < NV; ++vi) { + vertices[vi] = compact.GetVertex(vi); + uv[vi] = compact.GetVertexUV(vi); + normals[vi] = FixedNormal; + } + + int NT = compact.TriangleCount; + triangles = new IndexArray3i(NT); + for (int ti = 0; ti < NT; ++ti) + triangles[ti] = compact.GetTriangle(ti); + + return this; + } + + + + + /// + /// Actually computes the insertion. In some cases we would like more info + /// coming back than we get by using Generate() api. Note that resulting + /// mesh is *not* compacted. + /// + public DMesh3 ComputeResult(out MeshInsertPolygon insertion) { AxisAlignedBox2d bounds = Polygon.Bounds; double padding = 0.1 * bounds.DiagonalLength; @@ -44,26 +78,16 @@ override public MeshGenerator Generate() MeshEditor editor = new MeshEditor(base_mesh); editor.RemoveTriangles((tid) => { return selected.IsSelected(tid) == false; }, true); - DMesh3 compact = new DMesh3(base_mesh, true); - - int NV = compact.VertexCount; - vertices = new VectorArray3d(NV); - uv = new VectorArray2f(NV); - normals = new VectorArray3f(NV); Vector3d shift3 = new Vector3d(shift.x, shift.y, 0); - for (int vi = 0; vi < NV; ++vi) { - vertices[vi] = compact.GetVertex(vi) + shift3; - uv[vi] = compact.GetVertexUV(vi); - normals[vi] = FixedNormal; - } - - int NT = compact.TriangleCount; - triangles = new IndexArray3i(NT); - for (int ti = 0; ti < NT; ++ti) - triangles[ti] = compact.GetTriangle(ti); + MeshTransforms.Translate(base_mesh, shift3); - return this; + insertion = insert; + return base_mesh; } + + + + } From cf1a33b1ab7305ded9c37d1af809c09d7d99e1a1 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 12 Apr 2018 15:42:50 -0400 Subject: [PATCH 087/225] Improved handling of poke-face degeneracy cases. using small barycentric-coord tolerance would easily allow through near-degenerate cases. Added checks against geometric tolerance. Possibly this is still an issue w/ very small triangles...maybe need to use geometric tolerance for every case? also some edges introduced by splits were not identified as on-cut because we weren't including zero-vertices in on-cut vtx set. --- mesh_ops/MeshInsertUVPolyCurve.cs | 102 ++++++++++++++++++------------ 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 720de2b6..bda3abba 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -10,6 +10,14 @@ namespace g3 /// Assumptions: /// - mesh vertex x/y coordinates are 2D coordinates we want to use. Replace PointF if this is not the case. /// - segments of Curve lie entirely within UV-triangles + /// + /// Limitations: + /// - currently not robust to near-parallel line segments that are within epsilon-band of the + /// input loop. In this case, we will include all such segments in the 'cut' set, but we + /// will probably not be able to find a connected path through them. + /// - not robust to degenerate geometry. Strongly recommend that you use Validate() and/or + /// preprocess the input mesh to remove degenerate faces/edges + /// /// public class MeshInsertUVPolyCurve { @@ -27,7 +35,10 @@ public class MeshInsertUVPolyCurve public bool EnableCutSpansAndLoops = true; // probably always makes sense to use this...maybe not for very small problems? - bool UseTriSpatial = true; + public bool UseTriSpatial = true; + + // points/edges within this distance are considered the same + public double SpatialEpsilon = MathUtil.ZeroTolerance; // Results @@ -182,7 +193,8 @@ void insert_corners() if (contain_tid != DMesh3.InvalidID ) { Index3i tv = Mesh.GetTriangle(contain_tid); Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); - int vid = insert_corner_from_bary(i, contain_tid, bary); + // SpatialEpsilon is our zero-tolerance, so merge if we are closer than that + int vid = insert_corner_from_bary(i, contain_tid, bary, 0.01, 100*SpatialEpsilon); if (vid > 0) { // this should be always happening.. CurveVertices[i] = vid; inserted = true; @@ -202,43 +214,51 @@ void insert_corners() // insert point at bary_coords inside tid. If point is at vtx, just use that vtx. // If it is on an edge, do an edge split. Otherwise poke face. - int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, double tol = MathUtil.ZeroTolerance) + int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, + double bary_tol = MathUtil.ZeroTolerance, double spatial_tol = MathUtil.ZeroTolerance) { Vector2d vInsert = Curve[iCorner]; Index3i tv = Mesh.GetTriangle(tid); // handle cases where corner is on a vertex - if (bary_coords.x > 1 - tol) - return tv.a; - else if (bary_coords.y > 1 - tol) - return tv.b; - else if ( bary_coords.z > 1 - tol ) - return tv.c; + int cornerv = -1; + if (bary_coords.x > 1 - bary_tol) + cornerv = tv.a; + else if (bary_coords.y > 1 - bary_tol) + cornerv = tv.b; + else if (bary_coords.z > 1 - bary_tol) + cornerv = tv.c; + if ( cornerv != -1 && PointF(cornerv).Distance(vInsert) < spatial_tol ) + return cornerv; // handle cases where corner is on an edge int split_edge = -1; - if (bary_coords.x < tol) + if (bary_coords.x < bary_tol) split_edge = 1; - else if (bary_coords.y < tol) + else if (bary_coords.y < bary_tol) split_edge = 2; - else if (bary_coords.z < tol) + else if (bary_coords.z < bary_tol) split_edge = 0; if (split_edge >= 0) { int eid = Mesh.GetTriEdge(tid, split_edge); - Index2i ev = Mesh.GetEdgeT(eid); - spatial_remove_triangles(ev.a, ev.b); + Index2i ev = Mesh.GetEdgeV(eid); + Segment2d seg = new Segment2d(PointF(ev.a), PointF(ev.b)); + if (seg.DistanceSquared(vInsert) < spatial_tol*spatial_tol) { + Index2i et = Mesh.GetEdgeT(eid); + spatial_remove_triangles(et.a, et.b); - DMesh3.EdgeSplitInfo split_info; - MeshResult splitResult = Mesh.SplitEdge(eid, out split_info); - if (splitResult != MeshResult.Ok) - throw new Exception("MeshInsertUVPolyCurve.insert_corner_from_bary: edge split failed in case sum==2 - " + splitResult.ToString()); - SetPointF(split_info.vNew, vInsert); + DMesh3.EdgeSplitInfo split_info; + MeshResult splitResult = Mesh.SplitEdge(eid, out split_info); + if (splitResult != MeshResult.Ok) + throw new Exception("MeshInsertUVPolyCurve.insert_corner_from_bary: edge split failed in case sum==2 - " + splitResult.ToString()); + SetPointF(split_info.vNew, vInsert); - spatial_add_triangles(ev.a, ev.b); - spatial_add_triangles(split_info.eNewT2, split_info.eNewT3); + spatial_add_triangles(et.a, et.b); + spatial_add_triangles(split_info.eNewT2, split_info.eNewT3); - return split_info.vNew; + return split_info.vNew; + } } spatial_remove_triangle(tid); @@ -295,7 +315,7 @@ public virtual bool Apply() // If these vertices are already connected by an edge, we can just continue. int existing_edge = Mesh.FindEdge(i0_vid, i1_vid); if ( existing_edge != DMesh3.InvalidID ) { - OnCutEdges.Add(existing_edge); + add_cut_edge(existing_edge); continue; } @@ -324,7 +344,7 @@ public virtual bool Apply() } else { Vector2d v2 = PointF(vid); // tolerance defines band in which we will consider values to be zero - signs[vid] = (sbyte)seg.WhichSide(v2, MathUtil.ZeroTolerance); + signs[vid] = (sbyte)seg.WhichSide(v2, SpatialEpsilon); } } else signs[vid] = sbyte.MaxValue; @@ -359,10 +379,10 @@ public virtual bool Apply() bool eva_in_segment = false; if ( eva_sign == 0 ) - eva_in_segment = Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + MathUtil.ZeroTolerance); + eva_in_segment = Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon); bool evb_in_segment = false; if (evb_sign == 0) - evb_in_segment = Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + MathUtil.ZeroTolerance); + evb_in_segment = Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon); // If one or both vertices are on-segment, we have special case. // If just one vertex is on the segment, we can skip this edge. @@ -370,9 +390,12 @@ public virtual bool Apply() if (eva_in_segment || evb_in_segment) { if (eva_in_segment && evb_in_segment) { ZeroEdges.Add(eid); - OnCutEdges.Add(eid); + add_cut_edge(eid); + NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); } else { - ZeroVertices.Add(eva_in_segment ? ev.a : ev.b); + int zvid = eva_in_segment ? ev.a : ev.b; + ZeroVertices.Add(zvid); + NewCutVertices.Add(zvid); } continue; } @@ -391,7 +414,8 @@ public virtual bool Apply() // [RMS] we should have already caught this above, so if it happens here it is probably spurious? // we should have caught this case above, but numerics are different so it might occur again ZeroEdges.Add(eid); - OnCutEdges.Add(eid); + NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); + add_cut_edge(eid); continue; } else if (intr.Type != IntersectionType.Point) { continue; // no intersection @@ -402,7 +426,7 @@ public virtual bool Apply() // is within epsilon of one end of the edge. This is a spurious t-intersection and we // can ignore it. Some other edge should exist that picks up this vertex as part of it. // [TODO] what about if this edge is degenerate? - bool x_in_segment = Math.Abs(edge_seg.Project(x)) < (edge_seg.Extent - MathUtil.ZeroTolerance); + bool x_in_segment = Math.Abs(edge_seg.Project(x)) < (edge_seg.Extent - SpatialEpsilon); if (! x_in_segment ) { continue; } @@ -431,25 +455,18 @@ public virtual bool Apply() // the polypath. We want to keep track of these edges so we can extract loop later. Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN); if (NewCutVertices.Contains(ecn.a) && NewCutVertices.Contains(ecn.b)) - OnCutEdges.Add(splitInfo.eNewCN); + add_cut_edge(splitInfo.eNewCN); // since we don't handle bdry edges this should never be false, but maybe we will handle bdry later... if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); Index2i edn = Mesh.GetEdgeV(splitInfo.eNewDN); if (NewCutVertices.Contains(edn.a) && NewCutVertices.Contains(edn.b)) - OnCutEdges.Add(splitInfo.eNewDN); + add_cut_edge(splitInfo.eNewDN); } } } - - //MeshEditor editor = new MeshEditor(Mesh); - //foreach (int eid in OnCutEdges) - // editor.AppendBox(new Frame3f(Mesh.GetEdgePoint(eid, 0.5)), 0.1f); - //Util.WriteDebugMesh(Mesh, string.Format("C:\\git\\geometry3SharpDemos\\geometry3Test\\test_output\\after_inserted.obj")); - - // extract the cut paths if (EnableCutSpansAndLoops) find_cut_paths(OnCutEdges); @@ -459,7 +476,10 @@ public virtual bool Apply() } // Apply() - + // useful to have all these calls centralized for debugging... + void add_cut_edge(int eid) { + OnCutEdges.Add(eid); + } @@ -609,7 +629,7 @@ static List walk_edge_span_forward(DMesh3 mesh, int start_edge, int start_p bool done = false; while (!done) { - // fink outgoing edge in set and connected to current pivot vtx + // find outgoing edge in set and connected to current pivot vtx int next_edge = -1; foreach (int nbr_edge in mesh.VtxEdgesItr(cur_pivot_v)) { if (EdgeSet.Contains(nbr_edge)) { From 7fa7a1ddbac8d2731933dcd8321604b6424193ee Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 13 Apr 2018 10:52:12 -0400 Subject: [PATCH 088/225] explicitly track original mesh vertices that had a polycurve vertex snapped to them --- mesh_ops/MeshInsertUVPolyCurve.cs | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index bda3abba..5bc23a60 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -146,7 +146,7 @@ void spatial_remove_triangles(int t0, int t1) { // (sequentially) find each triangle that path point lies in, and insert a vertex for // that point into mesh. - void insert_corners() + void insert_corners(HashSet MeshVertsOnCurve) { PrimalQuery2d query = new PrimalQuery2d(PointF); @@ -194,9 +194,12 @@ void insert_corners() Index3i tv = Mesh.GetTriangle(contain_tid); Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); // SpatialEpsilon is our zero-tolerance, so merge if we are closer than that - int vid = insert_corner_from_bary(i, contain_tid, bary, 0.01, 100*SpatialEpsilon); + bool is_existing_v; + int vid = insert_corner_from_bary(i, contain_tid, bary, 0.01, 100*SpatialEpsilon, out is_existing_v); if (vid > 0) { // this should be always happening.. CurveVertices[i] = vid; + if (is_existing_v) + MeshVertsOnCurve.Add(vid); inserted = true; } else { throw new Exception("MeshInsertUVPolyCurve.insert_corners: failed to insert vertex " + i.ToString()); @@ -215,8 +218,9 @@ void insert_corners() // insert point at bary_coords inside tid. If point is at vtx, just use that vtx. // If it is on an edge, do an edge split. Otherwise poke face. int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, - double bary_tol = MathUtil.ZeroTolerance, double spatial_tol = MathUtil.ZeroTolerance) + double bary_tol, double spatial_tol, out bool is_existing_v) { + is_existing_v = false; Vector2d vInsert = Curve[iCorner]; Index3i tv = Mesh.GetTriangle(tid); @@ -228,8 +232,10 @@ int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, cornerv = tv.b; else if (bary_coords.z > 1 - bary_tol) cornerv = tv.c; - if ( cornerv != -1 && PointF(cornerv).Distance(vInsert) < spatial_tol ) + if (cornerv != -1 && PointF(cornerv).Distance(vInsert) < spatial_tol) { + is_existing_v = true; return cornerv; + } // handle cases where corner is on an edge int split_edge = -1; @@ -284,7 +290,8 @@ int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, public virtual bool Apply() { - insert_corners(); + HashSet OnCurveVerts = new HashSet(); // original vertices that were epsilon-coincident w/ curve vertices + insert_corners(OnCurveVerts); // [RMS] not using this? //HashSet corner_v = new HashSet(CurveVertices); @@ -377,12 +384,15 @@ public virtual bool Apply() int eva_sign = signs[ev.a]; int evb_sign = signs[ev.b]; + // [RMS] should we be using larger epsilon here? If we don't track OnCurveVerts explicitly, we + // need to at least use same epsilon we passed to insert_corner_from_bary...do we still also + // need that to catch the edges we split in the poke? bool eva_in_segment = false; if ( eva_sign == 0 ) - eva_in_segment = Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon); + eva_in_segment = OnCurveVerts.Contains(ev.a) || Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon); bool evb_in_segment = false; if (evb_sign == 0) - evb_in_segment = Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon); + evb_in_segment = OnCurveVerts.Contains(ev.b) || Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon); // If one or both vertices are on-segment, we have special case. // If just one vertex is on the segment, we can skip this edge. From a26c01269456775292bec96991ca4be23bc0dde9 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 14 Apr 2018 11:35:45 -0400 Subject: [PATCH 089/225] utility functions --- math/IndexUtil.cs | 15 +++++++ math/MathUtil.cs | 13 ++++++ mesh/DMesh3.cs | 21 ++++++++++ mesh/DSubmesh3.cs | 11 +++++ mesh/MeshUtil.cs | 105 +++++++++++++++++++++++++++++++++++++++++++++- mesh/Reducer.cs | 6 +-- 6 files changed, 167 insertions(+), 4 deletions(-) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index 983d6fdd..8679afaa 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -129,6 +129,21 @@ public static int find_tri_other_vtx(int a, int b, DVector tri_array, int t return DMesh3.InvalidID; } + + /// + /// assuming a is in tri-verts, returns other two vertices, in correct order (or Index2i.Max if not found) + /// + public static Index2i find_tri_other_verts(int a, ref Index3i tri_verts) + { + if (tri_verts.a == a) + return new Index2i(tri_verts.b, tri_verts.c); + else if (tri_verts.b == a) + return new Index2i(tri_verts.c, tri_verts.a); + else if (tri_verts.c == a) + return new Index2i(tri_verts.a, tri_verts.b); + return Index2i.Max; + } + // find sequence [a,b] in tri_verts (mod3) then return the third **index**, or InvalidID if not found public static int find_tri_other_index(int a, int b, int[] tri_verts) { diff --git a/math/MathUtil.cs b/math/MathUtil.cs index 85afb68e..ef708a78 100644 --- a/math/MathUtil.cs +++ b/math/MathUtil.cs @@ -483,6 +483,19 @@ public static Vector3d FastNormalArea(ref Vector3d v1, ref Vector3d v2, ref Vect } + /// + /// aspect ratio of triangle + /// + public static double AspectRatio(ref Vector3d v1, ref Vector3d v2, ref Vector3d v3) + { + double a = v1.Distance(ref v2), b = v2.Distance(ref v3), c = v3.Distance(ref v1); + double s = (a + b + c) / 2.0; + return (a * b * c) / (8.0 * (s - a) * (s - b) * (s - c)); + } + public static double AspectRatio(Vector3d v1, Vector3d v2, Vector3d v3) { + return AspectRatio(ref v1, ref v2, ref v3); + } + //! fast cotangent between two normalized vectors //! cot = cos/sin, both of which can be computed from vector identities diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index a94bbc97..93c95c5c 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -832,6 +832,27 @@ public double GetTriSolidAngle(int tID, ref Vector3d p) + /// + /// compute internal angle at vertex i of triangle (where i is 0,1,2); + /// TODO can be more efficient here, probably... + /// + public double GetTriInternalAngleR(int tID, int i) + { + int ti = 3 * tID; + int ta = 3 * triangles[ti]; + Vector3d a = new Vector3d(vertices[ta], vertices[ta + 1], vertices[ta + 2]); + int tb = 3 * triangles[ti + 1]; + Vector3d b = new Vector3d(vertices[tb], vertices[tb + 1], vertices[tb + 2]); + int tc = 3 * triangles[ti + 2]; + Vector3d c = new Vector3d(vertices[tc], vertices[tc + 1], vertices[tc + 2]); + if ( i == 0 ) + return (b-a).Normalized.AngleR((c-a).Normalized); + else if ( i == 1 ) + return (a-b).Normalized.AngleR((c-b).Normalized); + else + return (a-c).Normalized.AngleR((b-c).Normalized); + } + public Index2i GetEdgeV(int eID) { diff --git a/mesh/DSubmesh3.cs b/mesh/DSubmesh3.cs index c6e4980a..e445bd41 100644 --- a/mesh/DSubmesh3.cs +++ b/mesh/DSubmesh3.cs @@ -66,6 +66,10 @@ public int MapVertexToBaseMesh(int sub_vID) { public Index2i MapVerticesToSubmesh(Index2i v) { return new Index2i(BaseToSubV[v.a], BaseToSubV[v.b]); } + public Index2i MapVerticesToBaseMesh(Index2i v) { + return new Index2i(MapVertexToBaseMesh(v.a), MapVertexToBaseMesh(v.b)); + } + public void MapVerticesToSubmesh(int[] vertices) { for (int i = 0; i < vertices.Length; ++i) @@ -85,6 +89,13 @@ public void MapEdgesToSubmesh(int[] edges) edges[i] = MapEdgeToSubmesh(edges[i]); } + public int MapEdgeToBaseMesh(int sub_eid) + { + Index2i sub_ev = SubMesh.GetEdgeV(sub_eid); + Index2i base_ev = MapVerticesToBaseMesh(sub_ev); + return BaseMesh.FindEdge(base_ev.a, base_ev.b); + } + public int MapTriangleToSubmesh(int base_tID) { diff --git a/mesh/MeshUtil.cs b/mesh/MeshUtil.cs index 15c1b1c9..c4dcfa87 100644 --- a/mesh/MeshUtil.cs +++ b/mesh/MeshUtil.cs @@ -54,7 +54,9 @@ public static void ScaleMesh(DMesh3 mesh, Frame3f f, Vector3f vScale) { - + /// + /// computes opening angle between the two triangles connected to edge + /// public static double OpeningAngleD(DMesh3 mesh, int eid) { Index2i et = mesh.GetEdgeT(eid); @@ -67,6 +69,22 @@ public static double OpeningAngleD(DMesh3 mesh, int eid) } + /// + /// computes sum of opening-angles in triangles around vid, minus 2pi. + /// This is zero on flat areas. + /// + public static double DiscreteGaussCurvature(DMesh3 mesh, int vid) + { + double angle_sum = 0; + foreach (int tid in mesh.VtxTrianglesItr(vid)) { + Index3i et = mesh.GetTriangle(tid); + int idx = IndexUtil.find_tri_index(vid, ref et); + angle_sum += mesh.GetTriInternalAngleR(tid, idx); + } + return angle_sum - MathUtil.TwoPI; + } + + /// @@ -113,6 +131,91 @@ public static bool CheckIfCollapseCreatesFlip(DMesh3 mesh, int edgeID, Vector3d + /// + /// if before a flip we have normals (n1,n2) and after we have (m1,m2), check if + /// the dot between any of the 4 pairs changes sign after the flip, or is + /// less than the dot-product tolerance (ie angle tolerance) + /// + public static bool CheckIfEdgeFlipCreatesFlip(DMesh3 mesh, int eID, double flip_dot_tol = 0.0) + { + Util.gDevAssert(mesh.IsBoundaryEdge(eID) == false); + Index4i einfo = mesh.GetEdge(eID); + Index2i ov = mesh.GetEdgeOpposingV(eID); + + int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; + int t0 = einfo.c, t1 = einfo.d; + + Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); + Index3i tri_v = mesh.GetTriangle(t0); + int oa = a, ob = b; + IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); + Vector3d vOA = mesh.GetVertex(oa), vOB = mesh.GetVertex(ob); + Vector3d n0 = MathUtil.FastNormalDirection(ref vOA, ref vOB, ref vC); + Vector3d n1 = MathUtil.FastNormalDirection(ref vOB, ref vOA, ref vD); + Vector3d f0 = MathUtil.FastNormalDirection(ref vC, ref vD, ref vOB); + if (edge_flip_metric(ref n0, ref f0, flip_dot_tol) <= flip_dot_tol + || edge_flip_metric(ref n1, ref f0, flip_dot_tol) <= flip_dot_tol) + return true; + Vector3d f1 = MathUtil.FastNormalDirection(ref vD, ref vC, ref vOA); + if (edge_flip_metric(ref n0, ref f1, flip_dot_tol) <= flip_dot_tol + || edge_flip_metric(ref n1, ref f1, flip_dot_tol) <= flip_dot_tol) + return true; + return false; + } + static double edge_flip_metric(ref Vector3d n0, ref Vector3d n1, double flip_dot_tol) { + return (flip_dot_tol == 0) ? n0.Dot(n1) : n0.Normalized.Dot(n1.Normalized); + } + + + + /// + /// For given edge, return it's triangles and the triangles that would + /// be created if it was flipped (used in edge-flip optimizers) + /// + public static void GetEdgeFlipTris(DMesh3 mesh, int eID, + out Index3i orig_t0, out Index3i orig_t1, + out Index3i flip_t0, out Index3i flip_t1) + { + Index4i einfo = mesh.GetEdge(eID); + Index2i ov = mesh.GetEdgeOpposingV(eID); + int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; + int t0 = einfo.c; + Index3i tri_v = mesh.GetTriangle(t0); + int oa = a, ob = b; + IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); + orig_t0 = new Index3i(oa, ob, c); + orig_t1 = new Index3i(ob, oa, d); + flip_t0 = new Index3i(c, d, ob); + flip_t1 = new Index3i(d, c, oa); + } + + + /// + /// For given edge, return normals of it's two triangles, and normals + /// of the triangles created if edge is flipped (used in edge-flip optimizers) + /// + public static void GetEdgeFlipNormals(DMesh3 mesh, int eID, + out Vector3d n1, out Vector3d n2, + out Vector3d on1, out Vector3d on2) + { + Index4i einfo = mesh.GetEdge(eID); + Index2i ov = mesh.GetEdgeOpposingV(eID); + int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; + int t0 = einfo.c; + Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); + Index3i tri_v = mesh.GetTriangle(t0); + int oa = a, ob = b; + IndexUtil.orient_tri_edge(ref oa, ref ob, ref tri_v); + Vector3d vOA = mesh.GetVertex(oa), vOB = mesh.GetVertex(ob); + n1 = MathUtil.Normal(ref vOA, ref vOB, ref vC); + n2 = MathUtil.Normal(ref vOB, ref vOA, ref vD); + on1 = MathUtil.Normal(ref vC, ref vD, ref vOB); + on2 = MathUtil.Normal(ref vD, ref vC, ref vOA); + } + + + + public static DCurve3 ExtractLoopV(IMesh mesh, IEnumerable vertices) { DCurve3 curve = new DCurve3(); foreach (int vid in vertices) diff --git a/mesh/Reducer.cs b/mesh/Reducer.cs index b65ec6f0..7ef155ec 100644 --- a/mesh/Reducer.cs +++ b/mesh/Reducer.cs @@ -24,7 +24,7 @@ public class Reducer : MeshRefinerBase public bool MinimizeQuadricPositionError = true; // if true, we try to keep boundary vertices on boundary. You probably want this. - public bool PreserveBoundary = true; + public bool PreserveBoundaryShape = true; // [RMS] this is a debugging aid, will break to debugger if these edges are touched, in debug builds public List DebugEdges = new List(); @@ -326,7 +326,7 @@ protected Vector3d OptimalPoint(int eid, ref QuadricError q, int ea, int eb) { // if we would like to preserve boundary, we need to know that here // so that we properly score these edges - if (HaveBoundary && PreserveBoundary) { + if (HaveBoundary && PreserveBoundaryShape) { if (mesh.IsBoundaryEdge(eid)) { return (mesh.GetVertex(ea) + mesh.GetVertex(eb)) * 0.5; } else { @@ -513,7 +513,7 @@ protected virtual ProcessResult CollapseEdge(int edgeID, Vector3d vNewPos, out i return ProcessResult.Ignored_Constrained; // if we have a boundary, we want to collapse to boundary - if (PreserveBoundary && HaveBoundary) { + if (PreserveBoundaryShape && HaveBoundary) { if (collapse_to != -1) { if (( IsBoundaryV(b) && collapse_to != b) || ( IsBoundaryV(a) && collapse_to != a)) From 0c84997f47de4e2f4d875dea63164cf22ba912a5 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 14 Apr 2018 11:36:22 -0400 Subject: [PATCH 090/225] added optional action to RegionOperator that allows caller to customize the internal DSubmesh3 that is constructed. Useful to, for example, turn on triangle maps computation. --- mesh_ops/RegionOperator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mesh_ops/RegionOperator.cs b/mesh_ops/RegionOperator.cs index efe3c29e..ee1960fd 100644 --- a/mesh_ops/RegionOperator.cs +++ b/mesh_ops/RegionOperator.cs @@ -30,19 +30,25 @@ public class RegionOperator int[] cur_base_tris; - public RegionOperator(DMesh3 mesh, int[] regionTris) + public RegionOperator(DMesh3 mesh, int[] regionTris, Action submeshConfigF = null) { BaseMesh = mesh; - Region = new DSubmesh3(mesh, regionTris); + Region = new DSubmesh3(mesh); + if (submeshConfigF != null) + submeshConfigF(Region); + Region.Compute(regionTris); Region.ComputeBoundaryInfo(regionTris); cur_base_tris = (int[])regionTris.Clone(); } - public RegionOperator(DMesh3 mesh, IEnumerable regionTris) + public RegionOperator(DMesh3 mesh, IEnumerable regionTris, Action submeshConfigF = null) { BaseMesh = mesh; - Region = new DSubmesh3(mesh, regionTris); + Region = new DSubmesh3(mesh); + if (submeshConfigF != null) + submeshConfigF(Region); + Region.Compute(regionTris); int count = regionTris.Count(); Region.ComputeBoundaryInfo(regionTris, count); From 5392fb6e061548008f2301991cc7b4c694115184 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 14 Apr 2018 13:12:04 -0400 Subject: [PATCH 091/225] utility fns --- mesh/DSubmesh3.cs | 4 ++-- mesh_ops/RegionOperator.cs | 16 ++++++++++++++++ mesh_selection/MeshVertexSelection.cs | 27 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/mesh/DSubmesh3.cs b/mesh/DSubmesh3.cs index e445bd41..b3ec35af 100644 --- a/mesh/DSubmesh3.cs +++ b/mesh/DSubmesh3.cs @@ -27,9 +27,9 @@ public class DSubmesh3 public DVector SubToBaseT; // triangle index map from submesh to base mesh. Only computed if ComputeTriMaps = true. // boundary info - public IndexHashSet BaseBorderE; // list of internal border edge indices on base mesh + public IndexHashSet BaseBorderE; // list of internal border edge indices on base mesh. Does not include mesh boundary edges. public IndexHashSet BaseBoundaryE; // list of mesh-boundary edges on base mesh that are in submesh - public IndexHashSet BaseBorderV; // list of border vertex indices on base mesh (ie verts of BaseBorderE) + public IndexHashSet BaseBorderV; // list of border vertex indices on base mesh (ie verts of BaseBorderE - does not include mesh boundary vertices) public DSubmesh3(DMesh3 mesh, int[] subTriangles) diff --git a/mesh_ops/RegionOperator.cs b/mesh_ops/RegionOperator.cs index ee1960fd..b90f3737 100644 --- a/mesh_ops/RegionOperator.cs +++ b/mesh_ops/RegionOperator.cs @@ -65,6 +65,22 @@ public int[] CurrentBaseTriangles { } + /// + /// find base-mesh interior vertices of region (ie does not include region boundary vertices) + /// + public HashSet CurrentBaseInteriorVertices() + { + HashSet verts = new HashSet(); + IndexHashSet borderv = Region.BaseBorderV; + foreach ( int tid in cur_base_tris ) { + Index3i tv = BaseMesh.GetTriangle(tid); + if (borderv[tv.a] == false) verts.Add(tv.a); + if (borderv[tv.b] == false) verts.Add(tv.b); + if (borderv[tv.c] == false) verts.Add(tv.c); + } + return verts; + } + // After remeshing we may create an internal edge between two boundary vertices [a,b]. // Those vertices will be merged with vertices c and d in the base mesh. If the edge // [c,d] already exists in the base mesh, then after the merge we would have at least diff --git a/mesh_selection/MeshVertexSelection.cs b/mesh_selection/MeshVertexSelection.cs index 45bf1178..23023085 100644 --- a/mesh_selection/MeshVertexSelection.cs +++ b/mesh_selection/MeshVertexSelection.cs @@ -118,6 +118,33 @@ public void SelectTriangleVertices(MeshFaceSelection triangles) } + /// + /// Select vertices that are not on + /// + public void SelectInteriorVertices(MeshFaceSelection triangles, bool bIncludeBoundaryVertices = true) + { + foreach (int tid in triangles) { + Index3i nbrs = Mesh.GetTriNeighbourTris(tid); + + if ( bIncludeBoundaryVertices ) { + if ( (nbrs.a != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false) || + (nbrs.b != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false) || + (nbrs.c != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false)) + continue; + } else { + if (triangles.IsSelected(nbrs.a) == false || + triangles.IsSelected(nbrs.b) == false || + triangles.IsSelected(nbrs.c) == false) + continue; + } + + Index3i tri = Mesh.GetTriangle(tid); + add(tri.a); add(tri.b); add(tri.c); + } + } + + + public void SelectEdgeVertices(int[] edges) { From 3da5f6bd95569e1f05edb49d5d67d48777e22f0b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 14 Apr 2018 13:36:27 -0400 Subject: [PATCH 092/225] changed default behavior of remove tri/vtx to not preserve manifoldness. Currently was passing false to this in all calls made anywhere, so it seems like the 'safe' default is wrong (and constantly produces unexpected behavior) --- mesh/DMesh3_edge_operators.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index 72205841..8800fa0a 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -47,7 +47,7 @@ public void ReverseOrientation(bool bFlipNormals = true) { /// (if false, them throws exception if there are still any triangles!) /// if bPreserveManifold, checks that we will not create a bowtie vertex first /// - public MeshResult RemoveVertex(int vID, bool bRemoveAllTriangles = true, bool bPreserveManifold = true) + public MeshResult RemoveVertex(int vID, bool bRemoveAllTriangles = true, bool bPreserveManifold = false) { if (vertices_refcount.isValid(vID) == false) return MeshResult.Failed_NotAVertex; @@ -98,7 +98,7 @@ public MeshResult RemoveVertex(int vID, bool bRemoveAllTriangles = true, bool bP /// If this check is not done, you have to make sure you don't create a bowtie, because other /// code assumes we don't have bowties, and will not handle it properly /// - public MeshResult RemoveTriangle(int tID, bool bRemoveIsolatedVertices = true, bool bPreserveManifold = true) + public MeshResult RemoveTriangle(int tID, bool bRemoveIsolatedVertices = true, bool bPreserveManifold = false) { if ( ! triangles_refcount.isValid(tID) ) { Debug.Assert(false); From e3ccdada25cc3c9cf181b0a27e20ea46f9cefc1e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 14 Apr 2018 13:36:37 -0400 Subject: [PATCH 093/225] bugfix --- mesh_ops/SimpleHoleFiller.cs | 2 +- mesh_selection/MeshVertexSelection.cs | 37 +++++++++++++++------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/mesh_ops/SimpleHoleFiller.cs b/mesh_ops/SimpleHoleFiller.cs index d6f6a2ce..5ac92f55 100644 --- a/mesh_ops/SimpleHoleFiller.cs +++ b/mesh_ops/SimpleHoleFiller.cs @@ -70,7 +70,7 @@ public virtual bool Fill(int group_id = -1) // if fill failed, back out vertex-add if ( NewTriangles == null ) { - Mesh.RemoveVertex(NewVertex); + Mesh.RemoveVertex(NewVertex, true, false); NewVertex = DMesh3.InvalidID; } diff --git a/mesh_selection/MeshVertexSelection.cs b/mesh_selection/MeshVertexSelection.cs index 23023085..9fb8f0a2 100644 --- a/mesh_selection/MeshVertexSelection.cs +++ b/mesh_selection/MeshVertexSelection.cs @@ -119,27 +119,30 @@ public void SelectTriangleVertices(MeshFaceSelection triangles) /// - /// Select vertices that are not on + /// for each vertex of input triangle set, select vertex if all + /// one-ring triangles are contained in triangle set (ie vertex is not on boundary of triangle set). /// - public void SelectInteriorVertices(MeshFaceSelection triangles, bool bIncludeBoundaryVertices = true) + public void SelectInteriorVertices(MeshFaceSelection triangles) { - foreach (int tid in triangles) { - Index3i nbrs = Mesh.GetTriNeighbourTris(tid); - - if ( bIncludeBoundaryVertices ) { - if ( (nbrs.a != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false) || - (nbrs.b != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false) || - (nbrs.c != DMesh3.InvalidID && triangles.IsSelected(nbrs.a) == false)) - continue; - } else { - if (triangles.IsSelected(nbrs.a) == false || - triangles.IsSelected(nbrs.b) == false || - triangles.IsSelected(nbrs.c) == false) + HashSet borderv = new HashSet(); + foreach ( int tid in triangles ) { + Index3i tv = Mesh.GetTriangle(tid); + for ( int j = 0; j < 3; ++j ) { + int vid = tv[j]; + if (Selected.Contains(vid) || borderv.Contains(vid)) continue; + bool full_ring = true; + foreach (int ring_tid in Mesh.VtxTrianglesItr(vid)) { + if (triangles.IsSelected(ring_tid) == false) { + full_ring = false; + break; + } + } + if (full_ring) + add(vid); + else + borderv.Add(vid); } - - Index3i tri = Mesh.GetTriangle(tid); - add(tri.a); add(tri.b); add(tri.c); } } From 717c98fba3a4c6d8c76a81da9205a34eecc78bff Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:33:26 -0400 Subject: [PATCH 094/225] utility functions, etc --- curve/CurveUtils.cs | 8 ++ curve/DCurve3.cs | 27 ++++-- curve/ICurve.cs | 2 + math/Box2.cs | 2 +- math/Box3.cs | 169 +++++++++++++++++++++++++++++++++++++ math/Matrix3d.cs | 12 ++- math/Quaterniond.cs | 21 +++-- math/Segment3.cs | 14 +++ math/Vector3d.cs | 68 ++++++++++++--- queries/RayIntersection.cs | 8 +- spatial/PointAABBTree3.cs | 3 +- 11 files changed, 302 insertions(+), 32 deletions(-) diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs index 87897553..663b6fbd 100644 --- a/curve/CurveUtils.cs +++ b/curve/CurveUtils.cs @@ -165,7 +165,15 @@ public class IWrappedCurve3d : ISampledCurve3d public bool Closed { get; set; } public int VertexCount { get { return (VertexList == null) ? 0 : VertexList.Count; } } + public int SegmentCount { get { return Closed ? VertexCount : VertexCount - 1; } } + public Vector3d GetVertex(int i) { return VertexList[i]; } + public Segment3d GetSegment(int iSegment) { + return (Closed) ? new Segment3d(VertexList[iSegment], VertexList[(iSegment + 1) % VertexList.Count]) + : new Segment3d(VertexList[iSegment], VertexList[iSegment + 1]); + } + + public IEnumerable Vertices { get { return VertexList; } } } diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index d46c4349..4378a8e5 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -75,6 +75,9 @@ public void AppendVertex(Vector3d v) { public int VertexCount { get { return vertices.Count; } } + public int SegmentCount { + get { return Closed ? vertices.Count : vertices.Count - 1; } + } public Vector3d GetVertex(int i) { return vertices[i]; @@ -136,7 +139,7 @@ public Vector3d Start { get { return vertices[0]; } } public Vector3d End { - get { return vertices.Last(); } + get { return (Closed) ? vertices[0] : vertices.Last(); } } public IEnumerable Vertices { @@ -144,15 +147,29 @@ public IEnumerable Vertices { } - public Segment3d Segment(int iSegment) + public Segment3d GetSegment(int iSegment) { - return new Segment3d(vertices[iSegment], vertices[iSegment + 1]); + return (Closed) ? new Segment3d(vertices[iSegment], vertices[(iSegment+1)%vertices.Count]) + : new Segment3d(vertices[iSegment], vertices[iSegment + 1]); } public IEnumerable SegmentItr() { - for (int i = 0; i < vertices.Count - 1; ++i) - yield return new Segment3d(vertices[i], vertices[i + 1]); + if (Closed) { + int NV = vertices.Count - 1; + for (int i = 0; i < NV; ++i) + yield return new Segment3d(vertices[i], vertices[i + 1]); + } else { + int NV = vertices.Count; + for (int i = 0; i < vertices.Count; ++i) + yield return new Segment3d(vertices[i], vertices[(i + 1)%NV]); + } + } + + public Vector3d PointAt(int iSegment, double fSegT) + { + Segment3d seg = new Segment3d(vertices[iSegment], vertices[(iSegment + 1) % vertices.Count]); + return seg.PointAt(fSegT); } diff --git a/curve/ICurve.cs b/curve/ICurve.cs index 8e59be2b..eaf106ea 100644 --- a/curve/ICurve.cs +++ b/curve/ICurve.cs @@ -31,9 +31,11 @@ public interface IParametricCurve3d public interface ISampledCurve3d { int VertexCount { get; } + int SegmentCount { get; } bool Closed { get; } Vector3d GetVertex(int i); + Segment3d GetSegment(int i); IEnumerable Vertices { get; } } diff --git a/math/Box2.cs b/math/Box2.cs index fdb95a31..08b59779 100644 --- a/math/Box2.cs +++ b/math/Box2.cs @@ -197,7 +197,7 @@ public Vector2d ClosestPoint(Vector2d v) } } - return closest.x * AxisX + closest.y * AxisY; + return Center + closest.x*AxisX + closest.y*AxisY; } diff --git a/math/Box3.cs b/math/Box3.cs index 80fb62d2..f0ab9ad3 100644 --- a/math/Box3.cs +++ b/math/Box3.cs @@ -53,6 +53,13 @@ public Box3d(Frame3f frame, Vector3d extent) AxisZ = frame.Z; Extent = extent; } + public Box3d(Segment3d seg) + { + Center = seg.Center; + AxisZ = seg.Direction; + Vector3d.MakePerpVectors(ref AxisZ, out AxisX, out AxisY); + Extent = new Vector3d(0, 0, seg.Extent); + } public static readonly Box3d Empty = new Box3d(Vector3d.Zero); public static readonly Box3d UnitZeroCentered = new Box3d(Vector3d.Zero, 0.5 * Vector3d.One); @@ -252,6 +259,168 @@ public void ScaleExtents(Vector3d s) Extent *= s; } + + + + /// + /// Returns distance to box, or 0 if point is inside box. + /// Ported from WildMagic5 Wm5DistPoint3Box3.cpp + /// + public double DistanceSquared(Vector3d v) + { + // Work in the box's coordinate system. + v -= this.Center; + + // Compute squared distance and closest point on box. + double sqrDistance = 0; + double delta; + Vector3d closest = new Vector3d(); + int i; + for (i = 0; i < 3; ++i) { + closest[i] = Axis(i).Dot(ref v); + if (closest[i] < -Extent[i]) { + delta = closest[i] + Extent[i]; + sqrDistance += delta * delta; + closest[i] = -Extent[i]; + } else if (closest[i] > Extent[i]) { + delta = closest[i] - Extent[i]; + sqrDistance += delta * delta; + closest[i] = Extent[i]; + } + } + + return sqrDistance; + } + + + + /// + /// Returns distance to box, or 0 if point is inside box. + /// Ported from WildMagic5 Wm5DistPoint3Box3.cpp + /// + public Vector3d ClosestPoint(Vector3d v) + { + // Work in the box's coordinate system. + v -= this.Center; + + // Compute squared distance and closest point on box. + double sqrDistance = 0; + double delta; + Vector3d closest = new Vector3d(); + for (int i = 0; i < 3; ++i) { + closest[i] = Axis(i).Dot(ref v); + double extent = Extent[i]; + if (closest[i] < -extent) { + delta = closest[i] + extent; + sqrDistance += delta * delta; + closest[i] = -extent; + } else if (closest[i] > extent) { + delta = closest[i] - extent; + sqrDistance += delta * delta; + closest[i] = extent; + } + } + + return Center + closest.x*AxisX + closest.y*AxisY + closest.z*AxisZ; + } + + + + + + // ported from WildMagic5 Wm5ContBox3.cpp::MergeBoxes + public static Box3d Merge(ref Box3d box0, ref Box3d box1) + { + // Construct a box that contains the input boxes. + Box3d box = new Box3d(); + + // The first guess at the box center. This value will be updated later + // after the input box vertices are projected onto axes determined by an + // average of box axes. + box.Center = 0.5 * (box0.Center + box1.Center); + + // A box's axes, when viewed as the columns of a matrix, form a rotation + // matrix. The input box axes are converted to quaternions. The average + // quaternion is computed, then normalized to unit length. The result is + // the slerp of the two input quaternions with t-value of 1/2. The result + // is converted back to a rotation matrix and its columns are selected as + // the merged box axes. + Quaterniond q0 = new Quaterniond(), q1 = new Quaterniond(); + Matrix3d rot0 = new Matrix3d(ref box0.AxisX, ref box0.AxisY, ref box0.AxisZ, false); + q0.SetFromRotationMatrix(ref rot0); + Matrix3d rot1 = new Matrix3d(ref box1.AxisX, ref box1.AxisY, ref box1.AxisZ, false); + q1.SetFromRotationMatrix(ref rot1); + if (q0.Dot(q1) < 0) { + q1 = -q1; + } + + Quaterniond q = q0 + q1; + double invLength = 1.0 / Math.Sqrt(q.Dot(q)); + q = q * invLength; + Matrix3d q_mat = q.ToRotationMatrix(); + box.AxisX = q_mat.Column(0); box.AxisY = q_mat.Column(1); box.AxisZ = q_mat.Column(2); //q.ToRotationMatrix(box.Axis); + + // Project the input box vertices onto the merged-box axes. Each axis + // D[i] containing the current center C has a minimum projected value + // min[i] and a maximum projected value max[i]. The corresponding end + // points on the axes are C+min[i]*D[i] and C+max[i]*D[i]. The point C + // is not necessarily the midpoint for any of the intervals. The actual + // box center will be adjusted from C to a point C' that is the midpoint + // of each interval, + // C' = C + sum_{i=0}^2 0.5*(min[i]+max[i])*D[i] + // The box extents are + // e[i] = 0.5*(max[i]-min[i]) + + int i, j; + double dot; + Vector3d[] vertex = new Vector3d[8]; + Vector3d pmin = Vector3d.Zero; + Vector3d pmax = Vector3d.Zero; + + box0.ComputeVertices(vertex); + for (i = 0; i < 8; ++i) { + Vector3d diff = vertex[i] - box.Center; + for (j = 0; j < 3; ++j) { + dot = box.Axis(j).Dot(ref diff); + if (dot > pmax[j]) { + pmax[j] = dot; + } else if (dot < pmin[j]) { + pmin[j] = dot; + } + } + } + + box1.ComputeVertices(vertex); + for (i = 0; i < 8; ++i) { + Vector3d diff = vertex[i] - box.Center; + for (j = 0; j < 3; ++j) { + dot = box.Axis(j).Dot(ref diff); + if (dot > pmax[j]) { + pmax[j] = dot; + } else if (dot < pmin[j]) { + pmin[j] = dot; + } + } + } + + // [min,max] is the axis-aligned box in the coordinate system of the + // merged box axes. Update the current box center to be the center of + // the new box. Compute the extents based on the new center. + for (j = 0; j < 3; ++j) { + box.Center += (0.5*(pmax[j] + pmin[j])) * box.Axis(j); + box.Extent[j] = 0.5*(pmax[j] - pmin[j]); + } + + return box; + } + + + + + + + + public static implicit operator Box3d(Box3f v) { return new Box3d(v.Center, v.AxisX, v.AxisY, v.AxisZ, v.Extent); diff --git a/math/Matrix3d.cs b/math/Matrix3d.cs index 7f6f3993..7fc052f2 100644 --- a/math/Matrix3d.cs +++ b/math/Matrix3d.cs @@ -68,7 +68,17 @@ public Matrix3d(Vector3d v1, Vector3d v2, Vector3d v3, bool bRows) Row2 = new Vector3d(v1.z, v2.z, v3.z); } } - public Matrix3d(double m00, double m01, double m02, double m10, double m11, double m12, double m20, double m21, double m22) { + public Matrix3d(ref Vector3d v1, ref Vector3d v2, ref Vector3d v3, bool bRows) + { + if (bRows) { + Row0 = v1; Row1 = v2; Row2 = v3; + } else { + Row0 = new Vector3d(v1.x, v2.x, v3.x); + Row1 = new Vector3d(v1.y, v2.y, v3.y); + Row2 = new Vector3d(v1.z, v2.z, v3.z); + } + } + public Matrix3d(double m00, double m01, double m02, double m10, double m11, double m12, double m20, double m21, double m22) { Row0 = new Vector3d(m00, m01, m02); Row1 = new Vector3d(m10, m11, m12); Row2 = new Vector3d(m20, m21, m22); diff --git a/math/Quaterniond.cs b/math/Quaterniond.cs index d608338a..205192f0 100644 --- a/math/Quaterniond.cs +++ b/math/Quaterniond.cs @@ -74,7 +74,9 @@ public double Dot(Quaterniond q2) { } - + public static Quaterniond operator -(Quaterniond q2) { + return new Quaterniond(-q2.x, -q2.y, -q2.z, -q2.w); + } public static Quaterniond operator*(Quaterniond a, Quaterniond b) { double w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z; @@ -83,12 +85,19 @@ public double Dot(Quaterniond q2) { double z = a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x; return new Quaterniond(x, y, z, w); } - - + public static Quaterniond operator *(Quaterniond q1, double d) { + return new Quaterniond(d * q1.x, d * q1.y, d * q1.z, d * q1.w); + } + public static Quaterniond operator *(double d, Quaterniond q1) { + return new Quaterniond(d * q1.x, d * q1.y, d * q1.z, d * q1.w); + } public static Quaterniond operator -(Quaterniond q1, Quaterniond q2) { return new Quaterniond(q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w); } + public static Quaterniond operator +(Quaterniond q1, Quaterniond q2) { + return new Quaterniond(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w); + } public static Vector3d operator *(Quaterniond q, Vector3d v) { //return q.ToRotationMatrix() * v; @@ -256,8 +265,10 @@ public static Quaterniond Slerp(Quaterniond p, Quaterniond q, double t) { } - - public void SetFromRotationMatrix(Matrix3d rot) + public void SetFromRotationMatrix(Matrix3d rot) { + SetFromRotationMatrix(ref rot); + } + public void SetFromRotationMatrix(ref Matrix3d rot) { // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes // article "Quaternion Calculus and Fast Animation". diff --git a/math/Segment3.cs b/math/Segment3.cs index f96947ab..4583ef90 100644 --- a/math/Segment3.cs +++ b/math/Segment3.cs @@ -61,6 +61,20 @@ public double DistanceSquared(Vector3d p) Vector3d proj = Center + t * Direction; return (proj - p).LengthSquared; } + public double DistanceSquared(Vector3d p, out double t) + { + t = (p - Center).Dot(Direction); + if (t >= Extent) { + t = Extent; + return P1.DistanceSquared(p); + } else if (t <= -Extent) { + t = -Extent; + return P0.DistanceSquared(p); + } + Vector3d proj = Center + t * Direction; + return (proj - p).LengthSquared; + } + public Vector3d NearestPoint(Vector3d p) { diff --git a/math/Vector3d.cs b/math/Vector3d.cs index 0c2ca2da..6884a450 100644 --- a/math/Vector3d.cs +++ b/math/Vector3d.cs @@ -356,9 +356,12 @@ public static explicit operator Vector3(Vector3d v) // complicated functions go down here... - // [RMS] this is from WildMagic5, but I added returning the minLength value - // from GTEngine, because I use this in place of GTEngine's Orthonormalize in - // ComputeOrthogonalComplement below + /// + /// Gram-Schmidt orthonormalization of the input vectors. + /// [RMS] this is from WildMagic5, but I added returning the minLength value + /// from GTEngine, because I use this in place of GTEngine's Orthonormalize in + /// ComputeOrthogonalComplement below + /// public static double Orthonormalize(ref Vector3d u, ref Vector3d v, ref Vector3d w) { // If the input vectors are v0, v1, and v2, then the Gram-Schmidt @@ -393,9 +396,11 @@ public static double Orthonormalize(ref Vector3d u, ref Vector3d v, ref Vector3d } - // Input W must be a unit-length vector. The output vectors {U,V} are - // unit length and mutually perpendicular, and {U,V,W} is an orthonormal basis. - // ported from WildMagic5 + /// + /// Input W must be a unit-length vector. The output vectors {U,V} are + /// unit length and mutually perpendicular, and {U,V,W} is an orthonormal basis. + /// ported from WildMagic5 + /// public static void GenerateComplementBasis(ref Vector3d u, ref Vector3d v, Vector3d w) { double invLength; @@ -421,14 +426,16 @@ public static void GenerateComplementBasis(ref Vector3d u, ref Vector3d v, Vecto } } - // this function is from GTEngine - // Compute a right-handed orthonormal basis for the orthogonal complement - // of the input vectors. The function returns the smallest length of the - // unnormalized vectors computed during the process. If this value is nearly - // zero, it is possible that the inputs are linearly dependent (within - // numerical round-off errors). On input, numInputs must be 1 or 2 and - // v0 through v(numInputs-1) must be initialized. On output, the - // vectors v0 through v2 form an orthonormal set. + /// + /// this function is ported from GTEngine. + /// Compute a right-handed orthonormal basis for the orthogonal complement + /// of the input vectors. The function returns the smallest length of the + /// unnormalized vectors computed during the process. If this value is nearly + /// zero, it is possible that the inputs are linearly dependent (within + /// numerical round-off errors). On input, numInputs must be 1 or 2 and + /// v0 through v(numInputs-1) must be initialized. On output, the + /// vectors v0 through v2 form an orthonormal set. + /// public static double ComputeOrthogonalComplement(int numInputs, Vector3d v0, ref Vector3d v1, ref Vector3d v2 /*, bool robust = false*/) { if (numInputs == 1) { @@ -451,5 +458,38 @@ public static double ComputeOrthogonalComplement(int numInputs, Vector3d v0, ref return 0; } + + + /// + /// Returns two vectors perpendicular to n, as efficiently as possible. + /// Duff et all method, from https://graphics.pixar.com/library/OrthonormalB/paper.pdf + /// + public static void MakePerpVectors(ref Vector3d n, out Vector3d b1, out Vector3d b2) + { + if (n.z < 0.0) { + double a = 1.0 / (1.0 - n.z); + double b = n.x * n.y * a; + //b1 = Vec3f(1.0f - n.x * n.x * a, -b, n.x); + //b2 = Vec3f(b, n.y * n.y * a - 1.0f, -n.y); + b1.x = 1.0f - n.x * n.x * a; + b1.y = -b; + b1.z = n.x; + b2.x = b; + b2.y = n.y * n.y * a - 1.0f; + b2.z = -n.y; + } else { + double a = 1.0 / (1.0 + n.z); + double b = -n.x * n.y * a; + //b1 = Vec3f(1.0 - n.x * n.x * a, b, -n.x); + //b2 = Vec3f(b, 1.0 - n.y * n.y * a, -n.y); + b1.x = 1.0 - n.x * n.x * a; + b1.y = b; + b1.z = -n.x; + b2.x = b; + b2.y = 1.0 - n.y * n.y * a; + b2.z = -n.y; + } + } + } } diff --git a/queries/RayIntersection.cs b/queries/RayIntersection.cs index a3d27cd6..1c60a980 100644 --- a/queries/RayIntersection.cs +++ b/queries/RayIntersection.cs @@ -15,12 +15,12 @@ private RayIntersection() // basic ray-sphere intersection public static bool Sphere(Vector3f vOrigin, Vector3f vDirection, Vector3f vCenter, float fRadius, out float fRayT) { - bool bHit = SphereSigned(vOrigin, vDirection, vCenter, fRadius, out fRayT); + bool bHit = SphereSigned(ref vOrigin, ref vDirection, ref vCenter, fRadius, out fRayT); fRayT = Math.Abs(fRayT); return bHit; } - public static bool SphereSigned(Vector3f vOrigin, Vector3f vDirection, Vector3f vCenter, float fRadius, out float fRayT) + public static bool SphereSigned(ref Vector3f vOrigin, ref Vector3f vDirection, ref Vector3f vCenter, float fRadius, out float fRayT) { fRayT = 0.0f; Vector3f m = vOrigin - vCenter; @@ -44,11 +44,11 @@ public static bool SphereSigned(Vector3f vOrigin, Vector3f vDirection, Vector3f - public static bool SphereSigned(Vector3d vOrigin, Vector3d vDirection, Vector3d vCenter, double fRadius, out double fRayT) + public static bool SphereSigned(ref Vector3d vOrigin, ref Vector3d vDirection, ref Vector3d vCenter, double fRadius, out double fRayT) { fRayT = 0.0; Vector3d m = vOrigin - vCenter; - double b = m.Dot(vDirection); + double b = m.Dot(ref vDirection); double c = m.Dot(m) - fRadius * fRadius; // Exit if r’s origin outside s (c > 0) and r pointing away from s (b > 0) diff --git a/spatial/PointAABBTree3.cs b/spatial/PointAABBTree3.cs index 8b3edb3e..93b38841 100644 --- a/spatial/PointAABBTree3.cs +++ b/spatial/PointAABBTree3.cs @@ -7,8 +7,7 @@ namespace g3 { /// - /// Hierarchical Axis-Aligned-Bounding-Box tree for a DMesh3 mesh. - /// This class supports a variety of spatial queries, listed below. + /// Hierarchical Axis-Aligned-Bounding-Box tree for an IPointSet /// /// /// TODO: no timestamp support right now... From 4e62f0882e2ef226c0419ab1d6e3dc6eae02899f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:34:28 -0400 Subject: [PATCH 095/225] added static function that can be called w/o having to construct object. should probably replace existing calculation w/ this. --- distance/DistRay3Segment3.cs | 146 +++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 5 deletions(-) diff --git a/distance/DistRay3Segment3.cs b/distance/DistRay3Segment3.cs index bc9314b8..57f2bc04 100644 --- a/distance/DistRay3Segment3.cs +++ b/distance/DistRay3Segment3.cs @@ -5,9 +5,10 @@ namespace g3 { - // ported from WildMagic 5 - // https://www.geometrictools.com/Downloads/Downloads.html - + /// + /// Distance between ray and segment + /// ported from WildMagic5 + /// class DistRay3Segment3 { Ray3d ray; @@ -39,10 +40,14 @@ public DistRay3Segment3(Ray3d rayIn, Segment3d segmentIn) static public double MinDistance(Ray3d r, Segment3d s) { - return new DistRay3Segment3(r, s).Get(); + double rayt, segt; + double dsqr = SquaredDistance(ref r, ref s, out rayt, out segt); + return Math.Sqrt(dsqr); } static public double MinDistanceSegmentParam(Ray3d r, Segment3d s) { - return new DistRay3Segment3(r, s).Compute().SegmentParameter; + double rayt, segt; + double dsqr = SquaredDistance(ref r, ref s, out rayt, out segt); + return segt; } @@ -184,5 +189,136 @@ public double GetSquared() } + + + + + /// + /// compute w/o allocating temporaries/etc + /// + public static double SquaredDistance(ref Ray3d ray, ref Segment3d segment, + out double rayT, out double segT) + { + Vector3d diff = ray.Origin - segment.Center; + double a01 = -ray.Direction.Dot(segment.Direction); + double b0 = diff.Dot(ray.Direction); + double b1 = -diff.Dot(segment.Direction); + double c = diff.LengthSquared; + double det = Math.Abs(1 - a01 * a01); + double s0, s1, sqrDist, extDet; + + if (det >= MathUtil.ZeroTolerance) { + // The Ray and Segment are not parallel. + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segment.Extent * det; + + if (s0 >= 0) { + if (s1 >= -extDet) { + if (s1 <= extDet) // region 0 + { + // Minimum at interior points of Ray and Segment. + double invDet = (1) / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * (s0 + a01 * s1 + (2) * b0) + + s1 * (a01 * s0 + s1 + (2) * b1) + c; + } else // region 1 + { + s1 = segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } else { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } else // region 5 + { + s1 = -segment.Extent; + s0 = -(a01 * s1 + b0); + if (s0 > 0) { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } else { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } else { + if (s1 <= -extDet) // region 4 + { + s0 = -(-a01 * segment.Extent + b0); + if (s0 > 0) { + s1 = -segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } else { + s0 = 0; + s1 = -b1; + if (s1 < -segment.Extent) { + s1 = -segment.Extent; + } else if (s1 > segment.Extent) { + s1 = segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } else if (s1 <= extDet) // region 3 + { + s0 = 0; + s1 = -b1; + if (s1 < -segment.Extent) { + s1 = -segment.Extent; + } else if (s1 > segment.Extent) { + s1 = segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } else // region 2 + { + s0 = -(a01 * segment.Extent + b0); + if (s0 > 0) { + s1 = segment.Extent; + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } else { + s0 = 0; + s1 = -b1; + if (s1 < -segment.Extent) { + s1 = -segment.Extent; + } else if (s1 > segment.Extent) { + s1 = segment.Extent; + } + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + } + } else { + // Ray and Segment are parallel. + if (a01 > 0) { + // Opposite direction vectors. + s1 = -segment.Extent; + } else { + // Same direction vectors. + s1 = segment.Extent; + } + + s0 = -(a01 * s1 + b0); + if (s0 > 0) { + sqrDist = -s0 * s0 + s1 * (s1 + (2) * b1) + c; + } else { + s0 = 0; + sqrDist = s1 * (s1 + (2) * b1) + c; + } + } + + rayT = s0; + segT = s1; + + // Account for numerical round-off errors. + if (sqrDist < 0) + sqrDist = 0; + return sqrDist; + } + + + } } From 5f38b3965e02210aadb6a433ee08d8af461a982a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:34:50 -0400 Subject: [PATCH 096/225] use new static function instead of per-iteration object --- curve/CurveUtils.cs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs index 663b6fbd..bff793cc 100644 --- a/curve/CurveUtils.cs +++ b/curve/CurveUtils.cs @@ -75,31 +75,27 @@ public static int FindNearestIndex(ISampledCurve3d c, Vector3d v) - public static bool FindClosestRayIntersection(ISampledCurve3d c, double segRadius, Ray3d ray, out double rayT) + public static bool FindClosestRayIntersection(ISampledCurve3d c, double segRadius, Ray3d ray, out double minRayT) { - rayT = double.MaxValue; + minRayT = double.MaxValue; int nNearSegment = -1; - //double fNearSegT = 0.0; - int N = c.VertexCount; - int iStop = (c.Closed) ? N : N - 1; - for (int i = 0; i < iStop; ++i) { - DistRay3Segment3 dist = new DistRay3Segment3(ray, - new Segment3d(c.GetVertex(i), c.GetVertex( (i + 1) % N ))); + int nSegs = c.SegmentCount; + for (int i = 0; i < nSegs; ++i) { + Segment3d seg = c.GetSegment(i); // raycast to line bounding-sphere first (is this going ot be faster??) double fSphereHitT; - bool bHitBoundSphere = RayIntersection.SphereSigned(ray.Origin, ray.Direction, - dist.Segment.Center, dist.Segment.Extent + segRadius, out fSphereHitT); + bool bHitBoundSphere = RayIntersection.SphereSigned(ref ray.Origin, ref ray.Direction, + ref seg.Center, seg.Extent + segRadius, out fSphereHitT); if (bHitBoundSphere == false) continue; - // find ray/seg min-distance and use ray T - double dSqr = dist.GetSquared(); + double rayt, segt; + double dSqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg, out rayt, out segt); if ( dSqr < segRadius*segRadius) { - if (dist.RayParameter < rayT) { - rayT = dist.RayParameter; - //fNearSegT = dist.SegmentParameter; + if (rayt < minRayT) { + minRayT = rayt; nNearSegment = i; } } From d9644d5566305302cd925635cb54f404138cd189 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:37:12 -0400 Subject: [PATCH 097/225] refactored ray/aabb and ray/obb so that intersection test is a static function that can be called w/o constructing object --- intersection/IntrLine3AxisAlignedBox3.cs | 6 +- intersection/IntrRay3AxisAlignedBox3.cs | 139 ++++++++++++----------- intersection/IntrRay3Box3.cs | 118 ++++++++++--------- 3 files changed, 137 insertions(+), 126 deletions(-) diff --git a/intersection/IntrLine3AxisAlignedBox3.cs b/intersection/IntrLine3AxisAlignedBox3.cs index eb66f66b..16c71bf0 100644 --- a/intersection/IntrLine3AxisAlignedBox3.cs +++ b/intersection/IntrLine3AxisAlignedBox3.cs @@ -61,7 +61,7 @@ public bool Find() LineParam0 = -double.MaxValue; LineParam1 = double.MaxValue; - DoClipping(ref LineParam0, ref LineParam1, line.Origin, line.Direction, box, + DoClipping(ref LineParam0, ref LineParam1, ref line.Origin, ref line.Direction, ref box, true, ref Quantity, ref Point0, ref Point1, ref Type); Result = (Type != IntersectionType.Empty) ? @@ -111,8 +111,8 @@ public bool Test () static public bool DoClipping (ref double t0, ref double t1, - Vector3d origin, Vector3d direction, - AxisAlignedBox3d box, bool solid, ref int quantity, + ref Vector3d origin, ref Vector3d direction, + ref AxisAlignedBox3d box, bool solid, ref int quantity, ref Vector3d point0, ref Vector3d point1, ref IntersectionType intrType) { diff --git a/intersection/IntrRay3AxisAlignedBox3.cs b/intersection/IntrRay3AxisAlignedBox3.cs index e8241e6f..d0de1c05 100644 --- a/intersection/IntrRay3AxisAlignedBox3.cs +++ b/intersection/IntrRay3AxisAlignedBox3.cs @@ -61,7 +61,7 @@ public bool Find() RayParam0 = 0.0; RayParam1 = double.MaxValue; - IntrLine3AxisAlignedBox3.DoClipping(ref RayParam0, ref RayParam1, ray.Origin, ray.Direction, box, + IntrLine3AxisAlignedBox3.DoClipping(ref RayParam0, ref RayParam1, ref ray.Origin, ref ray.Direction, ref box, true, ref Quantity, ref Point0, ref Point1, ref Type); Result = (Type != IntersectionType.Empty) ? @@ -71,73 +71,76 @@ public bool Find() - // [RMS TODO: lots of useless dot products below!! left over from obox conversion] public bool Test () { - Vector3d WdU = Vector3d.Zero; - Vector3d AWdU = Vector3d.Zero; - Vector3d DdU = Vector3d.Zero; - Vector3d ADdU = Vector3d.Zero; - Vector3d AWxDdU = Vector3d.Zero; - double RHS; - - Vector3d diff = ray.Origin - box.Center; - Vector3d extent = box.Extents; - - WdU[0] = ray.Direction.Dot(Vector3d.AxisX); - AWdU[0] = Math.Abs(WdU[0]); - DdU[0] = diff.Dot(Vector3d.AxisX); - ADdU[0] = Math.Abs(DdU[0]); - if (ADdU[0] > extent.x && DdU[0]*WdU[0] >= (double)0) - { - return false; - } - - WdU[1] = ray.Direction.Dot(Vector3d.AxisY); - AWdU[1] = Math.Abs(WdU[1]); - DdU[1] = diff.Dot(Vector3d.AxisY); - ADdU[1] = Math.Abs(DdU[1]); - if (ADdU[1] > extent.y && DdU[1]*WdU[1] >= (double)0) - { - return false; - } - - WdU[2] = ray.Direction.Dot(Vector3d.AxisZ); - AWdU[2] = Math.Abs(WdU[2]); - DdU[2] = diff.Dot(Vector3d.AxisZ); - ADdU[2] = Math.Abs(DdU[2]); - if (ADdU[2] > extent.z && DdU[2]*WdU[2] >= (double)0) - { - return false; - } - - Vector3d WxD = ray.Direction.Cross(diff); - - AWxDdU[0] = Math.Abs(WxD.Dot(Vector3d.AxisX)); - RHS = extent.y*AWdU[2] + extent.z*AWdU[1]; - if (AWxDdU[0] > RHS) - { - return false; - } - - AWxDdU[1] = Math.Abs(WxD.Dot(Vector3d.AxisY)); - RHS = extent.x*AWdU[2] + extent.z*AWdU[0]; - if (AWxDdU[1] > RHS) - { - return false; - } - - AWxDdU[2] = Math.Abs(WxD.Dot(Vector3d.AxisZ)); - RHS = extent.x*AWdU[1] + extent.y*AWdU[0]; - if (AWxDdU[2] > RHS) - { - return false; - } - - return true; - } - - - - } + return Intersects(ref ray, ref box); + } + + + /// + /// test if ray intersects box. + /// expandExtents allows you to scale box for hit-testing purposes. + /// + public static bool Intersects(ref Ray3d ray, ref AxisAlignedBox3d box, double expandExtents = 0) + { + Vector3d WdU = Vector3d.Zero; + Vector3d AWdU = Vector3d.Zero; + Vector3d DdU = Vector3d.Zero; + Vector3d ADdU = Vector3d.Zero; + Vector3d AWxDdU = Vector3d.Zero; + double RHS; + + Vector3d diff = ray.Origin - box.Center; + Vector3d extent = box.Extents + expandExtents; + + WdU[0] = ray.Direction.x; // ray.Direction.Dot(Vector3d.AxisX); + AWdU[0] = Math.Abs(WdU[0]); + DdU[0] = diff.x; // diff.Dot(Vector3d.AxisX); + ADdU[0] = Math.Abs(DdU[0]); + if (ADdU[0] > extent.x && DdU[0] * WdU[0] >= (double)0) { + return false; + } + + WdU[1] = ray.Direction.y; // ray.Direction.Dot(Vector3d.AxisY); + AWdU[1] = Math.Abs(WdU[1]); + DdU[1] = diff.y; // diff.Dot(Vector3d.AxisY); + ADdU[1] = Math.Abs(DdU[1]); + if (ADdU[1] > extent.y && DdU[1] * WdU[1] >= (double)0) { + return false; + } + + WdU[2] = ray.Direction.z; // ray.Direction.Dot(Vector3d.AxisZ); + AWdU[2] = Math.Abs(WdU[2]); + DdU[2] = diff.z; // diff.Dot(Vector3d.AxisZ); + ADdU[2] = Math.Abs(DdU[2]); + if (ADdU[2] > extent.z && DdU[2] * WdU[2] >= (double)0) { + return false; + } + + Vector3d WxD = ray.Direction.Cross(diff); + + AWxDdU[0] = Math.Abs(WxD.x); // Math.Abs(WxD.Dot(Vector3d.AxisX)); + RHS = extent.y * AWdU[2] + extent.z * AWdU[1]; + if (AWxDdU[0] > RHS) { + return false; + } + + AWxDdU[1] = Math.Abs(WxD.y); // Math.Abs(WxD.Dot(Vector3d.AxisY)); + RHS = extent.x * AWdU[2] + extent.z * AWdU[0]; + if (AWxDdU[1] > RHS) { + return false; + } + + AWxDdU[2] = Math.Abs(WxD.z); // Math.Abs(WxD.Dot(Vector3d.AxisZ)); + RHS = extent.x * AWdU[1] + extent.y * AWdU[0]; + if (AWxDdU[2] > RHS) { + return false; + } + + return true; + } + + + + } } diff --git a/intersection/IntrRay3Box3.cs b/intersection/IntrRay3Box3.cs index 22420b03..278a7f5f 100644 --- a/intersection/IntrRay3Box3.cs +++ b/intersection/IntrRay3Box3.cs @@ -74,69 +74,77 @@ public bool Find() public bool Test () { - Vector3d WdU = Vector3d.Zero; - Vector3d AWdU = Vector3d.Zero; - Vector3d DdU = Vector3d.Zero; - Vector3d ADdU = Vector3d.Zero; - Vector3d AWxDdU = Vector3d.Zero; - double RHS; - - Vector3d diff = ray.Origin - box.Center; - - WdU[0] = ray.Direction.Dot(box.AxisX); - AWdU[0] = Math.Abs(WdU[0]); - DdU[0] = diff.Dot(box.AxisX); - ADdU[0] = Math.Abs(DdU[0]); - if (ADdU[0] > box.Extent.x && DdU[0]*WdU[0] >= (double)0) - { - return false; - } + return Intersects(ref ray, ref box); + } - WdU[1] = ray.Direction.Dot(box.AxisY); - AWdU[1] = Math.Abs(WdU[1]); - DdU[1] = diff.Dot(box.AxisY); - ADdU[1] = Math.Abs(DdU[1]); - if (ADdU[1] > box.Extent.y && DdU[1]*WdU[1] >= (double)0) - { - return false; - } - WdU[2] = ray.Direction.Dot(box.AxisZ); - AWdU[2] = Math.Abs(WdU[2]); - DdU[2] = diff.Dot(box.AxisZ); - ADdU[2] = Math.Abs(DdU[2]); - if (ADdU[2] > box.Extent.z && DdU[2]*WdU[2] >= (double)0) - { - return false; - } - Vector3d WxD = ray.Direction.Cross(diff); + /// + /// test if ray intersects box. + /// expandExtents allows you to scale box for hit-testing purposes. + /// + public static bool Intersects(ref Ray3d ray, ref Box3d box, double expandExtents = 0) + { + Vector3d WdU = Vector3d.Zero; + Vector3d AWdU = Vector3d.Zero; + Vector3d DdU = Vector3d.Zero; + Vector3d ADdU = Vector3d.Zero; + Vector3d AWxDdU = Vector3d.Zero; + double RHS; - AWxDdU[0] = Math.Abs(WxD.Dot(box.AxisX)); - RHS = box.Extent.y*AWdU[2] + box.Extent.z*AWdU[1]; - if (AWxDdU[0] > RHS) - { - return false; - } + Vector3d diff = ray.Origin - box.Center; + Vector3d extent = box.Extent + expandExtents; - AWxDdU[1] = Math.Abs(WxD.Dot(box.AxisY)); - RHS = box.Extent.x*AWdU[2] + box.Extent.z*AWdU[0]; - if (AWxDdU[1] > RHS) - { - return false; - } + WdU[0] = ray.Direction.Dot(ref box.AxisX); + AWdU[0] = Math.Abs(WdU[0]); + DdU[0] = diff.Dot(ref box.AxisX); + ADdU[0] = Math.Abs(DdU[0]); + if (ADdU[0] > extent.x && DdU[0] * WdU[0] >= (double)0) { + return false; + } + + WdU[1] = ray.Direction.Dot(ref box.AxisY); + AWdU[1] = Math.Abs(WdU[1]); + DdU[1] = diff.Dot(ref box.AxisY); + ADdU[1] = Math.Abs(DdU[1]); + if (ADdU[1] > extent.y && DdU[1] * WdU[1] >= (double)0) { + return false; + } + + WdU[2] = ray.Direction.Dot(ref box.AxisZ); + AWdU[2] = Math.Abs(WdU[2]); + DdU[2] = diff.Dot(ref box.AxisZ); + ADdU[2] = Math.Abs(DdU[2]); + if (ADdU[2] > extent.z && DdU[2] * WdU[2] >= (double)0) { + return false; + } + + Vector3d WxD = ray.Direction.Cross(diff); + + AWxDdU[0] = Math.Abs(WxD.Dot(ref box.AxisX)); + RHS = extent.y * AWdU[2] + extent.z * AWdU[1]; + if (AWxDdU[0] > RHS) { + return false; + } + + AWxDdU[1] = Math.Abs(WxD.Dot(ref box.AxisY)); + RHS = extent.x * AWdU[2] + extent.z * AWdU[0]; + if (AWxDdU[1] > RHS) { + return false; + } + + AWxDdU[2] = Math.Abs(WxD.Dot(ref box.AxisZ)); + RHS = extent.x * AWdU[1] + extent.y * AWdU[0]; + if (AWxDdU[2] > RHS) { + return false; + } + + return true; + } - AWxDdU[2] = Math.Abs(WxD.Dot(box.AxisZ)); - RHS = box.Extent.x*AWdU[1] + box.Extent.y*AWdU[0]; - if (AWxDdU[2] > RHS) - { - return false; - } - return true; - } - } + } } From d197b3fbde373f79b6455467c243a674d5d5139d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:39:05 -0400 Subject: [PATCH 098/225] added OBB-tree for DCurve3. this is functional but has not been tested very much... --- spatial/DCurveBoxTree.cs | 316 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 spatial/DCurveBoxTree.cs diff --git a/spatial/DCurveBoxTree.cs b/spatial/DCurveBoxTree.cs new file mode 100644 index 00000000..2f8faff3 --- /dev/null +++ b/spatial/DCurveBoxTree.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace g3 +{ + + /// + /// tree of Oriented Boxes (OBB) for a DCurve3. + /// Construction is sequential, ie pairs of segments are merged into boxes, then pairs of boxes, and so on + /// + /// [TODO] is this the best strategy? is there maybe some kind of sorting/sweepline algo? + /// [TODO] would it make more sense to have more than just 2 segments at lowest level? + /// + /// + public class DCurve3BoxTree + { + public DCurve3 Curve; + + Box3d[] boxes; + int layers; + List layer_counts; + + public DCurve3BoxTree(DCurve3 curve) + { + if (curve.Closed == false) + throw new NotImplementedException("not done yet"); + + Curve = curve; + build_sequential(curve); + } + + + public double DistanceSquared(Vector3d pt) { + int iSeg; double segT; + double distSqr = SquaredDistance(pt, out iSeg, out segT); + return distSqr; + } + public double Distance(Vector3d pt) + { + int iSeg; double segT; + double distSqr = SquaredDistance(pt, out iSeg, out segT); + return Math.Sqrt(distSqr); + } + public Vector3d NearestPoint(Vector3d pt) + { + int iSeg; double segT; + SquaredDistance(pt, out iSeg, out segT); + return Curve.PointAt(iSeg, segT); + } + + + + public double SquaredDistance(Vector3d pt, out int iNearSeg, out double fNearSegT, double max_dist = double.MaxValue) + { + int iRoot = boxes.Length - 1; + int iLayer = layers - 1; + + double min_dist = max_dist; + iNearSeg = -1; + fNearSegT = 0; + + find_min_distance(ref pt, ref min_dist, ref iNearSeg, ref fNearSegT, 0, iRoot, iLayer); + if (iNearSeg == -1) + return double.MaxValue; + return min_dist; + } + + + + void find_min_distance(ref Vector3d pt, ref double min_dist, ref int min_dist_seg, ref double min_dist_segt, int bi, int iLayerStart, int iLayer) + { + // hit polygon layer, check segments + if (iLayer == 0) { + int seg_i = 2 * bi; + Segment3d seg_a = Curve.GetSegment(seg_i); + double segt; + double segdist = seg_a.DistanceSquared(pt, out segt); + if (segdist <= min_dist) { + min_dist = segdist; + min_dist_seg = seg_i; + min_dist_segt = segt; + } + if ((seg_i + 1) < Curve.VertexCount) { + Segment3d seg_b = Curve.GetSegment(seg_i + 1); + segdist = seg_b.DistanceSquared(pt, out segt); + if (segdist <= min_dist) { + min_dist = segdist; + min_dist_seg = seg_i + 1; + min_dist_segt = segt; + } + } + + return; + } + + // test both boxes and recurse + int prev_layer = iLayer - 1; + int prev_count = layer_counts[prev_layer]; + int prev_start = iLayerStart - prev_count; + int prev_a = prev_start + 2 * bi; + double dist = boxes[prev_a].DistanceSquared(pt); + if (dist <= min_dist) { + find_min_distance(ref pt, ref min_dist, ref min_dist_seg, ref min_dist_segt, 2 * bi, prev_start, prev_layer); + } + if ((2 * bi + 1) >= prev_count) + return; + int prev_b = prev_a + 1; + double dist2 = boxes[prev_b].DistanceSquared(pt); + if (dist2 <= min_dist) { + find_min_distance(ref pt, ref min_dist, ref min_dist_seg, ref min_dist_segt, 2 * bi + 1, prev_start, prev_layer); + } + } + + + + /// + /// Find min-distance between ray and curve. Pass max_dist if you only care about a certain distance + /// TODO: not 100% sure this is working properly... ? + /// + public double SquaredDistance(Ray3d ray, out int iNearSeg, out double fNearSegT, out double fRayT, double max_dist = double.MaxValue) + { + int iRoot = boxes.Length - 1; + int iLayer = layers - 1; + + double min_dist = max_dist; + iNearSeg = -1; + fNearSegT = 0; + fRayT = double.MaxValue; + + find_min_distance(ref ray, ref min_dist, ref iNearSeg, ref fNearSegT, ref fRayT, 0, iRoot, iLayer); + if (iNearSeg == -1) + return double.MaxValue; + return min_dist; + } + + void find_min_distance(ref Ray3d ray, ref double min_dist, ref int min_dist_seg, ref double min_dist_segt, ref double min_dist_rayt, int bi, int iLayerStart, int iLayer) + { + // hit polygon layer, check segments + if (iLayer == 0) { + int seg_i = 2 * bi; + Segment3d seg_a = Curve.GetSegment(seg_i); + double segt, rayt; + double segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_a, out rayt, out segt); + double segdist = Math.Sqrt(segdist_sqr); + if (segdist <= min_dist) { + min_dist = segdist; + min_dist_seg = seg_i; + min_dist_segt = segt; + min_dist_rayt = rayt; + } + if ((seg_i + 1) < Curve.VertexCount) { + Segment3d seg_b = Curve.GetSegment(seg_i + 1); + segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_b, out rayt, out segt); + segdist = Math.Sqrt(segdist_sqr); + if (segdist <= min_dist) { + min_dist = segdist; + min_dist_seg = seg_i + 1; + min_dist_segt = segt; + min_dist_rayt = rayt; + } + } + + return; + } + + // test both boxes and recurse + // TODO: verify that this intersection strategy makes sense? + int prev_layer = iLayer - 1; + int prev_count = layer_counts[prev_layer]; + int prev_start = iLayerStart - prev_count; + int prev_a = prev_start + 2 * bi; + bool intersects = IntrRay3Box3.Intersects(ref ray, ref boxes[prev_a], min_dist); + if (intersects) { + find_min_distance(ref ray, ref min_dist, ref min_dist_seg, ref min_dist_segt, ref min_dist_rayt, 2 * bi, prev_start, prev_layer); + } + if ((2 * bi + 1) >= prev_count) + return; + int prev_b = prev_a + 1; + bool intersects2 = IntrRay3Box3.Intersects(ref ray, ref boxes[prev_b], min_dist); + if (intersects2) { + find_min_distance(ref ray, ref min_dist, ref min_dist_seg, ref min_dist_segt, ref min_dist_rayt, 2 * bi + 1, prev_start, prev_layer); + } + } + + + + + + + + /// + /// Find min-distance between ray and curve. Pass max_dist if you only care about a certain distance + /// TODO: not 100% sure this is working properly... ? + /// + public bool FindClosestRayIntersction(Ray3d ray, double radius, out int hitSegment, out double fRayT) + { + int iRoot = boxes.Length - 1; + int iLayer = layers - 1; + + hitSegment = -1; + fRayT = double.MaxValue; + + find_closest_ray_intersction(ref ray, radius, ref hitSegment, ref fRayT, 0, iRoot, iLayer); + return (hitSegment != -1); + } + + void find_closest_ray_intersction(ref Ray3d ray, double radius, ref int nearestSegment, ref double nearest_ray_t, int bi, int iLayerStart, int iLayer) + { + // hit polygon layer, check segments + if (iLayer == 0) { + int seg_i = 2 * bi; + Segment3d seg_a = Curve.GetSegment(seg_i); + double segt, rayt; + double segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_a, out rayt, out segt); + if (segdist_sqr <= radius*radius && rayt < nearest_ray_t) { + nearestSegment = seg_i; + nearest_ray_t = rayt; + } + if ((seg_i + 1) < Curve.VertexCount) { + Segment3d seg_b = Curve.GetSegment(seg_i + 1); + segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_b, out rayt, out segt); + if (segdist_sqr <= radius * radius && rayt < nearest_ray_t) { + nearestSegment = seg_i+1; + nearest_ray_t = rayt; + } + } + + return; + } + + // test both boxes and recurse + // TODO: verify that this intersection strategy makes sense? + int prev_layer = iLayer - 1; + int prev_count = layer_counts[prev_layer]; + int prev_start = iLayerStart - prev_count; + int prev_a = prev_start + 2 * bi; + bool intersects = IntrRay3Box3.Intersects(ref ray, ref boxes[prev_a], radius); + if (intersects) { + find_closest_ray_intersction(ref ray, radius, ref nearestSegment, ref nearest_ray_t, 2 * bi, prev_start, prev_layer); + } + if ((2 * bi + 1) >= prev_count) + return; + int prev_b = prev_a + 1; + bool intersects2 = IntrRay3Box3.Intersects(ref ray, ref boxes[prev_b], radius); + if (intersects2) { + find_closest_ray_intersction(ref ray, radius, ref nearestSegment, ref nearest_ray_t, 2 * bi + 1, prev_start, prev_layer); + } + } + + + + + + + // build tree of boxes as sequential array + void build_sequential(DCurve3 curve) + { + int NV = curve.VertexCount; + int N = NV; + int boxCount = 0; + layers = 0; + layer_counts = new List(); + + // count how many boxes in each layer, building up from initial segments + int bi = 0; + while (N > 1) { + int layer_boxes = (N / 2) + (N % 2 == 0 ? 0 : 1); + boxCount += layer_boxes; + N = layer_boxes; + + layer_counts.Add(layer_boxes); + bi += layer_boxes; + layers++; + } + + + boxes = new Box3d[boxCount]; + bi = 0; + + // make first layer + for (int si = 0; si < NV; si += 2) { + Vector3d v1 = curve[(si + 1) % NV]; + Segment3d seg1 = new Segment3d(curve[si], v1); + Box3d box = new Box3d(seg1); + if (si < NV - 1) { + Segment3d seg2 = new Segment3d(v1, curve[(si + 2) % NV]); + Box3d box2 = new Box3d(seg2); + box = Box3d.Merge(ref box, ref box2); + } + boxes[bi++] = box; + } + + // repeatedly build layers until we hit a single box + N = bi; + int prev_layer_start = 0; + bool done = false; + while (done == false) { + int layer_start = bi; + + for (int k = 0; k < N; k += 2) { + Box3d mbox = Box3d.Merge(ref boxes[prev_layer_start + k], ref boxes[prev_layer_start + k + 1]); + boxes[bi++] = mbox; + } + + N = (N / 2) + (N % 2 == 0 ? 0 : 1); + prev_layer_start = layer_start; + if (N == 1) + done = true; + } + } + + + } +} From a06128b6fc61c82dfbd8a380e49caae6918d1cc7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 14:52:33 -0400 Subject: [PATCH 099/225] custom static variant of ray/tri intersection specifically for DMeshAABBtree, to avoid creating object on each test --- intersection/IntrRay3Triangle3.cs | 60 +++++++++++++++++++++++++++++++ math/Vector3d.cs | 19 +++++----- spatial/DMeshAABBTree.cs | 31 +++++++++++----- 3 files changed, 93 insertions(+), 17 deletions(-) diff --git a/intersection/IntrRay3Triangle3.cs b/intersection/IntrRay3Triangle3.cs index e303d5b7..611c907f 100644 --- a/intersection/IntrRay3Triangle3.cs +++ b/intersection/IntrRay3Triangle3.cs @@ -107,5 +107,65 @@ public bool Find() Result = IntersectionResult.NoIntersection; return false; } + + + + /// + /// minimal intersection test, computes ray-t + /// + public static bool Intersects(ref Ray3d ray, ref Vector3d V0, ref Vector3d V1, ref Vector3d V2, out double rayT) + { + // Compute the offset origin, edges, and normal. + Vector3d diff = ray.Origin - V0; + Vector3d edge1 = V1 - V0; + Vector3d edge2 = V2 - V0; + Vector3d normal = edge1.Cross(ref edge2); + + rayT = double.MaxValue; + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + double DdN = ray.Direction.Dot(ref normal); + double sign; + if (DdN > MathUtil.ZeroTolerance) { + sign = 1; + } else if (DdN < -MathUtil.ZeroTolerance) { + sign = -1; + DdN = -DdN; + } else { + // Ray and triangle are parallel, call it a "no intersection" + // even if the ray does intersect. + return false; + } + + Vector3d cross = diff.Cross(ref edge2); + double DdQxE2 = sign * ray.Direction.Dot(ref cross); + if (DdQxE2 >= 0) { + cross = edge1.Cross(ref diff); + double DdE1xQ = sign * ray.Direction.Dot(ref cross); + if (DdE1xQ >= 0) { + if (DdQxE2 + DdE1xQ <= DdN) { + // Line intersects triangle, check if ray does. + double QdN = -sign * diff.Dot(ref normal); + if (QdN >= 0) { + // Ray intersects triangle. + double inv = (1) / DdN; + rayT = QdN * inv; + return true; + } + // else: t < 0, no intersection + } + // else: b1+b2 > 1, no intersection + } + // else: b2 < 0, no intersection + } + // else: b1 < 0, no intersection + + return false; + } + } } diff --git a/math/Vector3d.cs b/math/Vector3d.cs index 6884a450..8b719467 100644 --- a/math/Vector3d.cs +++ b/math/Vector3d.cs @@ -127,21 +127,24 @@ public double Dot(ref Vector3d v2) { return x * v2.x + y * v2.y + z * v2.z; } - public static double Dot(Vector3d v1, Vector3d v2) - { - return v1.Dot(v2); + public static double Dot(Vector3d v1, Vector3d v2) { + return v1.Dot(ref v2); } - public Vector3d Cross(Vector3d v2) - { + public Vector3d Cross(Vector3d v2) { return new Vector3d( y * v2.z - z * v2.y, z * v2.x - x * v2.z, x * v2.y - y * v2.x); } - public static Vector3d Cross(Vector3d v1, Vector3d v2) - { - return v1.Cross(v2); + public Vector3d Cross(ref Vector3d v2) { + return new Vector3d( + y * v2.z - z * v2.y, + z * v2.x - x * v2.z, + x * v2.y - y * v2.x); + } + public static Vector3d Cross(Vector3d v1, Vector3d v2) { + return v1.Cross(ref v2); } public Vector3d UnitCross(Vector3d v2) diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 06c549f6..f59eceec 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -278,15 +278,21 @@ protected void find_hit_triangle(int iBox, ref Ray3d ray, ref double fNearestT, if (TriangleFilterF != null && TriangleFilterF(ti) == false) continue; - // [TODO] optimize this mesh.GetTriVertices(ti, ref tri.V0, ref tri.V1, ref tri.V2); - IntrRay3Triangle3 ray_tri_hit = new IntrRay3Triangle3(ray, tri); - if ( ray_tri_hit.Find() ) { - if ( ray_tri_hit.RayParameter < fNearestT ) { - fNearestT = ray_tri_hit.RayParameter; + double rayt; + if (IntrRay3Triangle3.Intersects(ref ray, ref tri.V0, ref tri.V1, ref tri.V2, out rayt)) { + if (rayt < fNearestT) { + fNearestT = rayt; tID = ti; } } + //IntrRay3Triangle3 ray_tri_hit = new IntrRay3Triangle3(ray, tri); + //if ( ray_tri_hit.Find() ) { + // if ( ray_tri_hit.RayParameter < fNearestT ) { + // fNearestT = ray_tri_hit.RayParameter; + // tID = ti; + // } + //} } } else { // internal node, either 1 or 2 child boxes @@ -365,16 +371,23 @@ protected int find_all_hit_triangles(int iBox, List hitTriangles, ref Ray3d if (TriangleFilterF != null && TriangleFilterF(ti) == false) continue; - // [TODO] optimize this mesh.GetTriVertices(ti, ref tri.V0, ref tri.V1, ref tri.V2); - IntrRay3Triangle3 ray_tri_hit = new IntrRay3Triangle3(ray, tri); - if (ray_tri_hit.Find()) { - if (ray_tri_hit.RayParameter < fMaxDist) { + double rayt; + if (IntrRay3Triangle3.Intersects(ref ray, ref tri.V0, ref tri.V1, ref tri.V2, out rayt)) { + if (rayt < fMaxDist) { if (hitTriangles != null) hitTriangles.Add(ti); hit_count++; } } + //IntrRay3Triangle3 ray_tri_hit = new IntrRay3Triangle3(ray, tri); + //if (ray_tri_hit.Find()) { + // if (ray_tri_hit.RayParameter < fMaxDist) { + // if (hitTriangles != null) + // hitTriangles.Add(ti); + // hit_count++; + // } + //} } } else { // internal node, either 1 or 2 child boxes From 2115de77bd2c4b38feb074f59f4ca45ae4a62ceb Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 15 Apr 2018 23:01:35 -0400 Subject: [PATCH 100/225] tri/tri intersection optimizations. note that this may change behavior of AABBTree.FindAllIntersections for co-planar triangles --- intersection/IntrTriangle3Triangle3.cs | 94 +++++++++++++++++++++++++- math/Vector3d.cs | 7 +- spatial/DMeshAABBTree.cs | 55 ++++++++------- 3 files changed, 123 insertions(+), 33 deletions(-) diff --git a/intersection/IntrTriangle3Triangle3.cs b/intersection/IntrTriangle3Triangle3.cs index 5a8ac887..1fda8145 100644 --- a/intersection/IntrTriangle3Triangle3.cs +++ b/intersection/IntrTriangle3Triangle3.cs @@ -5,7 +5,7 @@ namespace g3 { // ported from WildMagic5 IntrTriangle3Triangle3 // use Test() for fast boolean query, does not compute intersection info - // use Find() to for full information + // use Find() to compute full information // By default fully-contained co-planar triangles are not reported as intersecting. // set ReportCoplanarIntersection=true to handle this case (more expensive) public class IntrTriangle3Triangle3 @@ -242,12 +242,100 @@ public bool Test() + public static bool Intersects(ref Triangle3d triangle0, ref Triangle3d triangle1) + { + // Get edge vectors for triangle0. + Vector3dTuple3 E0; + E0.V0 = triangle0.V1 - triangle0.V0; + E0.V1 = triangle0.V2 - triangle0.V1; + E0.V2 = triangle0.V0 - triangle0.V2; + + // Get normal vector of triangle0. + Vector3d N0 = E0.V0.UnitCross(ref E0.V1); + + // Project triangle1 onto normal line of triangle0, test for separation. + double N0dT0V0 = N0.Dot(ref triangle0.V0); + double min1, max1; + ProjectOntoAxis(ref triangle1, ref N0, out min1, out max1); + if (N0dT0V0 < min1 || N0dT0V0 > max1) { + return false; + } + + // Get edge vectors for triangle1. + Vector3dTuple3 E1; + E1.V0 = triangle1.V1 - triangle1.V0; + E1.V1 = triangle1.V2 - triangle1.V1; + E1.V2 = triangle1.V0 - triangle1.V2; + + // Get normal vector of triangle1. + Vector3d N1 = E1.V0.UnitCross(ref E1.V1); + + Vector3d dir; + double min0, max0; + int i0, i1; + + Vector3d N0xN1 = N0.UnitCross(ref N1); + if (N0xN1.Dot(ref N0xN1) >= MathUtil.ZeroTolerance) { + // Triangles are not parallel. + + // Project triangle0 onto normal line of triangle1, test for + // separation. + double N1dT1V0 = N1.Dot(ref triangle1.V0); + ProjectOntoAxis(ref triangle0, ref N1, out min0, out max0); + if (N1dT1V0 < min0 || N1dT1V0 > max0) { + return false; + } + + // Directions E0[i0]xE1[i1]. + for (i1 = 0; i1 < 3; ++i1) { + for (i0 = 0; i0 < 3; ++i0) { + dir = E0[i0].UnitCross(E1[i1]); // could pass ref if we reversed these...need to negate? + ProjectOntoAxis(ref triangle0, ref dir, out min0, out max0); + ProjectOntoAxis(ref triangle1, ref dir, out min1, out max1); + if (max0 < min1 || max1 < min0) { + return false; + } + } + } + + } else { // Triangles are parallel (and, in fact, coplanar). + // Directions N0xE0[i0]. + for (i0 = 0; i0 < 3; ++i0) { + dir = N0.UnitCross(E0[i0]); + ProjectOntoAxis(ref triangle0, ref dir, out min0, out max0); + ProjectOntoAxis(ref triangle1, ref dir, out min1, out max1); + if (max0 < min1 || max1 < min0) { + return false; + } + } + + // Directions N1xE1[i1]. + for (i1 = 0; i1 < 3; ++i1) { + dir = N1.UnitCross(E1[i1]); + ProjectOntoAxis(ref triangle0, ref dir, out min0, out max0); + ProjectOntoAxis(ref triangle1, ref dir, out min1, out max1); + if (max0 < min1 || max1 < min0) { + return false; + } + } + } + + return true; + } + + + + + + + + - void ProjectOntoAxis ( ref Triangle3d triangle, ref Vector3d axis, out double fmin, out double fmax) + static public void ProjectOntoAxis ( ref Triangle3d triangle, ref Vector3d axis, out double fmin, out double fmax) { double dot0 = axis.Dot(triangle.V0); double dot1 = axis.Dot(triangle.V1); @@ -277,7 +365,7 @@ void ProjectOntoAxis ( ref Triangle3d triangle, ref Vector3d axis, out double fm - void TrianglePlaneRelations ( ref Triangle3d triangle, ref Plane3d plane, + static public void TrianglePlaneRelations ( ref Triangle3d triangle, ref Plane3d plane, out Vector3d distance, out Index3i sign, out int positive, out int negative, out int zero) { // Compute the signed distances of triangle vertices to the plane. Use diff --git a/math/Vector3d.cs b/math/Vector3d.cs index 8b719467..044ef93b 100644 --- a/math/Vector3d.cs +++ b/math/Vector3d.cs @@ -147,8 +147,7 @@ public static Vector3d Cross(Vector3d v1, Vector3d v2) { return v1.Cross(ref v2); } - public Vector3d UnitCross(Vector3d v2) - { + public Vector3d UnitCross(ref Vector3d v2) { Vector3d n = new Vector3d( y * v2.z - z * v2.y, z * v2.x - x * v2.z, @@ -156,6 +155,10 @@ public Vector3d UnitCross(Vector3d v2) n.Normalize(); return n; } + public Vector3d UnitCross(Vector3d v2) { + return UnitCross(ref v2); + } + public double AngleD(Vector3d v2) { diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index f59eceec..20107b4f 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -476,9 +476,7 @@ protected int find_any_intersection(int iBox, ref Triangle3d triangle, ref AxisA if (TriangleFilterF != null && TriangleFilterF(ti) == false) continue; mesh.GetTriVertices(ti, ref box_tri.V0, ref box_tri.V1, ref box_tri.V2); - - IntrTriangle3Triangle3 intr = new IntrTriangle3Triangle3(triangle, box_tri); - if (intr.Test()) + if ( IntrTriangle3Triangle3.Intersects(ref triangle, ref box_tri)) return ti; } } else { // internal node, either 1 or 2 child boxes @@ -535,7 +533,7 @@ protected bool find_any_intersection(int iBox, DMeshAABBTree3 otherTree, Func(); result.Segments = new List(); - find_intersections(root_index, otherTree, TransformF, otherTree.root_index, 0, result); + IntrTriangle3Triangle3 intr = new IntrTriangle3Triangle3(new Triangle3d(), new Triangle3d()); + find_intersections(root_index, otherTree, TransformF, otherTree.root_index, 0, intr, result); return result; } protected void find_intersections(int iBox, DMeshAABBTree3 otherTree, Func TransformF, - int oBox, int depth, IntersectionsQueryResult result) + int oBox, int depth, + IntrTriangle3Triangle3 intr, IntersectionsQueryResult result) { int idx = box_to_index[iBox]; int odx = otherTree.box_to_index[oBox]; @@ -684,9 +682,6 @@ protected void find_intersections(int iBox, DMeshAABBTree3 otherTree, Func Date: Mon, 16 Apr 2018 14:33:46 -0400 Subject: [PATCH 101/225] yikes quite the typo... --- mesh_ops/MeshInsertUVPolyCurve.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 5bc23a60..51f2a2c7 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -441,7 +441,8 @@ public virtual bool Apply() continue; } - spatial_remove_triangles(ev.a, ev.b); + Index2i et = Mesh.GetEdgeT(eid); + spatial_remove_triangles(et.a, et.b); // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; @@ -451,7 +452,7 @@ public virtual bool Apply() //return false; } - spatial_add_triangles(ev.a, ev.b); + spatial_add_triangles(et.a, et.b); spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); // move split point to intersection position From b7d373a02f3cf5ae4082baf3326d643f2615ad54 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 16 Apr 2018 15:13:39 -0400 Subject: [PATCH 102/225] quaterniond conversions --- math/Quaterniond.cs | 9 +++++++++ mesh/MeshTransforms.cs | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/math/Quaterniond.cs b/math/Quaterniond.cs index 205192f0..555d49cb 100644 --- a/math/Quaterniond.cs +++ b/math/Quaterniond.cs @@ -323,6 +323,15 @@ public bool EpsilonEqual(Quaterniond q2, double epsilon) { } + // [TODO] should we be normalizing in these casts?? + public static implicit operator Quaterniond(Quaternionf q) { + return new Quaterniond(q.x, q.y, q.z, q.w); + } + public static explicit operator Quaternionf(Quaterniond q) { + return new Quaternionf((float)q.x, (float)q.y, (float)q.z, (float)q.w); + } + + public override string ToString() { return string.Format("{0:F8} {1:F8} {2:F8} {3:F8}", x, y, z, w); } diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index caf92baa..a784c597 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -37,6 +37,12 @@ public static Frame3f Rotate(Frame3f f, Vector3d origin, Quaternionf rotation) f.Origin = (Vector3f)Rotate(f.Origin, origin, rotation); return f; } + public static Frame3f Rotate(Frame3f f, Vector3d origin, Quaterniond rotation) + { + f.Rotate((Quaternionf)rotation); + f.Origin = (Vector3f)Rotate(f.Origin, origin, rotation); + return f; + } public static void Rotate(IDeformableMesh mesh, Vector3d origin, Quaternionf rotation) { int NV = mesh.MaxVertexID; From 259ef01130331552dbd68b83ef410c24127c825e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 16 Apr 2018 16:26:22 -0400 Subject: [PATCH 103/225] yikes was updating spatial DS before points were moved! --- mesh_ops/MeshInsertUVPolyCurve.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 51f2a2c7..6314816f 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -275,11 +275,12 @@ int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, if (result != MeshResult.Ok) throw new Exception("MeshInsertUVPolyCurve.insert_corner_from_bary: face poke failed - " + result.ToString()); + SetPointF(pokeinfo.new_vid, vInsert); + spatial_add_triangle(tid); spatial_add_triangle(pokeinfo.new_t1); spatial_add_triangle(pokeinfo.new_t2); - SetPointF(pokeinfo.new_vid, vInsert); return pokeinfo.new_vid; } @@ -452,9 +453,6 @@ public virtual bool Apply() //return false; } - spatial_add_triangles(et.a, et.b); - spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); - // move split point to intersection position SetPointF(splitInfo.vNew, x); NewCutVertices.Add(splitInfo.vNew); @@ -462,6 +460,9 @@ public virtual bool Apply() NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); + spatial_add_triangles(et.a, et.b); + spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); + // some splits - but not all - result in new 'other' edges that are on // the polypath. We want to keep track of these edges so we can extract loop later. Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN); From c3cd9d5660dbb3fafc2f3ec3213e297d0186bde2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 16 Apr 2018 16:29:24 -0400 Subject: [PATCH 104/225] extended DMesh3.SplitEdge to take t parameter. Usin in MeshInsertUVPolyCurve to correctly update mesh attribs (uv, color, etc). I don't think we need to explicitly set-to-x here anymore, but leaving that in for now. added some error checks to MeshInsertPolygons --- mesh/DMesh3_edge_operators.cs | 24 +++++++++++++++--------- mesh_ops/MeshInsertPolygon.cs | 4 +++- mesh_ops/MeshInsertUVPolyCurve.cs | 3 ++- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index 8800fa0a..edea1ea8 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -274,7 +274,11 @@ public MeshResult SplitEdge(int vA, int vB, out EdgeSplitInfo split) } return SplitEdge(eid, out split); } - public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) + /// + /// Split edge eab. + /// split_t defines position along edge, and is assumed to be based on order of vertices returned by GetEdgeV() + /// + public MeshResult SplitEdge(int eab, out EdgeSplitInfo split, double split_t = 0.5) { split = new EdgeSplitInfo(); if (! IsEdge(eab) ) @@ -291,20 +295,22 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) int c = IndexUtil.orient_tri_edge_and_find_other_vtx(ref a, ref b, T0tv_array); if (vertices_refcount.rawRefCount(c) > 32764) return MeshResult.Failed_HitValenceLimit; + if (a != edges[eab_i]) + split_t = 1.0 - split_t; // if we flipped a/b order we need to reverse t // quite a bit of code is duplicated between boundary and non-boundary case, but it // is too hard to follow later if we factor it out... if ( IsBoundaryEdge(eab) ) { // create new vertex - Vector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b)); + Vector3d vNew = Vector3d.Lerp(GetVertex(a), GetVertex(b), split_t); int f = AppendVertex(vNew); if (HasVertexNormals) - SetVertexNormal(f, (GetVertexNormal(a) + GetVertexNormal(b)).Normalized); + SetVertexNormal(f, Vector3f.Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized); if (HasVertexColors) - SetVertexColor(f, 0.5f * (GetVertexColor(a) + GetVertexColor(b))); + SetVertexColor(f, Colorf.Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); if (HasVertexUVs) - SetVertexUV(f, 0.5f * (GetVertexUV(a) + GetVertexUV(b))); + SetVertexUV(f, Vector2f.Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); // look up edge bc, which needs to be modified Index3i T0te = GetTriEdges(t0); @@ -359,14 +365,14 @@ public MeshResult SplitEdge(int eab, out EdgeSplitInfo split) return MeshResult.Failed_HitValenceLimit; // create new vertex - Vector3d vNew = 0.5 * (GetVertex(a) + GetVertex(b)); + Vector3d vNew = Vector3d.Lerp(GetVertex(a), GetVertex(b), split_t); int f = AppendVertex(vNew); if (HasVertexNormals) - SetVertexNormal(f, (GetVertexNormal(a) + GetVertexNormal(b)).Normalized); + SetVertexNormal(f, Vector3f.Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized); if (HasVertexColors) - SetVertexColor(f, 0.5f * (GetVertexColor(a) + GetVertexColor(b))); + SetVertexColor(f, Colorf.Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t)); if (HasVertexUVs) - SetVertexUV(f, 0.5f * (GetVertexUV(a) + GetVertexUV(b))); + SetVertexUV(f, Vector2f.Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t)); // look up edges that we are going to need to update // [TODO OPT] could use ordering to reduce # of compares here diff --git a/mesh_ops/MeshInsertPolygon.cs b/mesh_ops/MeshInsertPolygon.cs index 1dbe4fa6..65830167 100644 --- a/mesh_ops/MeshInsertPolygon.cs +++ b/mesh_ops/MeshInsertPolygon.cs @@ -26,7 +26,9 @@ public bool Insert() { OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer); Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok); - OuterInsert.Apply(); + bool outerApplyOK = OuterInsert.Apply(); + if (outerApplyOK == false || OuterInsert.Loops.Count == 0) + return false; if (SimplifyInsertion) OuterInsert.Simplify(); diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index 6314816f..eace99bb 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -432,6 +432,7 @@ public virtual bool Apply() continue; // no intersection } Vector2d x = intr.Point0; + double t = Math.Sqrt(x.DistanceSquared(va) / va.DistanceSquared(vb)); // this case happens if we aren't "on-segment" but after we do the test the intersection pt // is within epsilon of one end of the edge. This is a spurious t-intersection and we @@ -447,7 +448,7 @@ public virtual bool Apply() // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; - MeshResult result = Mesh.SplitEdge(eid, out splitInfo); + MeshResult result = Mesh.SplitEdge(eid, out splitInfo, t); if (result != MeshResult.Ok) { throw new Exception("MeshInsertUVSegment.Apply: SplitEdge failed - " + result.ToString()); //return false; From b58f8ad853690972f837d21fb4b8377ae1d348bb Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 18 Apr 2018 14:29:38 -0400 Subject: [PATCH 105/225] bugfix for DCurve3, supported open curves in DCurveBoxTree, added to csproj --- curve/DCurve3.cs | 8 ++++---- geometry3Sharp.csproj | 1 + spatial/DCurveBoxTree.cs | 24 +++++++++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index 4378a8e5..3f0f2b6b 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -156,13 +156,13 @@ public Segment3d GetSegment(int iSegment) public IEnumerable SegmentItr() { if (Closed) { + int NV = vertices.Count; + for (int i = 0; i < NV; ++i) + yield return new Segment3d(vertices[i], vertices[(i + 1)%NV]); + } else { int NV = vertices.Count - 1; for (int i = 0; i < NV; ++i) yield return new Segment3d(vertices[i], vertices[i + 1]); - } else { - int NV = vertices.Count; - for (int i = 0; i < vertices.Count; ++i) - yield return new Segment3d(vertices[i], vertices[(i + 1)%NV]); } } diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index eaf4a697..17d9ff96 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -292,6 +292,7 @@ + diff --git a/spatial/DCurveBoxTree.cs b/spatial/DCurveBoxTree.cs index 2f8faff3..0c231788 100644 --- a/spatial/DCurveBoxTree.cs +++ b/spatial/DCurveBoxTree.cs @@ -24,9 +24,6 @@ public class DCurve3BoxTree public DCurve3BoxTree(DCurve3 curve) { - if (curve.Closed == false) - throw new NotImplementedException("not done yet"); - Curve = curve; build_sequential(curve); } @@ -82,7 +79,7 @@ void find_min_distance(ref Vector3d pt, ref double min_dist, ref int min_dist_se min_dist_seg = seg_i; min_dist_segt = segt; } - if ((seg_i + 1) < Curve.VertexCount) { + if ( (seg_i+1) < Curve.SegmentCount ) { Segment3d seg_b = Curve.GetSegment(seg_i + 1); segdist = seg_b.DistanceSquared(pt, out segt); if (segdist <= min_dist) { @@ -150,7 +147,7 @@ void find_min_distance(ref Ray3d ray, ref double min_dist, ref int min_dist_seg, min_dist_segt = segt; min_dist_rayt = rayt; } - if ((seg_i + 1) < Curve.VertexCount) { + if ((seg_i + 1) < Curve.SegmentCount) { Segment3d seg_b = Curve.GetSegment(seg_i + 1); segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_b, out rayt, out segt); segdist = Math.Sqrt(segdist_sqr); @@ -218,7 +215,7 @@ void find_closest_ray_intersction(ref Ray3d ray, double radius, ref int nearestS nearestSegment = seg_i; nearest_ray_t = rayt; } - if ((seg_i + 1) < Curve.VertexCount) { + if ((seg_i + 1) < Curve.SegmentCount) { Segment3d seg_b = Curve.GetSegment(seg_i + 1); segdist_sqr = DistRay3Segment3.SquaredDistance(ref ray, ref seg_b, out rayt, out segt); if (segdist_sqr <= radius * radius && rayt < nearest_ray_t) { @@ -258,7 +255,7 @@ void find_closest_ray_intersction(ref Ray3d ray, double radius, ref int nearestS void build_sequential(DCurve3 curve) { int NV = curve.VertexCount; - int N = NV; + int N = (curve.Closed) ? NV : NV - 1; int boxCount = 0; layers = 0; layer_counts = new List(); @@ -274,13 +271,20 @@ void build_sequential(DCurve3 curve) bi += layer_boxes; layers++; } - + // [RMS] this case happens if N = 1, previous loop is skipped and we have to + // hardcode initialization to this redundant box + if ( layers == 0 ) { + layers = 1; + boxCount = 1; + layer_counts = new List() { 1 }; + } boxes = new Box3d[boxCount]; bi = 0; // make first layer - for (int si = 0; si < NV; si += 2) { + int NStop = (curve.Closed) ? NV : NV - 1; + for (int si = 0; si < NStop; si += 2) { Vector3d v1 = curve[(si + 1) % NV]; Segment3d seg1 = new Segment3d(curve[si], v1); Box3d box = new Box3d(seg1); @@ -294,6 +298,8 @@ void build_sequential(DCurve3 curve) // repeatedly build layers until we hit a single box N = bi; + if (N == 1) + return; int prev_layer_start = 0; bool done = false; while (done == false) { From 8fbed7f8b995119702d091516defa08838cccf40 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 18 Apr 2018 14:30:36 -0400 Subject: [PATCH 106/225] utility bitses --- math/AxisAlignedBox3i.cs | 21 +++++++++++++++++++++ math/IndexUtil.cs | 16 ++++++++++++++++ math/Vector2i.cs | 1 + math/Vector3i.cs | 2 ++ mesh/MeshEditor.cs | 6 ++++++ 5 files changed, 46 insertions(+) diff --git a/math/AxisAlignedBox3i.cs b/math/AxisAlignedBox3i.cs index d60cb05a..16ee2656 100644 --- a/math/AxisAlignedBox3i.cs +++ b/math/AxisAlignedBox3i.cs @@ -246,6 +246,27 @@ public Vector3i NearestPoint(Vector3i v) } + /// + /// Clamp v to grid bounds [min, max] + /// + public Vector3i ClampInclusive(Vector3i v) { + return new Vector3i( + MathUtil.Clamp(v.x, Min.x, Max.x), + MathUtil.Clamp(v.y, Min.y, Max.y), + MathUtil.Clamp(v.z, Min.z, Max.z)); + } + + /// + /// clamp v to grid bounds [min,max) + /// + public Vector3i ClampExclusive(Vector3i v) { + return new Vector3i( + MathUtil.Clamp(v.x, Min.x, Max.x-1), + MathUtil.Clamp(v.y, Min.y, Max.y-1), + MathUtil.Clamp(v.z, Min.z, Max.z-1)); + } + + //! relative translation public void Translate(Vector3i vTranslate) diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index 8679afaa..62269e54 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -383,6 +383,22 @@ public static void EdgesToVertices(DMesh3 mesh, HashSet edges, HashSet public static class gIndices { + // integer indices offsets in x/y directions + public static readonly Vector2i[] GridOffsets4 = new Vector2i[] { + new Vector2i( -1, 0), new Vector2i( 1, 0), + new Vector2i( 0, -1), new Vector2i( 0, 1) + }; + + // integer indices offsets in x/y directions and diagonals + public static readonly Vector2i[] GridOffsets8 = new Vector2i[] { + new Vector2i( -1, 0), new Vector2i( 1, 0), + new Vector2i( 0, -1), new Vector2i( 0, 1), + new Vector2i( -1, 1), new Vector2i( 1, 1), + new Vector2i( -1, -1), new Vector2i( 1, -1) + }; + + + // Corner vertices of box faces - see Box.Corner for points associated w/ indexing // Note that public static readonly int[,] BoxFaces = new int[6, 4] { diff --git a/math/Vector2i.cs b/math/Vector2i.cs index 5fc0e83c..72d22f30 100644 --- a/math/Vector2i.cs +++ b/math/Vector2i.cs @@ -30,6 +30,7 @@ public int[] array public void Add(int s) { x += s; y += s; } + public int LengthSquared { get { return x * x + y * y; } } public static Vector2i operator -(Vector2i v) diff --git a/math/Vector3i.cs b/math/Vector3i.cs index 0d605586..26e4f6b5 100644 --- a/math/Vector3i.cs +++ b/math/Vector3i.cs @@ -57,6 +57,8 @@ public void Subtract(Vector3i o) public void Add(int s) { x += s; y += s; z += s; } + public int LengthSquared { get { return x * x + y * y + z * z; } } + public static Vector3i operator -(Vector3i v) { diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 3bdc43f8..117d744b 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -758,6 +758,12 @@ public void AppendBox(Frame3f frame, Vector3f size) boxgen.MakeMesh(mesh); AppendMesh(mesh, Mesh.AllocateTriangleGroup()); } + public void AppendLine(Segment3d seg, float size) + { + Frame3f f = new Frame3f(seg.Center); + f.AlignAxis(2, (Vector3f)seg.Direction); + AppendBox(f, new Vector3f(size, size, seg.Extent)); + } public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) { MeshEditor editor = new MeshEditor(mesh); From 874e061092d36a2a123e80056368b13bcad6f99d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 18 Apr 2018 14:32:00 -0400 Subject: [PATCH 107/225] dijkstra mode that terminates when an indicator function returns true for current node (probably could use to implement existing ComputeToNode...) --- core/DijkstraGraphDistance.cs | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/core/DijkstraGraphDistance.cs b/core/DijkstraGraphDistance.cs index 6e1f20c1..f802a1e3 100644 --- a/core/DijkstraGraphDistance.cs +++ b/core/DijkstraGraphDistance.cs @@ -268,6 +268,7 @@ protected void ComputeToMaxDistance_Dense(float fMaxDistance) /// /// Compute distances until node_id is frozen, or (optional) max distance is reached /// Terminates early, so Queue may not be empty + /// [TODO] can reimplement this w/ internal call to ComputeToNode(func) ? /// public void ComputeToNode(int node_id, float fMaxDistance = float.MaxValue) { @@ -317,6 +318,66 @@ protected void ComputeToNode_Dense(int node_id, float fMaxDistance) + + + + /// + /// Compute distances until node_id is frozen, or (optional) max distance is reached + /// Terminates early, so Queue may not be empty + /// + public int ComputeToNode(Func terminatingNodeF, float fMaxDistance = float.MaxValue) + { + if (TrackOrder == true) + order = new List(); + + if (SparseNodes != null) + return ComputeToNode_Sparse(terminatingNodeF, fMaxDistance); + else + return ComputeToNode_Dense(terminatingNodeF, fMaxDistance); + } + protected int ComputeToNode_Sparse(Func terminatingNodeF, float fMaxDistance) + { + while (SparseQueue.Count > 0) { + GraphNode g = SparseQueue.Dequeue(); + max_value = Math.Max(g.priority, max_value); + if (max_value > fMaxDistance) + return -1; + g.frozen = true; + if (TrackOrder) + order.Add(g.id); + if (terminatingNodeF(g.id)) + return g.id; + update_neighbours_sparse(g); + } + return -1; + } + protected int ComputeToNode_Dense(Func terminatingNodeF, float fMaxDistance) + { + while (DenseQueue.Count > 0) { + float idx_priority = DenseQueue.FirstPriority; + max_value = Math.Max(idx_priority, max_value); + if (max_value > fMaxDistance) + return -1; + int idx = DenseQueue.Dequeue(); + GraphNodeStruct g = DenseNodes[idx]; + g.frozen = true; + if (TrackOrder) + order.Add(g.id); + g.distance = max_value; + DenseNodes[idx] = g; + if (terminatingNodeF(g.id)) + return g.id; + update_neighbours_dense(g.id); + } + return -1; + } + + + + + + + /// /// Get the maximum distance encountered during the Compute() /// From 0065c611a17d0b62116e13daeb146b587307d0e2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 18 Apr 2018 14:35:36 -0400 Subject: [PATCH 108/225] replace float.MaxValue usage w/ a constant. Don't update neighbour if distance is that constant. --- core/DijkstraGraphDistance.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/DijkstraGraphDistance.cs b/core/DijkstraGraphDistance.cs index f802a1e3..09b56243 100644 --- a/core/DijkstraGraphDistance.cs +++ b/core/DijkstraGraphDistance.cs @@ -19,6 +19,7 @@ namespace g3 /// public class DijkstraGraphDistance { + public const float InvalidValue = float.MaxValue; /// /// if you enable this, then you can call GetOrder() @@ -59,7 +60,7 @@ public bool Equals(GraphNodeStruct other) { return id == other.id; } - public static readonly GraphNodeStruct Zero = new GraphNodeStruct() { id = -1, parent = -1, distance = float.MaxValue, frozen = false }; + public static readonly GraphNodeStruct Zero = new GraphNodeStruct() { id = -1, parent = -1, distance = InvalidValue, frozen = false }; } @@ -270,7 +271,7 @@ protected void ComputeToMaxDistance_Dense(float fMaxDistance) /// Terminates early, so Queue may not be empty /// [TODO] can reimplement this w/ internal call to ComputeToNode(func) ? /// - public void ComputeToNode(int node_id, float fMaxDistance = float.MaxValue) + public void ComputeToNode(int node_id, float fMaxDistance = InvalidValue) { if (TrackOrder == true) order = new List(); @@ -325,7 +326,7 @@ protected void ComputeToNode_Dense(int node_id, float fMaxDistance) /// Compute distances until node_id is frozen, or (optional) max distance is reached /// Terminates early, so Queue may not be empty /// - public int ComputeToNode(Func terminatingNodeF, float fMaxDistance = float.MaxValue) + public int ComputeToNode(Func terminatingNodeF, float fMaxDistance = InvalidValue) { if (TrackOrder == true) order = new List(); @@ -387,18 +388,18 @@ public float MaxDistance { /// - /// Get the computed distance at node id. returns float.MaxValue if node was not computed. + /// Get the computed distance at node id. returns InvalidValue if node was not computed. /// public float GetDistance(int id) { if (SparseNodes != null) { GraphNode g = SparseNodes[id]; if (g == null) - return float.MaxValue; + return InvalidValue; return g.priority; } else { GraphNodeStruct g = DenseNodes[id]; - return (g.frozen) ? g.distance : float.MaxValue; + return (g.frozen) ? g.distance : InvalidValue; } } @@ -480,6 +481,9 @@ void update_neighbours_sparse(GraphNode parent) continue; float nbr_dist = NodeDistanceF(parent.id, nbr_id) + cur_dist; + if (nbr_dist == InvalidValue) + continue; + if (SparseQueue.Contains(nbr)) { if (nbr_dist < nbr.priority) { nbr.parent = parent; @@ -515,6 +519,9 @@ void update_neighbours_dense(int parent_id) continue; float nbr_dist = NodeDistanceF(parent_id, nbr_id) + cur_dist; + if (nbr_dist == InvalidValue) + continue; + if (DenseQueue.Contains(nbr_id)) { if (nbr_dist < nbr.distance) { nbr.parent = parent_id; From 7ae54a53d92cadaa927795645c71794c4db8657c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 18 Apr 2018 14:36:52 -0400 Subject: [PATCH 109/225] add vector3i accessor, expose some of the internal compute functions so we can use in derivations of this class --- spatial/MeshSignedDistanceGrid.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 8c1dad64..5adc416a 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -166,6 +166,9 @@ public DenseGrid3i IntersectionsGrid { public float this[int i, int j, int k] { get { return grid[i, j, k]; } } + public float this[Vector3i idx] { + get { return grid[idx.x, idx.y, idx.z]; } + } public Vector3f CellCenter(int i, int j, int k) { @@ -576,7 +579,7 @@ void compute_signs(int ni, int nj, int nk, DenseGrid3f distances, DenseGrid3i in // find distance x0 is from segment x1-x2 - static float point_segment_distance(ref Vector3f x0, ref Vector3f x1, ref Vector3f x2) + static public float point_segment_distance(ref Vector3f x0, ref Vector3f x1, ref Vector3f x2) { Vector3f dx = x2 - x1; float m2 = dx.LengthSquared; @@ -593,7 +596,7 @@ static float point_segment_distance(ref Vector3f x0, ref Vector3f x1, ref Vector // find distance x0 is from segment x1-x2 - static double point_segment_distance(ref Vector3d x0, ref Vector3d x1, ref Vector3d x2) + static public double point_segment_distance(ref Vector3d x0, ref Vector3d x1, ref Vector3d x2) { Vector3d dx = x2 - x1; double m2 = dx.LengthSquared; @@ -611,7 +614,7 @@ static double point_segment_distance(ref Vector3d x0, ref Vector3d x1, ref Vecto // find distance x0 is from triangle x1-x2-x3 - static float point_triangle_distance(ref Vector3f x0, ref Vector3f x1, ref Vector3f x2, ref Vector3f x3) + static public float point_triangle_distance(ref Vector3f x0, ref Vector3f x1, ref Vector3f x2, ref Vector3f x3) { // first find barycentric coordinates of closest point on infinite plane Vector3f x13 = (x1 - x3); @@ -638,7 +641,7 @@ static float point_triangle_distance(ref Vector3f x0, ref Vector3f x1, ref Vecto // find distance x0 is from triangle x1-x2-x3 - static double point_triangle_distance(ref Vector3d x0, ref Vector3d x1, ref Vector3d x2, ref Vector3d x3) + static public double point_triangle_distance(ref Vector3d x0, ref Vector3d x1, ref Vector3d x2, ref Vector3d x3) { // first find barycentric coordinates of closest point on infinite plane Vector3d x13 = (x1 - x3); @@ -668,7 +671,7 @@ static double point_triangle_distance(ref Vector3d x0, ref Vector3d x1, ref Vect // calculate twice signed area of triangle (0,0)-(x1,y1)-(x2,y2) // return an SOS-determined sign (-1, +1, or 0 only if it's a truly degenerate triangle) - static int orientation(double x1, double y1, double x2, double y2, out double twice_signed_area) + static public int orientation(double x1, double y1, double x2, double y2, out double twice_signed_area) { twice_signed_area = y1 * x2 - x1 * y2; if (twice_signed_area > 0) return 1; @@ -683,7 +686,7 @@ static int orientation(double x1, double y1, double x2, double y2, out double tw // robust test of (x0,y0) in the triangle (x1,y1)-(x2,y2)-(x3,y3) // if true is returned, the barycentric coordinates are set in a,b,c. - static bool point_in_triangle_2d(double x0, double y0, + static public bool point_in_triangle_2d(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, out double a, out double b, out double c) { From 1da428422adfa1ef0c038c83b14076da7688135d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 19 Apr 2018 20:20:16 -0400 Subject: [PATCH 110/225] integer tag utility struct --- core/TagSet.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/core/TagSet.cs b/core/TagSet.cs index 6dda9a22..a9d3b38e 100644 --- a/core/TagSet.cs +++ b/core/TagSet.cs @@ -43,6 +43,27 @@ public int Get(T reference) } + /// + /// integer type/value pair, packed into 32 bits - 8 for type, 24 for value + /// + public struct IntTagPair + { + public byte type; + public int value; + public IntTagPair(byte type, int value) { + Util.gDevAssert(value < 1 << 24); + this.type = type; + this.value = value; + } + public IntTagPair(int combined) + { + type = (byte)(combined >> 24); + value = combined & 0xFFFFFF; + } + public int intValue { get { return ((int)type) << 24 | value; } } + } + + /// From bd31d7dac2db8b09423c06aae51748a17556dc9f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 20 Apr 2018 11:08:32 -0400 Subject: [PATCH 111/225] polygon utilities --- curve/CurveUtils2.cs | 33 +++++++++++++++++++++++++++++++++ curve/GeneralPolygon2d.cs | 15 ++++++++++----- curve/Polygon2d.cs | 3 +++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/curve/CurveUtils2.cs b/curve/CurveUtils2.cs index 7c32733a..a3abec8c 100644 --- a/curve/CurveUtils2.cs +++ b/curve/CurveUtils2.cs @@ -215,5 +215,38 @@ public static void LaplacianSmoothConstrained(GeneralPolygon2d solid, double alp + + /// + /// Remove polygons and polygon-holes smaller than minArea + /// + public static List FilterDegenerate(List polygons, double minArea) + { + List result = new List(polygons.Count); + List filteredHoles = new List(); + foreach (var poly in polygons) { + if (poly.Outer.Area < minArea) + continue; + if (poly.Holes.Count == 0) { + result.Add(poly); + continue; + } + filteredHoles.Clear(); + for ( int i = 0; i < poly.Holes.Count; ++i ) { + Polygon2d hole = poly.Holes[i]; + if (hole.Area > minArea) + filteredHoles.Add(hole); + } + if ( filteredHoles.Count != poly.Holes.Count ) { + poly.ClearHoles(); + foreach (var h in filteredHoles) + poly.AddHole(h, false, false); + } + result.Add(poly); + } + return result; + } + + + } } diff --git a/curve/GeneralPolygon2d.cs b/curve/GeneralPolygon2d.cs index abca2fd4..e53c40a1 100644 --- a/curve/GeneralPolygon2d.cs +++ b/curve/GeneralPolygon2d.cs @@ -41,10 +41,10 @@ public Polygon2d Outer { } - public void AddHole(Polygon2d hole, bool bCheck = true) { + public void AddHole(Polygon2d hole, bool bCheckContainment = true, bool bCheckOrientation = true) { if ( outer == null ) throw new Exception("GeneralPolygon2d.AddHole: outer polygon not set!"); - if ( bCheck ) { + if ( bCheckContainment ) { if ( outer.Contains(hole) == false ) throw new Exception("GeneralPolygon2d.AddHole: outer does not contain hole!"); @@ -54,13 +54,18 @@ public void AddHole(Polygon2d hole, bool bCheck = true) { throw new Exception("GeneralPolygon2D.AddHole: new hole intersects existing hole!"); } } - - if ( (bOuterIsCW && hole.IsClockwise) || (bOuterIsCW == false && hole.IsClockwise == false) ) - throw new Exception("GeneralPolygon2D.AddHole: new hole has same orientation as outer polygon!"); + if ( bCheckOrientation ) { + if ((bOuterIsCW && hole.IsClockwise) || (bOuterIsCW == false && hole.IsClockwise == false)) + throw new Exception("GeneralPolygon2D.AddHole: new hole has same orientation as outer polygon!"); + } holes.Add(hole); } + public void ClearHoles() { + holes.Clear(); + } + bool HasHoles { get { return holes.Count > 0; } diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 2056bc90..f938c31b 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -218,6 +218,9 @@ public double SignedArea { return fArea * 0.5; } } + public double Area { + get { return Math.Abs(SignedArea); } + } From f597e821da1b1dc7b9c3d649cfef9312a412d3a8 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 22 Apr 2018 14:05:48 -0400 Subject: [PATCH 112/225] added 2D dense float/int grids, extended 2D and 3D grids --- geometry3Sharp.csproj | 1 + math/IndexUtil.cs | 19 +++ spatial/DenseGrid2.cs | 280 ++++++++++++++++++++++++++++++++++++++++++ spatial/DenseGrid3.cs | 195 ++++++++++++++++++++++++++++- 4 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 spatial/DenseGrid2.cs diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 17d9ff96..0d6c9673 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -294,6 +294,7 @@ + diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs index 62269e54..8891eb2d 100644 --- a/math/IndexUtil.cs +++ b/math/IndexUtil.cs @@ -268,6 +268,25 @@ public static void sort_indices(ref Index3i tri) + public static Vector3i ToGrid3Index(int idx, int nx, int ny) + { + int x = idx % nx; + int y = (idx / nx) % ny; + int z = idx / (nx * ny); + return new Vector3i(x, y, z); + } + + public static int ToGrid3Linear(int i, int j, int k, int nx, int ny) { + return i + nx * (j + ny * k); + } + public static int ToGrid3Linear(Vector3i ijk, int nx, int ny) { + return ijk.x + nx * (ijk.y + ny * ijk.z); + } + public static int ToGrid3Linear(ref Vector3i ijk, int nx, int ny) { + return ijk.x + nx * (ijk.y + ny * ijk.z); + } + + /// /// Filter out invalid entries in indices[] list. Will return indices itself if diff --git a/spatial/DenseGrid2.cs b/spatial/DenseGrid2.cs new file mode 100644 index 00000000..84eefdc1 --- /dev/null +++ b/spatial/DenseGrid2.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + + +namespace g3 +{ + + /// + /// 2D dense grid of floating-point scalar values. + /// + public class DenseGrid2f + { + public float[] Buffer; + public int ni, nj; + + public DenseGrid2f() + { + ni = nj = 0; + } + + public DenseGrid2f(int ni, int nj, float initialValue) + { + resize(ni, nj); + assign(initialValue); + } + + public DenseGrid2f(DenseGrid2f copy) + { + Buffer = new float[copy.Buffer.Length]; + Array.Copy(copy.Buffer, Buffer, Buffer.Length); + ni = copy.ni; nj = copy.nj; + } + + public void swap(DenseGrid2f g2) + { + Util.gDevAssert(ni == g2.ni && nj == g2.nj); + var tmp = g2.Buffer; + g2.Buffer = this.Buffer; + this.Buffer = tmp; + } + + public int size { get { return ni * nj; } } + + public void resize(int ni, int nj) + { + Buffer = new float[ni * nj]; + this.ni = ni; this.nj = nj; + } + + public void assign(float value) + { + for (int i = 0; i < Buffer.Length; ++i) + Buffer[i] = value; + } + + public void assign_border(float value, int rings) + { + for ( int j = 0; j < rings; ++j ) { + int jb = nj - 1 - j; + for ( int i = 0; i < ni; ++i ) { + Buffer[i + ni * j] = value; + Buffer[i + ni * jb] = value; + } + } + int stop = nj - 1 - rings; + for ( int j = rings; j < stop; ++j ) { + for ( int i = 0; i < rings; ++i ) { + Buffer[i + ni * j] = value; + Buffer[(ni - 1 - i) + ni * j] = value; + } + } + } + + + public void clear() { + Array.Clear(Buffer, 0, Buffer.Length); + } + + public void copy(DenseGrid2f copy) + { + Array.Copy(copy.Buffer, this.Buffer, this.Buffer.Length); + } + + public float this[int i] { + get { return Buffer[i]; } + set { Buffer[i] = value; } + } + + public float this[int i, int j] { + get { return Buffer[i + ni * j]; } + set { Buffer[i + ni * j] = value; } + } + + public float this[Vector2i ijk] { + get { return Buffer[ijk.x + ni * ijk.y]; } + set { Buffer[ijk.x + ni * ijk.y] = value; } + } + + public void get_x_pair(int i0, int j, out double a, out double b) + { + int offset = ni * j; + a = Buffer[offset + i0]; + b = Buffer[offset + i0 + 1]; + } + + public void apply(Func f) + { + for (int j = 0; j < nj; j++ ) { + for ( int i = 0; i < ni; i++ ) { + int idx = i + ni * j; + Buffer[idx] = f(Buffer[idx]); + } + } + } + + public void set_min(DenseGrid2f grid2) + { + for (int k = 0; k < Buffer.Length; ++k) + Buffer[k] = Math.Min(Buffer[k], grid2.Buffer[k]); + } + public void set_max(DenseGrid2f grid2) + { + for (int k = 0; k < Buffer.Length; ++k) + Buffer[k] = Math.Max(Buffer[k], grid2.Buffer[k]); + } + + public AxisAlignedBox2i Bounds { + get { return new AxisAlignedBox2i(0, 0, ni, nj); } + } + + + public IEnumerable Indices() + { + for (int y = 0; y < nj; ++y) { + for (int x = 0; x < ni; ++x) + yield return new Vector2i(x, y); + } + } + + + public IEnumerable InsetIndices(int border_width) + { + int stopy = nj - border_width, stopx = ni - border_width; + for (int y = border_width; y < stopy; ++y) { + for (int x = border_width; x < stopx; ++x) + yield return new Vector2i(x, y); + } + } + + + } + + + + + + + /// + /// 2D dense grid of integers. + /// + public class DenseGrid2i + { + public int[] Buffer; + public int ni, nj; + + public DenseGrid2i() + { + ni = nj = 0; + } + + public DenseGrid2i(int ni, int nj, int initialValue) + { + resize(ni, nj); + assign(initialValue); + } + + public DenseGrid2i(DenseGrid2i copy) + { + resize(copy.ni, copy.nj); + Array.Copy(copy.Buffer, this.Buffer, this.Buffer.Length); + } + + public int size { get { return ni * nj; } } + + public void resize(int ni, int nj) + { + Buffer = new int[ni * nj]; + this.ni = ni; this.nj = nj; + } + + public void clear() { + Array.Clear(Buffer, 0, Buffer.Length); + } + + + public void copy(DenseGrid2i copy) + { + Array.Copy(copy.Buffer, this.Buffer, this.Buffer.Length); + } + + public void assign(int value) + { + for (int i = 0; i < Buffer.Length; ++i) + Buffer[i] = value; + } + + public int this[int i] { + get { return Buffer[i]; } + set { Buffer[i] = value; } + } + + public int this[int i, int j] { + get { return Buffer[i + ni * j]; } + set { Buffer[i + ni * j] = value; } + } + + public int this[Vector2i ijk] { + get { return Buffer[ijk.x + ni * ijk.y]; } + set { Buffer[ijk.x + ni * ijk.y] = value; } + } + + public void increment(int i, int j) + { + Buffer[i + ni * j]++; + } + public void decrement(int i, int j) + { + Buffer[i + ni * j]--; + } + + public void atomic_increment(int i, int j) + { + System.Threading.Interlocked.Increment(ref Buffer[i + ni * j]); + } + + public void atomic_decrement(int i, int j) + { + System.Threading.Interlocked.Decrement(ref Buffer[i + ni * j]); + } + + public void atomic_incdec(int i, int j, bool decrement = false) { + if ( decrement ) + System.Threading.Interlocked.Decrement(ref Buffer[i + ni * j]); + else + System.Threading.Interlocked.Increment(ref Buffer[i + ni * j]); + } + + public int sum() { + int sum = 0; + for (int i = 0; i < Buffer.Length; ++i) + sum += Buffer[i]; + return sum; + } + + + public IEnumerable Indices() + { + for (int y = 0; y < nj; ++y) { + for (int x = 0; x < ni; ++x) + yield return new Vector2i(x, y); + } + } + + + public IEnumerable InsetIndices(int border_width) + { + int stopy = nj - border_width, stopx = ni - border_width; + for (int y = border_width; y < stopy; ++y) { + for (int x = border_width; x < stopx; ++x) + yield return new Vector2i(x, y); + } + } + + } + + + +} diff --git a/spatial/DenseGrid3.cs b/spatial/DenseGrid3.cs index 6ce6041d..cbe6050c 100644 --- a/spatial/DenseGrid3.cs +++ b/spatial/DenseGrid3.cs @@ -6,12 +6,42 @@ namespace g3 { - + /// + /// 3D dense grid of floating-point scalar values. + /// public class DenseGrid3f { public float[] Buffer; public int ni, nj, nk; + public DenseGrid3f() + { + ni = nj = nk = 0; + } + + public DenseGrid3f(int ni, int nj, int nk, float initialValue) + { + resize(ni, nj, nk); + assign(initialValue); + } + + public DenseGrid3f(DenseGrid3f copy) + { + Buffer = new float[copy.Buffer.Length]; + Array.Copy(copy.Buffer, Buffer, Buffer.Length); + ni = copy.ni; nj = copy.nj; nk = copy.nk; + } + + public void swap(DenseGrid3f g2) + { + Util.gDevAssert(ni == g2.ni && nj == g2.nj && nk == g2.nk); + var tmp = g2.Buffer; + g2.Buffer = this.Buffer; + this.Buffer = tmp; + } + + public int size { get { return ni * nj * nk; } } + public void resize(int ni, int nj, int nk) { Buffer = new float[ni * nj * nk]; @@ -24,6 +54,24 @@ public void assign(float value) Buffer[i] = value; } + public void set_min(ref Vector3i ijk, float f) + { + int idx = ijk.x + ni * (ijk.y + nj * ijk.z); + if (f < Buffer[idx]) + Buffer[idx] = f; + } + public void set_max(ref Vector3i ijk, float f) + { + int idx = ijk.x + ni * (ijk.y + nj * ijk.z); + if (f > Buffer[idx]) + Buffer[idx] = f; + } + + public float this[int i] { + get { return Buffer[i]; } + set { Buffer[i] = value; } + } + public float this[int i, int j, int k] { get { return Buffer[i + ni * (j + nj * k)]; } set { Buffer[i + ni * (j + nj * k)] = value; } @@ -53,6 +101,49 @@ public void apply(Func f) } } + + public DenseGrid2f get_slice(int slice_i, int dimension) + { + DenseGrid2f slice; + if (dimension == 0) { + slice = new DenseGrid2f(nj, nk, 0); + for (int k = 0; k < nk; ++k) + for (int j = 0; j < nj; ++j) + slice[j, k] = Buffer[slice_i + ni * (j + nj * k)]; + } else if (dimension == 1) { + slice = new DenseGrid2f(ni, nk, 0); + for (int k = 0; k < nk; ++k) + for (int i = 0; i < ni; ++i) + slice[i, k] = Buffer[i + ni * (slice_i + nj * k)]; + } else { + slice = new DenseGrid2f(ni, nj, 0); + for (int j = 0; j < nj; ++j) + for (int i = 0; i < ni; ++i) + slice[i, j] = Buffer[i + ni * (j + nj * slice_i)]; + } + return slice; + } + + + public void set_slice(DenseGrid2f slice, int slice_i, int dimension) + { + if (dimension == 0) { + for (int k = 0; k < nk; ++k) + for (int j = 0; j < nj; ++j) + Buffer[slice_i + ni * (j + nj * k)] = slice[j, k]; + } else if (dimension == 1) { + for (int k = 0; k < nk; ++k) + for (int i = 0; i < ni; ++i) + Buffer[i + ni * (slice_i + nj * k)] = slice[i, k]; + } else { + for (int j = 0; j < nj; ++j) + for (int i = 0; i < ni; ++i) + Buffer[i + ni * (j + nj * slice_i)] = slice[i, j]; + } + } + + + public AxisAlignedBox3i Bounds { get { return new AxisAlignedBox3i(0, 0, 0, ni, nj, nk); } } @@ -81,26 +172,53 @@ public IEnumerable InsetIndices(int border_width) } + public Vector3i to_index(int idx) { + int x = idx % ni; + int y = (idx / ni) % nj; + int z = idx / (ni * nj); + return new Vector3i(x, y, z); + } + public int to_linear(int i, int j, int k) + { + return i + ni * (j + nj * k); + } + public int to_linear(ref Vector3i ijk) + { + return ijk.x + ni * (ijk.y + nj * ijk.z); + } + public int to_linear(Vector3i ijk) + { + return ijk.x + ni * (ijk.y + nj * ijk.z); + } } - + /// + /// 3D dense grid of integers. + /// public class DenseGrid3i { public int[] Buffer; public int ni, nj, nk; + public DenseGrid3i() + { + ni = nj = nk = 0; + } + public DenseGrid3i(int ni, int nj, int nk, int initialValue) { resize(ni, nj, nk); assign(initialValue); } + public int size { get { return ni * nj * nk; } } + public void resize(int ni, int nj, int nk) { Buffer = new int[ni * nj * nk]; @@ -113,11 +231,21 @@ public void assign(int value) Buffer[i] = value; } + public int this[int i] { + get { return Buffer[i]; } + set { Buffer[i] = value; } + } + public int this[int i, int j, int k] { get { return Buffer[i + ni * (j + nj * k)]; } set { Buffer[i + ni * (j + nj * k)] = value; } } + public int this[Vector3i ijk] { + get { return Buffer[ijk.x + ni * (ijk.y + nj * ijk.z)]; } + set { Buffer[ijk.x + ni * (ijk.y + nj * ijk.z)] = value; } + } + public void increment(int i, int j, int k) { Buffer[i + ni * (j + nj * k)]++; @@ -143,6 +271,69 @@ public void atomic_incdec(int i, int j, int k, bool decrement = false) { else System.Threading.Interlocked.Increment(ref Buffer[i + ni * (j + nj * k)]); } + + + + public DenseGrid2i get_slice(int slice_i, int dimension) + { + DenseGrid2i slice; + if ( dimension == 0 ) { + slice = new DenseGrid2i(nj, nk, 0); + for (int k = 0; k < nk; ++k) + for (int j = 0; j < nj; ++j) + slice[j, k] = Buffer[slice_i + ni * (j + nj * k)]; + } else if (dimension == 1) { + slice = new DenseGrid2i(ni, nk, 0); + for (int k = 0; k < nk; ++k) + for (int i = 0; i < ni; ++i) + slice[i, k] = Buffer[i + ni * (slice_i + nj * k)]; + } else { + slice = new DenseGrid2i(ni, nj, 0); + for (int j = 0; j < nj; ++j) + for (int i = 0; i < ni; ++i) + slice[i, j] = Buffer[i + ni * (j + nj * slice_i)]; + } + return slice; + } + + + /// + /// convert to binary bitmap + /// + public Bitmap3 get_bitmap(int thresh = 0) + { + Bitmap3 bmp = new Bitmap3(new Vector3i(ni, nj, nk)); + for (int i = 0; i < Buffer.Length; ++i) + bmp[i] = (Buffer[i] > thresh) ? true : false; + return bmp; + } + + + public IEnumerable Indices() + { + for (int z = 0; z < nk; ++z) { + for (int y = 0; y < nj; ++y) { + for (int x = 0; x < ni; ++x) + yield return new Vector3i(x, y, z); + } + } + } + + + public IEnumerable InsetIndices(int border_width) + { + int stopy = nj - border_width, stopx = ni - border_width; + for (int z = border_width; z < nk - border_width; ++z) { + for (int y = border_width; y < stopy; ++y) { + for (int x = border_width; x < stopx; ++x) + yield return new Vector3i(x, y, z); + } + } + } + + + + } From ee9fa991015fd4bf6e74e517686237425250b2ed Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 22 Apr 2018 14:07:45 -0400 Subject: [PATCH 113/225] discretize implicit functions into dense grid --- geometry3Sharp.csproj | 1 + implicit/ImplicitFieldSampler3d.cs | 91 ++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 implicit/ImplicitFieldSampler3d.cs diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 0d6c9673..f2318307 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -104,6 +104,7 @@ + diff --git a/implicit/ImplicitFieldSampler3d.cs b/implicit/ImplicitFieldSampler3d.cs new file mode 100644 index 00000000..8f01b3fa --- /dev/null +++ b/implicit/ImplicitFieldSampler3d.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace g3 +{ + /// + /// Sample implicit fields into a dense grid + /// + public class ImplicitFieldSampler3d + { + public DenseGrid3f Grid; + public double CellSize; + public Vector3d GridOrigin; + public ShiftGridIndexer3 Indexer; + public AxisAlignedBox3i GridBounds; + + public float BackgroundValue; + + + public enum CombineModes + { + DistanceMinUnion = 0 + } + public CombineModes CombineMode = CombineModes.DistanceMinUnion; + + + public ImplicitFieldSampler3d(AxisAlignedBox3d fieldBounds, double cellSize) + { + CellSize = cellSize; + GridOrigin = fieldBounds.Min; + Indexer = new ShiftGridIndexer3(GridOrigin, CellSize); + + Vector3d max = fieldBounds.Max; max += cellSize; + int ni = (int)((max.x - GridOrigin.x) / CellSize) + 1; + int nj = (int)((max.y - GridOrigin.y) / CellSize) + 1; + int nk = (int)((max.z - GridOrigin.z) / CellSize) + 1; + + GridBounds = new AxisAlignedBox3i(0, 0, 0, ni, nj, nk); + + BackgroundValue = (float)((ni + nj + nk) * CellSize); + Grid = new DenseGrid3f(ni, nj, nk, BackgroundValue); + } + + + + public DenseGridTrilinearImplicit ToImplicit() { + return new DenseGridTrilinearImplicit(Grid, GridOrigin, CellSize); + } + + + + public void Clear(float f) + { + BackgroundValue = f; + Grid.assign(BackgroundValue); + } + + + + public void Sample(BoundedImplicitFunction3d f, double expandRadius = 0) + { + AxisAlignedBox3d bounds = f.Bounds(); + + Vector3d expand = expandRadius * Vector3d.One; + Vector3i gridMin = Indexer.ToGrid(bounds.Min-expand), + gridMax = Indexer.ToGrid(bounds.Max+expand) + Vector3i.One; + gridMin = GridBounds.ClampExclusive(gridMin); + gridMax = GridBounds.ClampExclusive(gridMax); + + AxisAlignedBox3i gridbox = new AxisAlignedBox3i(gridMin, gridMax); + switch (CombineMode) { + case CombineModes.DistanceMinUnion: + sample_min(f, gridbox.IndicesInclusive()); + break; + } + } + + + void sample_min(BoundedImplicitFunction3d f, IEnumerable indices) + { + gParallel.ForEach(indices, (idx) => { + Vector3d v = Indexer.FromGrid(idx); + double d = f.Value(ref v); + Grid.set_min(ref idx, (float)d); + }); + } + + } +} From 583dfe66f2118b1f3c4c9be8bac164bcad45eb9f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 22 Apr 2018 14:53:01 -0400 Subject: [PATCH 114/225] polygon utils --- curve/CurveUtils2.cs | 56 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/curve/CurveUtils2.cs b/curve/CurveUtils2.cs index a3abec8c..5d68cc46 100644 --- a/curve/CurveUtils2.cs +++ b/curve/CurveUtils2.cs @@ -173,14 +173,25 @@ public static void LaplacianSmoothConstrained(Polygon2d poly, double alpha, int for (int i = 0; i < N; ++i ) { Vector2d curpos = poly[i]; Vector2d smoothpos = (poly[(i + N - 1) % N] + poly[(i + 1) % N]) * 0.5; - bool contained = true; - if (bAllowShrink == false || bAllowGrow == false) - contained = origPoly.Contains(smoothpos); + bool do_smooth = true; - if (bAllowShrink && contained == false) - do_smooth = false; - if (bAllowGrow && contained == true) - do_smooth = false; + if (bAllowShrink == false || bAllowGrow == false) { + bool is_inside = origPoly.Contains(smoothpos); + if (is_inside == true) + do_smooth = bAllowShrink; + else + do_smooth = bAllowGrow; + } + + // [RMS] this is old code...I think not correct? + //bool contained = true; + //if (bAllowShrink == false || bAllowGrow == false) + // contained = origPoly.Contains(smoothpos); + //bool do_smooth = true; + //if (bAllowShrink && contained == false) + // do_smooth = false; + //if (bAllowGrow && contained == true) + // do_smooth = false; if ( do_smooth ) { Vector2d newpos = beta * curpos + alpha * smoothpos; @@ -216,6 +227,37 @@ public static void LaplacianSmoothConstrained(GeneralPolygon2d solid, double alp + public static Polygon2d SplitToTargetLength(Polygon2d poly, double length) + { + Polygon2d result = new Polygon2d(); + result.AppendVertex(poly[0]); + for (int j = 0; j < poly.VertexCount; ++j) { + int next = (j + 1) % poly.VertexCount; + double len = poly[j].Distance(poly[next]); + if (len < length) { + result.AppendVertex(poly[next]); + continue; + } + + int steps = (int)Math.Ceiling(len / length); + for (int k = 1; k < steps; ++k) { + double t = (double)(k) / (double)steps; + Vector2d v = (1.0 - t) * poly[j] + (t) * poly[next]; + result.AppendVertex(v); + } + + if (j < poly.VertexCount - 1) { + Util.gDevAssert(poly[j].Distance(result.Vertices[result.VertexCount - 1]) > 0.0001); + result.AppendVertex(poly[next]); + } + } + + return result; + } + + + + /// /// Remove polygons and polygon-holes smaller than minArea /// From 01f5ed45d0eaa9e4b1b9c97013f3c91b2622f046 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 22 Apr 2018 22:28:00 -0400 Subject: [PATCH 115/225] very useful aabb function --- math/AxisAlignedBox3d.cs | 12 +++++++++++- math/AxisAlignedBox3f.cs | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/math/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs index 96f1e02f..ed41045d 100644 --- a/math/AxisAlignedBox3d.cs +++ b/math/AxisAlignedBox3d.cs @@ -100,7 +100,6 @@ public Vector3d Center { get { return new Vector3d(0.5 * (Min.x + Max.x), 0.5 * (Min.y + Max.y), 0.5 * (Min.z + Max.z)); } } - public static bool operator ==(AxisAlignedBox3d a, AxisAlignedBox3d b) { return a.Min == b.Min && a.Max == b.Max; } @@ -138,6 +137,17 @@ public Vector3d Corner(int i) return new Vector3d(x, y, z); } + /// + /// Returns point on face/edge/corner. For each coord value neg==min, 0==center, pos==max + /// + public Vector3d Point(int xi, int yi, int zi) + { + double x = (xi < 0) ? Min.x : ((xi == 0) ? (0.5*(Min.x + Max.x)) : Max.x); + double y = (yi < 0) ? Min.y : ((yi == 0) ? (0.5*(Min.y + Max.y)) : Max.y); + double z = (zi < 0) ? Min.z : ((zi == 0) ? (0.5*(Min.z + Max.z)) : Max.z); + return new Vector3d(x, y, z); + } + // TODO ////! 0 == bottom-left, 1 = bottom-right, 2 == top-right, 3 == top-left diff --git a/math/AxisAlignedBox3f.cs b/math/AxisAlignedBox3f.cs index c3f8b7ee..356483ac 100644 --- a/math/AxisAlignedBox3f.cs +++ b/math/AxisAlignedBox3f.cs @@ -150,6 +150,18 @@ public Vector3f Corner(int i) } + /// + /// Returns point on face/edge/corner. For each coord value neg==min, 0==center, pos==max + /// + public Vector3f Point(int xi, int yi, int zi) + { + float x = (xi < 0) ? Min.x : ((xi == 0) ? (0.5f * (Min.x + Max.x)) : Max.x); + float y = (yi < 0) ? Min.y : ((yi == 0) ? (0.5f * (Min.y + Max.y)) : Max.y); + float z = (zi < 0) ? Min.z : ((zi == 0) ? (0.5f * (Min.z + Max.z)) : Max.z); + return new Vector3f(x, y, z); + } + + //! value is subtracted from min and added to max public void Expand(float fRadius) { From 89dcd9f03abbc3cc920eedcc0b933204fa9745fd Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 24 Apr 2018 09:59:37 -0400 Subject: [PATCH 116/225] utility bits --- curve/PolyLine2d.cs | 37 +++++++++++++++++++++++++++++++++++++ mesh/MeshEditor.cs | 6 +++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/curve/PolyLine2d.cs b/curve/PolyLine2d.cs index 5ff1aac5..a0d201e5 100644 --- a/curve/PolyLine2d.cs +++ b/curve/PolyLine2d.cs @@ -340,6 +340,43 @@ public PolyLine2d Transform(ITransform2 xform) } + + static public PolyLine2d MakeBoxSpiral(Vector2d center, double len, double spacing) + { + double d = spacing / 2; + PolyLine2d pline = new PolyLine2d(); + pline.AppendVertex(center); + + Vector2d c = center; + c.x += spacing / 2; + pline.AppendVertex(c); + c.y += spacing; + pline.AppendVertex(c); + double accum = spacing / 2 + spacing; + + double w = spacing / 2; + double h = spacing; + + double sign = -1.0; + while (accum < len) { + w += spacing; + c.x += sign * w; + pline.AppendVertex(c); + accum += w; + + h += spacing; + c.y += sign * h; + pline.AppendVertex(c); + accum += h; + + sign *= -1.0; + } + + return pline; + } + + + } diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 117d744b..8f201332 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -246,10 +246,10 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id /// vertex ordering must reslut in appropriate orientation (which is...??) /// [TODO] check and fail on bad orientation /// - public virtual int[] StitchSpan(int[] vspan1, int[] vspan2, int group_id = -1) + public virtual int[] StitchSpan(IList vspan1, IList vspan2, int group_id = -1) { - int N = vspan1.Length; - if (N != vspan2.Length) + int N = vspan1.Count; + if (N != vspan2.Count) throw new Exception("MeshEditor.StitchSpan: spans are not the same length!!"); N--; From 307dfe4ee3a4e8fbb54173d49eb2a61223406ec3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 24 Apr 2018 13:03:56 -0400 Subject: [PATCH 117/225] optional flag to force return face normal --- queries/MeshQueries.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index 0789ea6f..0f56c1c2 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -26,11 +26,11 @@ public static DistPoint3Triangle3 TriangleDistance(DMesh3 mesh, int ti, Vector3d /// Find point-normal(Z) frame at closest point to queryPoint on mesh. /// Returns interpolated vertex-normal frame if available, otherwise tri-normal frame. /// - public static Frame3f NearestPointFrame(DMesh3 mesh, ISpatial spatial, Vector3d queryPoint) + public static Frame3f NearestPointFrame(DMesh3 mesh, ISpatial spatial, Vector3d queryPoint, bool bForceFaceNormal = false) { int tid = spatial.FindNearestTriangle(queryPoint); Vector3d surfPt = TriangleDistance(mesh, tid, queryPoint).TriangleClosest; - if (mesh.HasVertexNormals) + if (mesh.HasVertexNormals && bForceFaceNormal == false) return SurfaceFrame(mesh, tid, surfPt); else return new Frame3f(surfPt, mesh.GetTriNormal(tid)); @@ -113,7 +113,7 @@ public static DistTriangle3Triangle3 TrianglesDistance(DMesh3 mesh1, int ti, DMe /// Find point-normal frame at ray-intersection point on mesh, or return false if no hit. /// Returns interpolated vertex-normal frame if available, otherwise tri-normal frame. /// - public static bool RayHitPointFrame(DMesh3 mesh, ISpatial spatial, Ray3d ray, out Frame3f hitPosFrame) + public static bool RayHitPointFrame(DMesh3 mesh, ISpatial spatial, Ray3d ray, out Frame3f hitPosFrame, bool bForceFaceNormal = false) { hitPosFrame = new Frame3f(); int tid = spatial.FindNearestHitTriangle(ray); @@ -123,7 +123,7 @@ public static bool RayHitPointFrame(DMesh3 mesh, ISpatial spatial, Ray3d ray, ou if (isect.Result != IntersectionResult.Intersects) return false; Vector3d surfPt = ray.PointAt(isect.RayParameter); - if (mesh.HasVertexNormals) + if (mesh.HasVertexNormals && bForceFaceNormal == false) hitPosFrame = SurfaceFrame(mesh, tid, surfPt); // TODO isect has bary-coords already!! else hitPosFrame = new Frame3f(surfPt, mesh.GetTriNormal(tid)); @@ -135,7 +135,7 @@ public static bool RayHitPointFrame(DMesh3 mesh, ISpatial spatial, Ray3d ray, ou /// Get point-normal frame on surface of mesh. Assumption is that point lies in tID. /// returns interpolated vertex-normal frame if available, otherwise tri-normal frame. /// - public static Frame3f SurfaceFrame(DMesh3 mesh, int tID, Vector3d point) + public static Frame3f SurfaceFrame(DMesh3 mesh, int tID, Vector3d point, bool bForceFaceNormal = false) { if (!mesh.IsTriangle(tID)) throw new Exception("MeshQueries.SurfaceFrame: triangle " + tID + " does not exist!"); @@ -143,7 +143,7 @@ public static Frame3f SurfaceFrame(DMesh3 mesh, int tID, Vector3d point) mesh.GetTriVertices(tID, ref tri.V0, ref tri.V1, ref tri.V2); Vector3d bary = tri.BarycentricCoords(point); point = tri.PointAt(bary); - if (mesh.HasVertexNormals) { + if (mesh.HasVertexNormals && bForceFaceNormal == false) { Vector3d normal = mesh.GetTriBaryNormal(tID, bary.x, bary.y, bary.z); return new Frame3f(point, normal); } else From b6544b41e8d72a4a2efe683d20d825633eb644a0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 24 Apr 2018 14:57:32 -0400 Subject: [PATCH 118/225] curve smoothing fn --- curve/CurveUtils.cs | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs index bff793cc..bc5a9db5 100644 --- a/curve/CurveUtils.cs +++ b/curve/CurveUtils.cs @@ -146,6 +146,56 @@ public static void InPlaceSmooth(IList vertices, int iStart, int iEnd, + /// + /// smooth set of vertices using extra buffer + /// + public static void IterativeSmooth(IList vertices, double alpha, int nIterations, bool bClosed) + { + IterativeSmooth(vertices, 0, vertices.Count, alpha, nIterations, bClosed); + } + /// + /// smooth set of vertices using extra buffer + /// + public static void IterativeSmooth(IList vertices, int iStart, int iEnd, double alpha, int nIterations, bool bClosed, Vector3d[] buffer = null) + { + int N = vertices.Count; + if (buffer == null || buffer.Length < N) + buffer = new Vector3d[N]; + if (bClosed) { + for (int iter = 0; iter < nIterations; ++iter) { + for (int ii = iStart; ii < iEnd; ++ii) { + int i = (ii % N); + int iPrev = (ii == 0) ? N - 1 : ii - 1; + int iNext = (ii + 1) % N; + Vector3d prev = vertices[iPrev], next = vertices[iNext]; + Vector3d c = (prev + next) * 0.5f; + buffer[i] = (1 - alpha) * vertices[i] + (alpha) * c; + } + for (int ii = iStart; ii < iEnd; ++ii) { + int i = (ii % N); + vertices[i] = buffer[i]; + } + } + } else { + for (int iter = 0; iter < nIterations; ++iter) { + for (int i = iStart; i <= iEnd; ++i) { + if (i == 0 || i >= N - 1) + continue; + Vector3d prev = vertices[i - 1], next = vertices[i + 1]; + Vector3d c = (prev + next) * 0.5f; + buffer[i] = (1 - alpha) * vertices[i] + (alpha) * c; + } + for (int ii = iStart; ii < iEnd; ++ii) { + int i = (ii % N); + vertices[i] = buffer[i]; + } + } + } + } + + + + } From 2c69ccbc2a2bd9a4bf590aa48e01e6d249323c21 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 24 Apr 2018 20:45:55 -0400 Subject: [PATCH 119/225] util functions --- curve/CurveUtils2.cs | 30 ++++++++++++++++++++++++++++++ curve/Polygon2d.cs | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/curve/CurveUtils2.cs b/curve/CurveUtils2.cs index a3abec8c..de6729c0 100644 --- a/curve/CurveUtils2.cs +++ b/curve/CurveUtils2.cs @@ -215,6 +215,36 @@ public static void LaplacianSmoothConstrained(GeneralPolygon2d solid, double alp + /// + /// return list of objects for which keepF(obj) returns true + /// + public static List Filter(List objects, Func keepF) + { + List result = new List(objects.Count); + foreach (var obj in objects) { + if (keepF(obj)) + result.Add(obj); + } + return result; + } + + + /// + /// Split the input list into two new lists, based on predicate (set1 == true) + /// + public static void Split(List objects, out List set1, out List set2, Func splitF) + { + set1 = new List(); + set2 = new List(); + foreach (var obj in objects) { + if (splitF(obj)) + set1.Add(obj); + else + set2.Add(obj); + } + } + + /// /// Remove polygons and polygon-holes smaller than minArea diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index f938c31b..7bf37970 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -709,6 +709,17 @@ public void Chamfer(double chamfer_dist, double minConvexAngleDeg = 30, double m + /// + /// Return minimal bounding box of vertices, computed to epsilon tolerance + /// + public Box2d MinimalBoundingBox(double epsilon) + { + ContMinBox2 box2 = new ContMinBox2(vertices, epsilon, QueryNumberType.QT_DOUBLE, false); + return box2.MinBox; + } + + + static public Polygon2d MakeRectangle(Vector2d center, double width, double height) { VectorArray2d vertices = new VectorArray2d(4); From f7622cafabeed381929b7db8f3c2ac61123f34f7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 24 Apr 2018 23:01:25 -0400 Subject: [PATCH 120/225] svg writer util --- io/SVGWriter.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/io/SVGWriter.cs b/io/SVGWriter.cs index 8fe0f15d..549bcf17 100644 --- a/io/SVGWriter.cs +++ b/io/SVGWriter.cs @@ -244,6 +244,27 @@ public static void QuickWrite(List polygons, string sPath, dou } + public static void QuickWrite(List polygons1, string color1, float width1, + List polygons2, string color2, float width2, + string sPath) + { + SVGWriter writer = new SVGWriter(); + Style style1 = SVGWriter.Style.Outline(color1, width1); + Style style1_holes = SVGWriter.Style.Outline(color1, width1/2); + foreach (GeneralPolygon2d poly in polygons1) { + writer.AddPolygon(poly.Outer, style1); + foreach (var hole in poly.Holes) + writer.AddPolygon(hole, style1_holes); + } + Style style2 = SVGWriter.Style.Outline(color2, width2); + Style style2_holes = SVGWriter.Style.Outline(color2, width2 / 2); + foreach (GeneralPolygon2d poly in polygons2) { + writer.AddPolygon(poly.Outer, style2); + foreach (var hole in poly.Holes) + writer.AddPolygon(hole, style2_holes); + } + writer.Write(sPath); + } From 36ae0fb6961c7b128a40bede0aa9d3464e5ffb51 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 25 Apr 2018 12:00:23 -0400 Subject: [PATCH 121/225] added ModifyVerticesMeshChange --- mesh/DMesh3Changes.cs | 111 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index 694a78b6..384d96eb 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -5,6 +5,117 @@ namespace g3 { + /// + /// Mesh change for vertex deformations. Currently minimal support for initializing buffers. + /// AppendNewVertex() can be used to accumulate modified vertices and their initial positions. + /// + public class ModifyVerticesMeshChange + { + public DVector ModifiedV; + public DVector OldPositions, NewPositions; + public DVector OldNormals, NewNormals; + public DVector OldColors, NewColors; + public DVector OldUVs, NewUVs; + + public Action OnApplyF; + public Action OnRevertF; + + public ModifyVerticesMeshChange(DMesh3 mesh, MeshComponents wantComponents = MeshComponents.All) + { + initialize_buffers(mesh, wantComponents); + } + + + public int AppendNewVertex(DMesh3 mesh, int vid) + { + int idx = ModifiedV.Length; + ModifiedV.Add(vid); + OldPositions.Add(mesh.GetVertex(vid)); + NewPositions.Add(OldPositions[idx]); + if (NewNormals != null) { + OldNormals.Add(mesh.GetVertexNormal(vid)); + NewNormals.Add(OldNormals[idx]); + } + if (NewColors != null) { + OldColors.Add(mesh.GetVertexColor(vid)); + NewColors.Add(OldColors[idx]); + } + if (NewUVs != null) { + OldUVs.Add(mesh.GetVertexUV(vid)); + NewUVs.Add(OldUVs[idx]); + } + return idx; + } + + public void Apply(DMesh3 mesh) + { + int N = ModifiedV.size; + for (int i = 0; i < N; ++i) { + int vid = ModifiedV[i]; + mesh.SetVertex(vid, NewPositions[i]); + if (NewNormals != null) + mesh.SetVertexNormal(vid, NewNormals[i]); + if (NewColors != null) + mesh.SetVertexColor(vid, NewColors[i]); + if (NewUVs != null) + mesh.SetVertexUV(vid, NewUVs[i]); + } + if (OnApplyF != null) + OnApplyF(this); + } + + + public void Revert(DMesh3 mesh) + { + int N = ModifiedV.size; + for (int i = 0; i < N; ++i) { + int vid = ModifiedV[i]; + mesh.SetVertex(vid, OldPositions[i]); + if (NewNormals != null) + mesh.SetVertexNormal(vid, OldNormals[i]); + if (NewColors != null) + mesh.SetVertexColor(vid, OldColors[i]); + if (NewUVs != null) + mesh.SetVertexUV(vid, OldUVs[i]); + } + if (OnRevertF != null) + OnRevertF(this); + } + + + void initialize_buffers(DMesh3 mesh, MeshComponents components) + { + ModifiedV = new DVector(); + NewPositions = new DVector(); + OldPositions = new DVector(); + if (mesh.HasVertexNormals && (components & MeshComponents.VertexNormals) != 0) { + NewNormals = new DVector(); + OldNormals = new DVector(); + } + if (mesh.HasVertexColors && (components & MeshComponents.VertexColors) != 0) { + NewColors = new DVector(); + OldColors = new DVector(); + } + if (mesh.HasVertexUVs && (components & MeshComponents.VertexUVs) != 0) { + NewUVs = new DVector(); + OldUVs = new DVector(); + } + } + + } + + + + + + + + + + + + + /// /// Remove triangles from mesh and store necessary data to be able to reverse the change. /// Vertex and Triangle IDs will be restored on Revert() From ecfcc2e734ea342cc339f7cb7e67a6a740b2fc36 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 25 Apr 2018 20:28:09 -0400 Subject: [PATCH 122/225] add triangles constructor to DijkstraGraphDistance, option to use higher-res plane mesh in poly triangulator --- core/DijkstraGraphDistance.cs | 20 ++++++++++++++++++ .../TriangulatedPolygonGenerator.cs | 21 +++++++++++-------- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/core/DijkstraGraphDistance.cs b/core/DijkstraGraphDistance.cs index 09b56243..63bf25b5 100644 --- a/core/DijkstraGraphDistance.cs +++ b/core/DijkstraGraphDistance.cs @@ -136,6 +136,26 @@ public static DijkstraGraphDistance MeshVertices(DMesh3 mesh, bool bSparse = fal + /// + /// shortcut to construct graph for mesh triangles + /// + public static DijkstraGraphDistance MeshTriangles(DMesh3 mesh, bool bSparse = false) + { + Func tri_dist_f = (a, b) => { + return (float)mesh.GetTriCentroid(a).Distance(mesh.GetTriCentroid(b)); + }; + + return (bSparse) ? + new DijkstraGraphDistance(mesh.MaxTriangleID, true, + (id) => { return mesh.IsTriangle(id); }, tri_dist_f, + mesh.TriTrianglesItr, null) + : new DijkstraGraphDistance(mesh.MaxTriangleID, false, + (id) => { return true; }, tri_dist_f, + mesh.TriTrianglesItr, null); + } + + + /// /// reset internal data structures/etc /// diff --git a/mesh_generators/TriangulatedPolygonGenerator.cs b/mesh_generators/TriangulatedPolygonGenerator.cs index 9123fe7b..d09102a8 100644 --- a/mesh_generators/TriangulatedPolygonGenerator.cs +++ b/mesh_generators/TriangulatedPolygonGenerator.cs @@ -13,6 +13,7 @@ public class TriangulatedPolygonGenerator : MeshGenerator public TrivialRectGenerator.UVModes UVMode = TrivialRectGenerator.UVModes.FullUVSquare; + public int Subdivisions = 1; override public MeshGenerator Generate() { @@ -53,15 +54,17 @@ public DMesh3 ComputeResult(out MeshInsertPolygon insertion) double padding = 0.1 * bounds.DiagonalLength; bounds.Expand(padding); - TrivialRectGenerator rectgen = new TrivialRectGenerator(); - rectgen.Width = (float)bounds.Width; - rectgen.Height = (float)bounds.Height; - rectgen.IndicesMap = new Index2i(1, 2); - rectgen.UVMode = UVMode; - rectgen.Clockwise = true; // MeshPolygonInserter assumes mesh faces are CW? (except code says CCW...) - rectgen.Generate(); - DMesh3 base_mesh = new DMesh3(); - rectgen.MakeMesh(base_mesh); + TrivialRectGenerator rectgen = (Subdivisions == 1) ? + new TrivialRectGenerator() : new GriddedRectGenerator() { EdgeVertices = Subdivisions }; + + rectgen.Width = (float)bounds.Width; + rectgen.Height = (float)bounds.Height; + rectgen.IndicesMap = new Index2i(1, 2); + rectgen.UVMode = UVMode; + rectgen.Clockwise = true; // MeshPolygonInserter assumes mesh faces are CW? (except code says CCW...) + rectgen.Generate(); + DMesh3 base_mesh = new DMesh3(); + rectgen.MakeMesh(base_mesh); GeneralPolygon2d shiftPolygon = new GeneralPolygon2d(Polygon); Vector2d shift = bounds.Center; From 5d97cc4bf243c7fa7176a2bee5cba978f2283b44 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 25 Apr 2018 22:44:27 -0400 Subject: [PATCH 123/225] iterator --- mesh/MeshIterators.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mesh/MeshIterators.cs b/mesh/MeshIterators.cs index e3a775d9..6f9ce32d 100644 --- a/mesh/MeshIterators.cs +++ b/mesh/MeshIterators.cs @@ -124,6 +124,18 @@ public static IEnumerable BoundaryEdges(DMesh3 mesh) } + public static IEnumerable InteriorEdges(DMesh3 mesh) + { + int N = mesh.MaxEdgeID; + for (int i = 0; i < N; ++i) { + if (mesh.IsEdge(i)) { + if (mesh.IsBoundaryEdge(i) == false) + yield return i; + } + } + } + + public static IEnumerable GroupBoundaryEdges(DMesh3 mesh) { int N = mesh.MaxEdgeID; From bff0f4235982fcb7f27fd8fd677bd6f1c3ab3dda Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 9 May 2018 18:08:54 -0400 Subject: [PATCH 124/225] IsBoundaryLoop can now take a different mesh to check --- mesh/EdgeLoop.cs | 11 +++++++---- mesh/EdgeSpan.cs | 8 +++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mesh/EdgeLoop.cs b/mesh/EdgeLoop.cs index c339fba3..e83d51e8 100644 --- a/mesh/EdgeLoop.cs +++ b/mesh/EdgeLoop.cs @@ -203,15 +203,18 @@ public bool IsInternalLoop() /// - /// Check if all edges of this loop are boundary edges + /// Check if all edges of this loop are boundary edges. + /// If testMesh != null, will check that mesh instead of internal Mesh /// - public bool IsBoundaryLoop() + public bool IsBoundaryLoop(DMesh3 testMesh = null) { + DMesh3 useMesh = (testMesh != null) ? testMesh : Mesh; + int NV = Vertices.Length; for (int i = 0; i < NV; ++i ) { - int eid = Mesh.FindEdge(Vertices[i], Vertices[(i + 1) % NV]); + int eid = useMesh.FindEdge(Vertices[i], Vertices[(i + 1) % NV]); Debug.Assert(eid != DMesh3.InvalidID); - if (Mesh.IsBoundaryEdge(eid) == false) + if (useMesh.IsBoundaryEdge(eid) == false) return false; } return true; diff --git a/mesh/EdgeSpan.cs b/mesh/EdgeSpan.cs index 0c038aed..f55e4835 100644 --- a/mesh/EdgeSpan.cs +++ b/mesh/EdgeSpan.cs @@ -128,13 +128,15 @@ public bool IsInternalSpan() } - public bool IsBoundarySpan() + public bool IsBoundarySpan(DMesh3 testMesh = null) { + DMesh3 useMesh = (testMesh != null) ? testMesh : Mesh; + int NV = Vertices.Length; for (int i = 0; i < NV-1; ++i ) { - int eid = Mesh.FindEdge(Vertices[i], Vertices[i + 1]); + int eid = useMesh.FindEdge(Vertices[i], Vertices[i + 1]); Debug.Assert(eid != DMesh3.InvalidID); - if (Mesh.IsBoundaryEdge(eid) == false) + if (useMesh.IsBoundaryEdge(eid) == false) return false; } return true; From f23639cdda4a36da01d45b1f062148790ab18e13 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 10 May 2018 15:52:23 -0400 Subject: [PATCH 125/225] utility fn --- mesh_selection/MeshConnectedComponents.cs | 27 +++++++++++++++++++ mesh_selection/MeshVertexSelection.cs | 32 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/mesh_selection/MeshConnectedComponents.cs b/mesh_selection/MeshConnectedComponents.cs index 3c95befb..2c71559a 100644 --- a/mesh_selection/MeshConnectedComponents.cs +++ b/mesh_selection/MeshConnectedComponents.cs @@ -224,5 +224,32 @@ public static DMesh3 LargestT(DMesh3 meshIn) return submesh.SubMesh; } + + + + /// + /// Utility function that finds set of triangles connected to tSeed. Does not use MeshConnectedComponents class. + /// + public static HashSet FindConnectedT(DMesh3 mesh, int tSeed) + { + HashSet found = new HashSet(); + found.Add(tSeed); + List queue = new List(64) { tSeed }; + while ( queue.Count > 0 ) { + int tid = queue[queue.Count - 1]; + queue.RemoveAt(queue.Count - 1); + Index3i nbr_t = mesh.GetTriNeighbourTris(tid); + for ( int j = 0; j < 3; ++j ) { + int nbrid = nbr_t[j]; + if (nbrid == DMesh3.InvalidID || found.Contains(nbrid)) + continue; + found.Add(nbrid); + queue.Add(nbrid); + } + } + return found; + } + + } } diff --git a/mesh_selection/MeshVertexSelection.cs b/mesh_selection/MeshVertexSelection.cs index 9fb8f0a2..cda76921 100644 --- a/mesh_selection/MeshVertexSelection.cs +++ b/mesh_selection/MeshVertexSelection.cs @@ -149,6 +149,38 @@ public void SelectInteriorVertices(MeshFaceSelection triangles) + /// + /// Select set of boundary vertices connected to vSeed. + /// + public void SelectConnectedBoundaryV(int vSeed) + { + if ( ! Mesh.IsBoundaryVertex(vSeed)) + throw new Exception("MeshConnectedComponents.FindConnectedBoundaryV: vSeed is not a boundary vertex"); + + HashSet found = (Selected.Count == 0) ? Selected : new HashSet(); + found.Add(vSeed); + List queue = temp; queue.Clear(); + queue.Add(vSeed); + while (queue.Count > 0) { + int vid = queue[queue.Count - 1]; + queue.RemoveAt(queue.Count - 1); + foreach (int nbrid in Mesh.VtxVerticesItr(vid)) { + if (Mesh.IsBoundaryVertex(nbrid) && found.Contains(nbrid) == false) { + found.Add(nbrid); + queue.Add(nbrid); + } + } + } + if ( found != Selected ) { + foreach (int vid in found) + add(vid); + } + temp.Clear(); + } + + + + public void SelectEdgeVertices(int[] edges) { for (int i = 0; i < edges.Length; ++i) { From c963d7baa7d126a7fccebcc62a905b562e311336 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 10 May 2018 22:23:27 -0400 Subject: [PATCH 126/225] added Matrix3d vector-product constructor. exposed members of DMeshAABBTree to subclasses. --- math/MathUtil.cs | 1 + math/Matrix3d.cs | 20 ++++++++++++++++++++ spatial/DMeshAABBTree.cs | 18 +++++++++--------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/math/MathUtil.cs b/math/MathUtil.cs index ef708a78..6c5723a6 100644 --- a/math/MathUtil.cs +++ b/math/MathUtil.cs @@ -10,6 +10,7 @@ public static class MathUtil public const double Deg2Rad = (Math.PI / 180.0); public const double Rad2Deg = (180.0 / Math.PI); public const double TwoPI = 2.0 * Math.PI; + public const double FourPI = 4.0 * Math.PI; public const double HalfPI = 0.5 * Math.PI; public const double ZeroTolerance = 1e-08; public const double Epsilon = 2.2204460492503131e-016; diff --git a/math/Matrix3d.cs b/math/Matrix3d.cs index 7fc052f2..f0fd6395 100644 --- a/math/Matrix3d.cs +++ b/math/Matrix3d.cs @@ -85,6 +85,18 @@ public Matrix3d(double m00, double m01, double m02, double m10, double m11, doub } + /// + /// Construct outer-product of u*transpose(v) of u and v + /// result is that Mij = u_i * v_j + /// + public Matrix3d(ref Vector3d u, ref Vector3d v) + { + Row0 = new Vector3d(u.x*v.x, u.x*v.y, u.x*v.z); + Row1 = new Vector3d(u.y*v.x, u.y*v.y, u.y*v.z); + Row2 = new Vector3d(u.z*v.x, u.z*v.y, u.z*v.z); + } + + public static readonly Matrix3d Identity = new Matrix3d(true); public static readonly Matrix3d Zero = new Matrix3d(false); @@ -201,6 +213,14 @@ public void Multiply(ref Vector3d v, ref Vector3d vOut) { } + + public double InnerProduct(ref Matrix3d m2) + { + return Row0.Dot(ref m2.Row0) + Row1.Dot(ref m2.Row1) + Row2.Dot(ref m2.Row2); + } + + + public double Determinant { get { double a11 = Row0.x, a12 = Row0.y, a13 = Row0.z, a21 = Row1.x, a22 = Row1.y, a23 = Row1.z, a31 = Row2.x, a32 = Row2.y, a33 = Row2.z; diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 20107b4f..6220377c 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -30,8 +30,8 @@ namespace g3 /// public class DMeshAABBTree3 : ISpatial { - DMesh3 mesh; - int mesh_timestamp; + protected DMesh3 mesh; + protected int mesh_timestamp; public DMeshAABBTree3(DMesh3 m, bool autoBuild = false) { @@ -1311,9 +1311,9 @@ public double TotalExtentSum() // storage for box nodes. // - box_to_index is a pointer into index_list // - box_centers and box_extents are the centers/extents of the bounding boxes - DVector box_to_index; - DVector box_centers; - DVector box_extents; + protected DVector box_to_index; + protected DVector box_centers; + protected DVector box_extents; // list of indices for a given box. There is *no* marker/sentinel between // boxes, you have to get the starting index from box_to_index[] @@ -1325,13 +1325,13 @@ public double TotalExtentSum() // internal box, with index (-index_list[i])-1 (shift-by-one in case actual value is 0!) // - if i > triangles_end and index_list[i] > 0, this is a two-child // internal box, with indices index_list[i]-1 and index_list[i+1]-1 - DVector index_list; + protected DVector index_list; // index_list[i] for i < triangles_end is a triangle-index list, otherwise box-index pair/single - int triangles_end = -1; + protected int triangles_end = -1; // box_to_index[root_index] is the root node of the tree - int root_index = -1; + protected int root_index = -1; @@ -2040,7 +2040,7 @@ double box_distance_sqr(int iBox, Vector3d p) } - bool box_contains(int iBox, Vector3d p) + protected bool box_contains(int iBox, Vector3d p) { // [TODO] this could be way faster... Vector3d c = (Vector3d)box_centers[iBox]; From 9f03660213f6747b18381244a2b30686ab3fbf79 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 11 May 2018 10:36:52 -0400 Subject: [PATCH 127/225] valid check --- spatial/DMeshAABBTree.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 6220377c..41f65334 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -103,6 +103,8 @@ public void Build(BuildStrategy eStrategy = BuildStrategy.TopDownMidpoint, } + public bool IsValid { get { return mesh_timestamp == mesh.ShapeTimestamp; } } + /// /// Does this ISpatial implementation support nearest-point query? (yes) From c3cbf65c68771ad476cff795ca02425149779cfb Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 11 May 2018 17:21:16 -0400 Subject: [PATCH 128/225] buffer util functions --- core/BufferUtil.cs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/BufferUtil.cs b/core/BufferUtil.cs index 1dc5a938..4a8c9167 100644 --- a/core/BufferUtil.cs +++ b/core/BufferUtil.cs @@ -464,6 +464,47 @@ static public IndexArray4i ToIndexArray4i(byte[] buffer) } + /// + /// convert int array to bytes + /// + static public byte[] ToBytes(int[] array) + { + byte[] result = new byte[array.Length * sizeof(int)]; + Buffer.BlockCopy(array, 0, result, 0, result.Length); + return result; + } + + /// + /// convert short array to bytes + /// + static public byte[] ToBytes(short[] array) + { + byte[] result = new byte[array.Length * sizeof(short)]; + Buffer.BlockCopy(array, 0, result, 0, result.Length); + return result; + } + + /// + /// convert float array to bytes + /// + static public byte[] ToBytes(float[] array) + { + byte[] result = new byte[array.Length * sizeof(float)]; + Buffer.BlockCopy(array, 0, result, 0, result.Length); + return result; + } + + /// + /// convert double array to bytes + /// + static public byte[] ToBytes(double[] array) + { + byte[] result = new byte[array.Length * sizeof(double)]; + Buffer.BlockCopy(array, 0, result, 0, result.Length); + return result; + } + + /// From a7dfecd7f472e30a11d5156ac42ca43b24b351c0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 16 May 2018 11:14:42 -0400 Subject: [PATCH 129/225] xform normals in meshtransforms.rotate --- mesh/MeshTransforms.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mesh/MeshTransforms.cs b/mesh/MeshTransforms.cs index a784c597..99cfe776 100644 --- a/mesh/MeshTransforms.cs +++ b/mesh/MeshTransforms.cs @@ -63,11 +63,14 @@ public static Vector3d Rotate(Vector3d pos, Vector3d origin, Quaterniond rotatio } public static void Rotate(IDeformableMesh mesh, Vector3d origin, Quaterniond rotation) { + bool bHasNormals = mesh.HasVertexNormals; int NV = mesh.MaxVertexID; for (int vid = 0; vid < NV; ++vid) { if (mesh.IsVertex(vid)) { Vector3d v = rotation * (mesh.GetVertex(vid) - origin) + origin; mesh.SetVertex(vid, v); + if ( bHasNormals ) + mesh.SetVertexNormal(vid, (Vector3f)(rotation * mesh.GetVertexNormal(vid)) ); } } } From 4a59fcb5b538d75bf1f15325e5efc9968dac36e1 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 19 May 2018 11:30:40 -0400 Subject: [PATCH 130/225] returns wrong value for very small inputs unless we clamp --- math/MathUtil.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/math/MathUtil.cs b/math/MathUtil.cs index 6c5723a6..6f00b591 100644 --- a/math/MathUtil.cs +++ b/math/MathUtil.cs @@ -371,12 +371,12 @@ public static float SmoothRise0To1(float fX, float yshift, float xZero, float sp } public static float WyvillRise01(float fX) { - float d = 1 - fX * fX; - return (d >= 0) ? 1 - (d * d * d) : 0; + float d = MathUtil.Clamp(1.0f - fX*fX, 0.0f, 1.0f); + return 1 - (d * d * d); } public static double WyvillRise01(double fX) { - double d = 1 - fX * fX; - return (d >= 0) ? 1 - (d * d * d) : 0; + double d = MathUtil.Clamp(1.0 - fX*fX, 0.0, 1.0); + return 1 - (d * d * d); } public static float WyvillFalloff01(float fX) { From 2244af3eeab2de8c2cf85c99e0fbdf0d0ad3ef6e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 20 May 2018 15:00:20 -0400 Subject: [PATCH 131/225] utility fn --- core/SafeCollections.cs | 13 +++++++++++++ io/SVGWriter.cs | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/core/SafeCollections.cs b/core/SafeCollections.cs index ef408fce..947054f3 100644 --- a/core/SafeCollections.cs +++ b/core/SafeCollections.cs @@ -33,6 +33,19 @@ public void SafeAdd(T value) spinlock.Exit(); } + + public void SafeOperation(Action> opF) + { + bool lockTaken = false; + while (lockTaken == false) + spinlock.Enter(ref lockTaken); + + opF(List); + + spinlock.Exit(); + } + + public List Result { get { return List; } } diff --git a/io/SVGWriter.cs b/io/SVGWriter.cs index 549bcf17..187a4438 100644 --- a/io/SVGWriter.cs +++ b/io/SVGWriter.cs @@ -268,6 +268,14 @@ public static void QuickWrite(List polygons1, string color1, f + public static void QuickWrite(DGraph2 graph, string sPath, float line_width = 1) + { + SVGWriter writer = new SVGWriter(); + Style style = SVGWriter.Style.Outline("black", line_width); + writer.AddGraph(graph, style); + writer.Write(sPath); + } + From 0822bf9e22bb0f062cbd51ca5c57df4b3866659a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 20 May 2018 15:00:38 -0400 Subject: [PATCH 132/225] don't add obviously degenerate edge --- mesh_ops/MeshIsoCurves.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs index d67ae3f8..6169ba32 100644 --- a/mesh_ops/MeshIsoCurves.cs +++ b/mesh_ops/MeshIsoCurves.cs @@ -163,9 +163,11 @@ protected void compute_full(IEnumerable Triangles, bool bIsFullMeshHint = f int cross_vid = add_or_append_vertex(cross); add_edge_pos(triVerts[i], triVerts[j], cross); - int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); - if (graph_eid >= 0 && WantGraphEdgeInfo) - add_edge_vert(graph_eid, tid, triEdges[(z0+1)%3], triVerts[z0], new Index2i(vert_vid, cross_vid)); + if (vert_vid != cross_vid) { + int graph_eid = Graph.AppendEdge(vert_vid, cross_vid, (int)TriangleCase.EdgeVertex); + if (graph_eid >= 0 && WantGraphEdgeInfo) + add_edge_vert(graph_eid, tid, triEdges[(z0 + 1) % 3], triVerts[z0], new Index2i(vert_vid, cross_vid)); + } // else degenerate edge } } else { From e569636f6b4b5a804db28c9deb42b62188127d24 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 22 May 2018 10:44:52 -0400 Subject: [PATCH 133/225] added ProgressCancel, use to allow cancellation of computation in Reducer and Remesher --- core/ProgressCancel.cs | 58 +++++++++++++++++++++++++++++++++++++++++ geometry3Sharp.csproj | 1 + mesh/MeshRefinerBase.cs | 13 +++++++++ mesh/Reducer.cs | 18 +++++++++++++ mesh/Remesher.cs | 11 ++++++++ 5 files changed, 101 insertions(+) create mode 100644 core/ProgressCancel.cs diff --git a/core/ProgressCancel.cs b/core/ProgressCancel.cs new file mode 100644 index 00000000..0a831229 --- /dev/null +++ b/core/ProgressCancel.cs @@ -0,0 +1,58 @@ +using System; + +namespace g3 +{ + /// + /// interface that provides a cancel function + /// + public interface ICancelSource + { + bool Cancelled(); + } + + + /// + /// Just wraps a func as an ICancelSource + /// + public class CancelFunction : ICancelSource + { + public Func CancelF; + public CancelFunction(Func cancelF) { + CancelF = cancelF; + } + public bool Cancelled() { return CancelF(); } + } + + + /// + /// This class is intended to be passed to long-running computes to + /// 1) provide progress info back to caller (not implemented yet) + /// 2) allow caller to cancel the computation + /// + public class ProgressCancel + { + public ICancelSource Source; + + bool WasCancelled = false; // will be set to true if CancelF() ever returns true + + public ProgressCancel(ICancelSource source) + { + Source = source; + } + public ProgressCancel(Func cancelF) + { + Source = new CancelFunction(cancelF); + } + + /// + /// Check if client would like to cancel + /// + public bool Cancelled() + { + if (WasCancelled) + return true; + WasCancelled = Source.Cancelled(); + return WasCancelled; + } + } +} diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index f2318307..1d998af5 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -78,6 +78,7 @@ + diff --git a/mesh/MeshRefinerBase.cs b/mesh/MeshRefinerBase.cs index 82bd2124..0f124892 100644 --- a/mesh/MeshRefinerBase.cs +++ b/mesh/MeshRefinerBase.cs @@ -50,6 +50,19 @@ public void SetExternalConstraints(MeshConstraints cons) } + /// + /// Set this to be able to cancel running remesher + /// + public ProgressCancel Progress = null; + + /// + /// if this returns true, abort computation. + /// + protected virtual bool Cancelled() { + return (Progress == null) ? false : Progress.Cancelled(); + } + + protected double edge_flip_metric(ref Vector3d n0, ref Vector3d n1) { if (edge_flip_tol == 0) { diff --git a/mesh/Reducer.cs b/mesh/Reducer.cs index 7ef155ec..152e3c1b 100644 --- a/mesh/Reducer.cs +++ b/mesh/Reducer.cs @@ -81,8 +81,14 @@ public virtual void DoReduce() begin_setup(); Precompute(); + if (Cancelled()) + return; InitializeVertexQuadrics(); + if (Cancelled()) + return; InitializeQueue(); + if (Cancelled()) + return; end_setup(); begin_ops(); @@ -103,6 +109,8 @@ public virtual void DoReduce() int eid = EdgeQueue.Dequeue(); if (!mesh.IsEdge(eid)) continue; + if (Cancelled()) + return; int vKept; ProcessResult result = CollapseEdge(eid, EdgeQuadrics[eid].collapse_pt, out vKept); @@ -114,6 +122,9 @@ public virtual void DoReduce() end_collapse(); end_ops(); + if (Cancelled()) + return; + Reproject(); end_pass(); @@ -170,6 +181,8 @@ public virtual void FastCollapsePass(double fMinEdgeLength) begin_setup(); Precompute(); + if (Cancelled()) + return; end_setup(); begin_ops(); @@ -183,6 +196,8 @@ public virtual void FastCollapsePass(double fMinEdgeLength) continue; if (mesh.IsBoundaryEdge(eid)) continue; + if (Cancelled()) + return; mesh.GetEdgeV(eid, ref va, ref vb); if (va.DistanceSquared(ref vb) > min_sqr) @@ -200,6 +215,9 @@ public virtual void FastCollapsePass(double fMinEdgeLength) end_collapse(); end_ops(); + if (Cancelled()) + return; + Reproject(); end_pass(); diff --git a/mesh/Remesher.cs b/mesh/Remesher.cs index f555e857..be375627 100644 --- a/mesh/Remesher.cs +++ b/mesh/Remesher.cs @@ -171,10 +171,15 @@ public virtual void BasicRemeshPass() { if (result == ProcessResult.Ok_Collapsed || result == ProcessResult.Ok_Flipped || result == ProcessResult.Ok_Split) ModifiedEdgesLastPass++; } + if (Cancelled()) // expensive to check every iter? + return; cur_eid = next_edge(cur_eid, out done); } while (done == false); end_ops(); + if (Cancelled()) + return; + begin_smooth(); if (EnableSmoothing && SmoothSpeedT > 0) { if (EnableSmoothInPlace) @@ -185,6 +190,9 @@ public virtual void BasicRemeshPass() { } end_smooth(); + if (Cancelled()) + return; + begin_project(); if (target != null && ProjectionMode == TargetProjectionMode.AfterRefinement) { FullProjectionPass(); @@ -192,6 +200,9 @@ public virtual void BasicRemeshPass() { } end_project(); + if (Cancelled()) + return; + end_pass(); } From d2a0063e423d3f33314b69c1188757674bdabf84 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 22 May 2018 17:51:24 -0400 Subject: [PATCH 134/225] sequence composition --- math/TransformSequence.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/math/TransformSequence.cs b/math/TransformSequence.cs index 28081a9f..8880d8d8 100644 --- a/math/TransformSequence.cs +++ b/math/TransformSequence.cs @@ -59,6 +59,17 @@ public TransformSequence() Operations = new List(); } + public TransformSequence(TransformSequence copy) + { + Operations = new List(copy.Operations); + } + + + + public void Append(TransformSequence sequence) + { + Operations.AddRange(sequence.Operations); + } public void AppendTranslation(Vector3d dv) From a33e7bf09da2fa5e448f00ad405ea8d0e1f44e8c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 22 May 2018 22:25:23 -0400 Subject: [PATCH 135/225] refactor point/tri distance calc so we can call statically. add IOrientedProjectionTarget, implement in MeshProjectionTarget --- distance/DistPoint3Triangle3.cs | 24 ++++++++++++++++-------- math/Triangle3.cs | 10 ++++++++++ spatial/BasicProjectionTargets.cs | 20 +++++++++++++++++--- spatial/SpatialInterfaces.cs | 5 +++++ 4 files changed, 48 insertions(+), 11 deletions(-) diff --git a/distance/DistPoint3Triangle3.cs b/distance/DistPoint3Triangle3.cs index f54725d5..5187fba3 100644 --- a/distance/DistPoint3Triangle3.cs +++ b/distance/DistPoint3Triangle3.cs @@ -52,6 +52,13 @@ public double GetSquared() if (DistanceSquared >= 0) return DistanceSquared; + DistanceSquared = DistanceSqr(ref point, ref triangle, out TriangleClosest, out TriangleBaryCoords); + return DistanceSquared; + } + + + public static double DistanceSqr(ref Vector3d point, ref Triangle3d triangle, out Vector3d closestPoint, out Vector3d baryCoords ) + { Vector3d diff = triangle.V0 - point; Vector3d edge0 = triangle.V1 - triangle.V0; Vector3d edge1 = triangle.V2 - triangle.V0; @@ -213,16 +220,17 @@ public double GetSquared() } } } + closestPoint = triangle.V0 + s * edge0 + t * edge1; + baryCoords = new Vector3d(1 - s - t, s, t); // Account for numerical round-off error. - if (sqrDistance < 0) { - sqrDistance = 0; - } - DistanceSquared = sqrDistance; - - TriangleClosest = triangle.V0 + s * edge0 + t * edge1; - TriangleBaryCoords = new Vector3d(1 - s - t, s, t); - return sqrDistance; + return Math.Max(sqrDistance, 0); } + + + + + + } } diff --git a/math/Triangle3.cs b/math/Triangle3.cs index c918870e..5500c98a 100644 --- a/math/Triangle3.cs +++ b/math/Triangle3.cs @@ -20,6 +20,16 @@ public Vector3d this[int key] set { if (key == 0) V0 = value; else if (key == 1) V1 = value; else V2 = value; } } + public Vector3d Normal { + get { return MathUtil.Normal(ref V0, ref V1, ref V2); } + } + public double Area { + get { return MathUtil.Area(ref V0, ref V1, ref V2); } + } + public double AspectRatio { + get { return MathUtil.AspectRatio(ref V0, ref V1, ref V2); } + } + public Vector3d PointAt(double bary0, double bary1, double bary2) { return bary0 * V0 + bary1 * V1 + bary2 * V2; diff --git a/spatial/BasicProjectionTargets.cs b/spatial/BasicProjectionTargets.cs index a5bb6168..71b7e7de 100644 --- a/spatial/BasicProjectionTargets.cs +++ b/spatial/BasicProjectionTargets.cs @@ -5,7 +5,7 @@ namespace g3 { - public class MeshProjectionTarget : IProjectionTarget + public class MeshProjectionTarget : IOrientedProjectionTarget { public DMesh3 Mesh { get; set; } public ISpatial Spatial { get; set; } @@ -28,8 +28,22 @@ public MeshProjectionTarget(DMesh3 mesh) public Vector3d Project(Vector3d vPoint, int identifier = -1) { int tNearestID = Spatial.FindNearestTriangle(vPoint); - DistPoint3Triangle3 q = MeshQueries.TriangleDistance(Mesh, tNearestID, vPoint); - return q.TriangleClosest; + Triangle3d triangle = new Triangle3d(); + Mesh.GetTriVertices(tNearestID, ref triangle.V0, ref triangle.V1, ref triangle.V2); + Vector3d nearPt, bary; + DistPoint3Triangle3.DistanceSqr(ref vPoint, ref triangle, out nearPt, out bary); + return nearPt; + } + + public Vector3d Project(Vector3d vPoint, out Vector3d vProjectNormal, int identifier = -1) + { + int tNearestID = Spatial.FindNearestTriangle(vPoint); + Triangle3d triangle = new Triangle3d(); + Mesh.GetTriVertices(tNearestID, ref triangle.V0, ref triangle.V1, ref triangle.V2); + Vector3d nearPt, bary; + DistPoint3Triangle3.DistanceSqr(ref vPoint, ref triangle, out nearPt, out bary); + vProjectNormal = triangle.Normal; + return nearPt; } /// diff --git a/spatial/SpatialInterfaces.cs b/spatial/SpatialInterfaces.cs index 956b76b6..2c1225ec 100644 --- a/spatial/SpatialInterfaces.cs +++ b/spatial/SpatialInterfaces.cs @@ -34,6 +34,11 @@ public interface IProjectionTarget Vector3d Project(Vector3d vPoint, int identifier = -1); } + public interface IOrientedProjectionTarget : IProjectionTarget + { + Vector3d Project(Vector3d vPoint, out Vector3d vProjectNormal, int identifier = -1); + } + public interface IIntersectionTarget { bool HasNormal { get; } From d7912e90b420ac7410fa9d591876807b0cf0af8b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 22 May 2018 23:16:07 -0400 Subject: [PATCH 136/225] add TransformedMeshProjectionTarget --- math/TransformSequence.cs | 2 +- spatial/BasicProjectionTargets.cs | 57 +++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/math/TransformSequence.cs b/math/TransformSequence.cs index 8880d8d8..a8197e69 100644 --- a/math/TransformSequence.cs +++ b/math/TransformSequence.cs @@ -190,7 +190,7 @@ public Vector3d TransformP(Vector3d p) /// - /// Apply transforms to point + /// Apply transforms to vector. Includes scaling. /// public Vector3d TransformV(Vector3d v) { diff --git a/spatial/BasicProjectionTargets.cs b/spatial/BasicProjectionTargets.cs index 71b7e7de..6d274c5c 100644 --- a/spatial/BasicProjectionTargets.cs +++ b/spatial/BasicProjectionTargets.cs @@ -5,6 +5,10 @@ namespace g3 { + /// + /// MeshProjectionTarget provides an IProjectionTarget interface to a mesh + spatial data structure. + /// Use to project points to mesh surface. + /// public class MeshProjectionTarget : IOrientedProjectionTarget { public DMesh3 Mesh { get; set; } @@ -25,7 +29,7 @@ public MeshProjectionTarget(DMesh3 mesh) Spatial = new DMeshAABBTree3(mesh, true); } - public Vector3d Project(Vector3d vPoint, int identifier = -1) + public virtual Vector3d Project(Vector3d vPoint, int identifier = -1) { int tNearestID = Spatial.FindNearestTriangle(vPoint); Triangle3d triangle = new Triangle3d(); @@ -35,7 +39,7 @@ public Vector3d Project(Vector3d vPoint, int identifier = -1) return nearPt; } - public Vector3d Project(Vector3d vPoint, out Vector3d vProjectNormal, int identifier = -1) + public virtual Vector3d Project(Vector3d vPoint, out Vector3d vProjectNormal, int identifier = -1) { int tNearestID = Spatial.FindNearestTriangle(vPoint); Triangle3d triangle = new Triangle3d(); @@ -69,11 +73,60 @@ public static MeshProjectionTarget Auto(DMesh3 mesh, IEnumerable triangles, DSubmesh3 submesh = new DSubmesh3(mesh, targetRegion); return new MeshProjectionTarget(submesh.SubMesh); } + } + + + + + /// + /// Extension of MeshProjectionTarget that allows the target to have a transformation + /// relative to it's internal space. Call SetTransform(), or initialize the transforms yourself + /// + public class TransformedMeshProjectionTarget : MeshProjectionTarget + { + public TransformSequence SourceToTargetXForm; + public TransformSequence TargetToSourceXForm; + + public TransformedMeshProjectionTarget() { } + public TransformedMeshProjectionTarget(DMesh3 mesh, ISpatial spatial) : base(mesh, spatial) + { + } + public TransformedMeshProjectionTarget(DMesh3 mesh) : base(mesh) + { + } + public void SetTransform(TransformSequence sourceToTargetX) + { + SourceToTargetXForm = sourceToTargetX; + TargetToSourceXForm = SourceToTargetXForm.MakeInverse(); + } + + public override Vector3d Project(Vector3d vPoint, int identifier = -1) + { + Vector3d vTargetPt = SourceToTargetXForm.TransformP(vPoint); + Vector3d vTargetProj = base.Project(vTargetPt, identifier); + return TargetToSourceXForm.TransformP(vTargetProj); + } + + + public override Vector3d Project(Vector3d vPoint, out Vector3d vProjectNormal, int identifier = -1) + { + Vector3d vTargetPt = SourceToTargetXForm.TransformP(vPoint); + Vector3d vTargetProjNormal; + Vector3d vTargetProj = base.Project(vTargetPt, out vTargetProjNormal, identifier); + vProjectNormal = TargetToSourceXForm.TransformV(vTargetProjNormal).Normalized; + return TargetToSourceXForm.TransformP(vTargetProj); + } } + + + + + + public class PlaneProjectionTarget : IProjectionTarget { public Vector3d Origin; From 64fd259687a5dab4c5f15b10c019ab4a878dd2c2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 25 May 2018 12:36:05 -0400 Subject: [PATCH 137/225] fix dotnet 3.5 compilation --- core/BufferUtil.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/BufferUtil.cs b/core/BufferUtil.cs index 4a8c9167..59bba42b 100644 --- a/core/BufferUtil.cs +++ b/core/BufferUtil.cs @@ -513,7 +513,11 @@ static public byte[] ToBytes(double[] array) static public byte[] CompressZLib(byte[] buffer, bool bFast) { MemoryStream ms = new MemoryStream(); +#if G3_USING_UNITY && (NET_2_0 || NET_2_0_SUBSET) + DeflateStream zip = new DeflateStream(ms, CompressionMode.Compress); +#else DeflateStream zip = new DeflateStream(ms, (bFast) ? CompressionLevel.Fastest : CompressionLevel.Optimal, true); +#endif zip.Write(buffer, 0, buffer.Length); zip.Close(); ms.Position = 0; From 16ae9582354acbc139299ac26305852bb745e8c6 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 25 May 2018 12:36:14 -0400 Subject: [PATCH 138/225] warning fixes --- comp_geom/GraphCells2d.cs | 2 +- curve/PolyLine2d.cs | 1 - distance/DistRay3Segment3.cs | 2 +- io/StandardMeshReader.cs | 8 +++----- mesh/MeshUtil.cs | 2 +- mesh/NTMesh3.cs | 2 +- spatial/TriangleBinsGrid2d.cs | 4 ++++ 7 files changed, 11 insertions(+), 10 deletions(-) diff --git a/comp_geom/GraphCells2d.cs b/comp_geom/GraphCells2d.cs index 9e8f501b..e4c794d6 100644 --- a/comp_geom/GraphCells2d.cs +++ b/comp_geom/GraphCells2d.cs @@ -62,7 +62,7 @@ public void FindCells() int start_vid = idx.a; int wid = idx.b; - int e0 = wedges[start_vid][wid].a; + int e0 = wedges[start_vid][wid].a; e0 = e0+1-1; // get rid of unused variable warning, want to keep this for debugging int e1 = wedges[start_vid][wid].b; loopv.Clear(); diff --git a/curve/PolyLine2d.cs b/curve/PolyLine2d.cs index a0d201e5..213ff496 100644 --- a/curve/PolyLine2d.cs +++ b/curve/PolyLine2d.cs @@ -343,7 +343,6 @@ public PolyLine2d Transform(ITransform2 xform) static public PolyLine2d MakeBoxSpiral(Vector2d center, double len, double spacing) { - double d = spacing / 2; PolyLine2d pline = new PolyLine2d(); pline.AppendVertex(center); diff --git a/distance/DistRay3Segment3.cs b/distance/DistRay3Segment3.cs index 57f2bc04..e053cc72 100644 --- a/distance/DistRay3Segment3.cs +++ b/distance/DistRay3Segment3.cs @@ -46,7 +46,7 @@ static public double MinDistance(Ray3d r, Segment3d s) { } static public double MinDistanceSegmentParam(Ray3d r, Segment3d s) { double rayt, segt; - double dsqr = SquaredDistance(ref r, ref s, out rayt, out segt); + /*double dsqr = */SquaredDistance(ref r, ref s, out rayt, out segt); return segt; } diff --git a/io/StandardMeshReader.cs b/io/StandardMeshReader.cs index 3b903e2d..d1e5b258 100644 --- a/io/StandardMeshReader.cs +++ b/io/StandardMeshReader.cs @@ -305,11 +305,9 @@ public IOReadResult ReadFile(string sFilename, IMeshBuilder builder, ReadOptions public IOReadResult ReadFile(Stream stream, IMeshBuilder builder, ReadOptions options, ParsingMessagesHandler messages) { // detect binary STL - BinaryReader binReader = new BinaryReader(stream); - byte[] header = binReader.ReadBytes(80); - bool bIsBinary = false; - - bIsBinary = Util.IsBinaryStream(stream, 500); + //BinaryReader binReader = new BinaryReader(stream); + //byte[] header = binReader.ReadBytes(80); + bool bIsBinary = Util.IsBinaryStream(stream, 500); // [RMS] Thingi10k includes some files w/ unicode string in ascii header... // How can we detect this? can we check that each character is a character? diff --git a/mesh/MeshUtil.cs b/mesh/MeshUtil.cs index c4dcfa87..251203fa 100644 --- a/mesh/MeshUtil.cs +++ b/mesh/MeshUtil.cs @@ -143,7 +143,7 @@ public static bool CheckIfEdgeFlipCreatesFlip(DMesh3 mesh, int eID, double flip_ Index2i ov = mesh.GetEdgeOpposingV(eID); int a = einfo.a, b = einfo.b, c = ov.a, d = ov.b; - int t0 = einfo.c, t1 = einfo.d; + int t0 = einfo.c; Vector3d vC = mesh.GetVertex(c), vD = mesh.GetVertex(d); Index3i tri_v = mesh.GetTriangle(t0); diff --git a/mesh/NTMesh3.cs b/mesh/NTMesh3.cs index e24b9f01..145b585e 100644 --- a/mesh/NTMesh3.cs +++ b/mesh/NTMesh3.cs @@ -1617,7 +1617,7 @@ public bool CheckValidity(FailMode eFailMode = FailMode.Throw) CheckOrFailF(IsEdge(edge)); } - List vTris = new List(), vTris2 = new List(); + List vTris = new List(); GetVtxTriangles(vID, vTris); CheckOrFailF(vertices_refcount.refCount(vID) == vTris.Count + 1); diff --git a/spatial/TriangleBinsGrid2d.cs b/spatial/TriangleBinsGrid2d.cs index 99702f12..d9b44b7d 100644 --- a/spatial/TriangleBinsGrid2d.cs +++ b/spatial/TriangleBinsGrid2d.cs @@ -49,6 +49,10 @@ public TriangleBinsGrid2d(AxisAlignedBox2d bounds, int numCells) } + public AxisAlignedBox2d Bounds { + get { return bounds; } + } + /// /// Insert triangle. This function is thread-safe, uses a SpinLock internally /// From 652ebcf3c3ce862bbb19cca178e028222e0cbf78 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 25 May 2018 12:46:10 -0400 Subject: [PATCH 139/225] allocate --- spatial/TriangleBinsGrid2d.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spatial/TriangleBinsGrid2d.cs b/spatial/TriangleBinsGrid2d.cs index d9b44b7d..1d3484fc 100644 --- a/spatial/TriangleBinsGrid2d.cs +++ b/spatial/TriangleBinsGrid2d.cs @@ -29,7 +29,7 @@ public class TriangleBinsGrid2d int bins_x, bins_y; AxisAlignedBox2i grid_bounds; - SpinLock spinlock; + SpinLock spinlock = new SpinLock(); /// /// "invalid" value will be returned by queries if no valid result is found (eg bounded-distance query) From 053728b2555ec795d9e1cb8875d0657706e3f639 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 28 May 2018 19:32:57 -0400 Subject: [PATCH 140/225] add DVector.copy, SetVerticesMeshChange --- core/DVector.cs | 19 ++++++++++++++- mesh/DMesh3Changes.cs | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/core/DVector.cs b/core/DVector.cs index 76de9c56..fcda514e 100644 --- a/core/DVector.cs +++ b/core/DVector.cs @@ -52,7 +52,6 @@ public DVector(IEnumerable init) iCurBlockUsed = 0; Blocks = new List(); Blocks.Add(new T[nBlockSize]); - // AAAHHH this could be so more efficient... foreach (T v in init) Add(v); } @@ -179,6 +178,24 @@ public void resize(int count) { } + public void copy(DVector copyIn) + { + if (this.Blocks != null && copyIn.Blocks.Count == this.Blocks.Count) { + int N = copyIn.Blocks.Count; + for (int k = 0; k < N; ++k) + Array.Copy(copyIn.Blocks[k], this.Blocks[k], copyIn.Blocks[k].Length); + iCurBlock = copyIn.iCurBlock; + iCurBlockUsed = copyIn.iCurBlockUsed; + } else { + resize(copyIn.size); + int N = copyIn.Blocks.Count; + for (int k = 0; k < N; ++k) + Array.Copy(copyIn.Blocks[k], this.Blocks[k], copyIn.Blocks[k].Length); + iCurBlock = copyIn.iCurBlock; + iCurBlockUsed = copyIn.iCurBlockUsed; + } + } + public T this[int i] diff --git a/mesh/DMesh3Changes.cs b/mesh/DMesh3Changes.cs index 384d96eb..3f673d57 100644 --- a/mesh/DMesh3Changes.cs +++ b/mesh/DMesh3Changes.cs @@ -111,6 +111,60 @@ void initialize_buffers(DMesh3 mesh, MeshComponents components) + /// + /// Mesh change for full-mesh vertex deformations - more efficient than ModifyVerticesMeshChange. + /// Note that this does not enforce that vertex count does not change! + /// + public class SetVerticesMeshChange + { + public DVector OldPositions, NewPositions; + public DVector OldNormals, NewNormals; + public DVector OldColors, NewColors; + public DVector OldUVs, NewUVs; + + public Action OnApplyF; + public Action OnRevertF; + + public SetVerticesMeshChange() + { + } + + public void Apply(DMesh3 mesh) + { + if ( NewPositions != null ) + mesh.VerticesBuffer.copy(NewPositions); + if (mesh.HasVertexNormals && NewNormals != null) + mesh.NormalsBuffer.copy(NewNormals); + if (mesh.HasVertexColors&& NewColors != null) + mesh.ColorsBuffer.copy(NewColors); + if (mesh.HasVertexUVs && NewUVs != null) + mesh.UVBuffer.copy(NewUVs); + if (OnApplyF != null) + OnApplyF(this); + } + + + public void Revert(DMesh3 mesh) + { + if ( OldPositions != null ) + mesh.VerticesBuffer.copy(OldPositions); + if (mesh.HasVertexNormals && OldNormals != null) + mesh.NormalsBuffer.copy(OldNormals); + if (mesh.HasVertexColors && OldColors != null) + mesh.ColorsBuffer.copy(OldColors); + if (mesh.HasVertexUVs && OldUVs != null) + mesh.UVBuffer.copy(OldUVs); + if (OnRevertF != null) + OnRevertF(this); + } + } + + + + + + + From 9da01981afa45f6b65029c0329dc2aa68dfeb213 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 29 May 2018 22:37:29 -0400 Subject: [PATCH 141/225] catch a case in DMesh3.MergeEdges that would otherwise result in the mesh being broken --- mesh/DMesh3_edge_operators.cs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/mesh/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs index edea1ea8..1df7b540 100644 --- a/mesh/DMesh3_edge_operators.cs +++ b/mesh/DMesh3_edge_operators.cs @@ -850,6 +850,33 @@ public MeshResult MergeEdges(int eKeep, int eDiscard, out MergeEdgesInfo merge_i if (b != d && find_edge(b, d) != DMesh3.InvalidID) return MeshResult.Failed_InvalidNeighbourhood; + // if vertices at either end already share a common neighbour vertex, and we + // do the merge, that would create duplicate edges. This is something like the + // 'link condition' in edge collapses. + // Note that we have to catch cases where both edges to the shared vertex are + // boundary edges, in that case we will also merge this edge later on + if ( a != c ) { + int ea = 0, ec = 0, other_v = (b == d) ? b : -1; + foreach ( int cnbr in VtxVerticesItr(c) ) { + if (cnbr != other_v && (ea = find_edge(a, cnbr)) != DMesh3.InvalidID) { + ec = find_edge(c, cnbr); + if (IsBoundaryEdge(ea) == false || IsBoundaryEdge(ec) == false) + return MeshResult.Failed_InvalidNeighbourhood; + } + } + } + if ( b != d ) { + int eb = 0, ed = 0, other_v = (a == c) ? a : -1; + foreach ( int dnbr in VtxVerticesItr(d)) { + if (dnbr != other_v && (eb = find_edge(b, dnbr)) != DMesh3.InvalidID) { + ed = find_edge(d, dnbr); + if (IsBoundaryEdge(eb) == false || IsBoundaryEdge(ed) == false) + return MeshResult.Failed_InvalidNeighbourhood; + } + } + } + + // [TODO] this acts on each interior tri twice. could avoid using vtx-tri iterator? if (a != c) { // replace c w/ a in edges and tris connected to c, and move edges to a @@ -1113,7 +1140,13 @@ List vertex_edges_list(int vid) { return new List( vertex_edges.ValueItr(vid) ); } - + List vertex_vertices_list(int vid) + { + List vnbrs = new List(); + foreach (int eid in vertex_edges.ValueItr(vid)) + vnbrs.Add(edge_other_v(eid, vid)); + return vnbrs; + } void set_edge_vertices(int eID, int a, int b) { From d63ebb686abec14f6b70a87e9630b86fe62d9cb0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 29 May 2018 22:39:55 -0400 Subject: [PATCH 142/225] DMesh3.IsBowtieVertex did not properly handle some cases (eg two tetrahedra connected at a vtx). Now doing a connected-component walk around vtx. Note that this is the same code necessary for in-order walk around a manifold one ring... --- mesh/DMesh3.cs | 52 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 93c95c5c..16e1be7e 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -1934,11 +1934,53 @@ public List GetAllVertexGroups(int vID) { public bool IsBowtieVertex(int vID) { if (vertices_refcount.isValid(vID)) { - int nTris = GetVtxTriangleCount(vID); - int vtx_edge_count = GetVtxEdgeCount(vID); - if (!(nTris == vtx_edge_count || nTris == vtx_edge_count - 1)) - return true; - return false; + // find a boundary edge to start at + int start_eid = -1; + bool start_at_boundary = false; + foreach (int eid in vertex_edges.ValueItr(vID)) { + if (edges[4 * eid + 3] == DMesh3.InvalidID) { + start_at_boundary = true; + start_eid = eid; + break; + } + } + // if no boundary edge, start at arbitrary edge + if (start_eid == -1) + start_eid = vertex_edges.First(vID); + // initial triangle + int start_tid = edges[4 * start_eid + 2]; + + int prev_tid = start_tid; + int prev_eid = start_eid; + + // walk forward to next edge. if we hit start edge or boundary edge, + // we are done the walk. count number of edges as we go. + int count = 1; + while (true) { + int i = 3 * prev_tid; + Index3i tv = new Index3i(triangles[i], triangles[i+1], triangles[i+2]); + Index3i te = new Index3i(triangle_edges[i], triangle_edges[i+1], triangle_edges[i+2]); + int vert_idx = IndexUtil.find_tri_index(vID, ref tv); + int e1 = te[vert_idx], e2 = te[(vert_idx+2) % 3]; + int next_eid = (e1 == prev_eid) ? e2 : e1; + if (next_eid == start_eid) + break; + Index2i next_eid_tris = GetEdgeT(next_eid); + int next_tid = (next_eid_tris.a == prev_tid) ? next_eid_tris.b : next_eid_tris.a; + if (next_tid == DMesh3.InvalidID) { + break; + } + prev_eid = next_eid; + prev_tid = next_tid; + count++; + } + + // if we did not see all edges at vertex, we have a bowtie + int nEdges = vertex_edges.Count(vID); + int target_count = (start_at_boundary) ? nEdges - 1 : nEdges; + bool is_bowtie = (target_count != count); + return is_bowtie; + } else throw new Exception("DMesh3.IsBowtieVertex: " + vID + " is not a valid vertex"); } From 929fb1838996229461b3f956ce8fceb53143b815 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 29 May 2018 23:04:17 -0400 Subject: [PATCH 143/225] rest of loop is busted if we actually have no edges --- mesh/DMesh3.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index 16e1be7e..f2e95488 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -1934,6 +1934,10 @@ public List GetAllVertexGroups(int vID) { public bool IsBowtieVertex(int vID) { if (vertices_refcount.isValid(vID)) { + int nEdges = vertex_edges.Count(vID); + if (nEdges == 0) + return false; + // find a boundary edge to start at int start_eid = -1; bool start_at_boundary = false; @@ -1976,7 +1980,6 @@ public bool IsBowtieVertex(int vID) } // if we did not see all edges at vertex, we have a bowtie - int nEdges = vertex_edges.Count(vID); int target_count = (start_at_boundary) ? nEdges - 1 : nEdges; bool is_bowtie = (target_count != count); return is_bowtie; From 4ae00358e8c5ba0b3603a821aa2e3e5d98d1d2d2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 29 May 2018 23:05:03 -0400 Subject: [PATCH 144/225] Mesh.AppendTriangle can return multiple failure codes, InvalidID check is not sufficient. --- mesh/MeshEditor.cs | 16 +++++++++------- mesh_ops/SimpleHoleFiller.cs | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 8f201332..c9fc872b 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -72,7 +72,7 @@ public virtual int[] AddTriangleFan_OrderedVertexLoop(int center, int[] vertex_l Index3i newT = new Index3i(center, b, a); int new_tid = Mesh.AppendTriangle(newT, group_id); - if (new_tid == DMesh3.InvalidID) + if (new_tid < 0) goto operation_failed; new_tris[i] = new_tid; @@ -109,7 +109,7 @@ public virtual int[] AddTriangleFan_OrderedEdgeLoop(int center, int[] edge_loop, Index3i newT = new Index3i(center, b, a); int new_tid = Mesh.AppendTriangle(newT, group_id); - if (new_tid == DMesh3.InvalidID) + if (new_tid < 0) goto operation_failed; new_tris[i] = new_tid; @@ -156,7 +156,7 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); - if (tid1 == DMesh3.InvalidID || tid2 == DMesh3.InvalidID) + if (tid1 < 0 || tid2 < 0) goto operation_failed; new_tris[2 * i] = tid1; @@ -218,7 +218,7 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); - if (tid1 == DMesh3.InvalidID || tid2 == DMesh3.InvalidID) + if (tid1 < 0 || tid2 < 0) goto operation_failed; new_tris[2 * i] = tid1; @@ -268,7 +268,7 @@ public virtual int[] StitchSpan(IList vspan1, IList vspan2, int group_ int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); - if (tid1 == DMesh3.InvalidID || tid2 == DMesh3.InvalidID) + if (tid1 < 0 || tid2 < 0) goto operation_failed; new_tris[2 * i] = tid1; @@ -539,9 +539,10 @@ static int bowtie_sorter(List l1, List l2) { /// /// Disconnect all bowtie vertices in mesh. Iterates because sometimes - /// disconnecting a bowtie creates new bowties (how??) + /// disconnecting a bowtie creates new bowties (how??). + /// Returns number of remaining bowties after iterations. /// - public void DisconnectAllBowties(int nMaxIters = 10) + public int DisconnectAllBowties(int nMaxIters = 10) { List bowties = new List(MeshIterators.BowtieVertices(Mesh)); int iter = 0; @@ -550,6 +551,7 @@ public void DisconnectAllBowties(int nMaxIters = 10) DisconnectBowtie(vid); bowties = new List(MeshIterators.BowtieVertices(Mesh)); } + return bowties.Count; } diff --git a/mesh_ops/SimpleHoleFiller.cs b/mesh_ops/SimpleHoleFiller.cs index 5ac92f55..1b0e072f 100644 --- a/mesh_ops/SimpleHoleFiller.cs +++ b/mesh_ops/SimpleHoleFiller.cs @@ -41,7 +41,7 @@ public virtual bool Fill(int group_id = -1) if ( Loop.Vertices.Length == 3 ) { Index3i tri = new Index3i(Loop.Vertices[0], Loop.Vertices[2], Loop.Vertices[1]); int new_tid = Mesh.AppendTriangle(tri, group_id); - if (new_tid == DMesh3.InvalidID) + if (new_tid < 0) return false; NewTriangles = new int[1] { new_tid }; NewVertex = DMesh3.InvalidID; @@ -72,9 +72,9 @@ public virtual bool Fill(int group_id = -1) if ( NewTriangles == null ) { Mesh.RemoveVertex(NewVertex, true, false); NewVertex = DMesh3.InvalidID; - } - - return true; + return false; + } else + return true; } From 44e96b7ff678f22cceacd00e653b9e0a10841978 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 30 May 2018 14:04:03 -0400 Subject: [PATCH 145/225] always use 64-bit --- geometry3Sharp.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 1d998af5..5a6d1530 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -31,6 +31,7 @@ prompt 4 true + x64 From 2fc389a222da26cf05ddc5f9bb92bd5af7b21979 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 30 May 2018 22:28:43 -0400 Subject: [PATCH 146/225] add comment --- spatial/DMeshAABBTree.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 41f65334..359350f6 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -44,7 +44,9 @@ public DMeshAABBTree3(DMesh3 m, bool autoBuild = false) public DMesh3 Mesh { get { return mesh; } } - // if non-null, return false to ignore certain triangles + /// + /// If non-null, only triangle IDs that pass this filter (ie filter is true) are considered + /// public Func TriangleFilterF = null; From d386af2105b4491348ca4eed90514aa7dc0cfeb3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 31 May 2018 11:46:05 -0400 Subject: [PATCH 147/225] modulo iteration util --- math/MathUtil.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/math/MathUtil.cs b/math/MathUtil.cs index 6f00b591..d49d34c0 100644 --- a/math/MathUtil.cs +++ b/math/MathUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace g3 @@ -643,5 +644,20 @@ public static int PowerOf10(int n) { } + /// + /// Iterate from 0 to (nMax-1) using prime-modulo, so we see every index once, but not in-order + /// + public static IEnumerable ModuloIteration(int nMaxExclusive, int nPrime = 31337) + { + int i = 0; + bool done = false; + while (done == false) { + yield return i; + i = (i + nPrime) % nMaxExclusive; + done = (i == 0); + } + } + + } } From b40112f66ec4018d8e1dc6695e7f178c6243d877 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 1 Jun 2018 00:48:03 -0400 Subject: [PATCH 148/225] MeshEditor colors for box-append --- mesh/MeshEditor.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index c9fc872b..88a7f0d0 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -750,6 +750,10 @@ public void AppendBox(Frame3f frame, float size) AppendBox(frame, size * Vector3f.One); } public void AppendBox(Frame3f frame, Vector3f size) + { + AppendBox(frame, size, Colorf.White); + } + public void AppendBox(Frame3f frame, Vector3f size, Colorf color) { TrivialBox3Generator boxgen = new TrivialBox3Generator() { Box = new Box3d(frame, size), @@ -758,6 +762,8 @@ public void AppendBox(Frame3f frame, Vector3f size) boxgen.Generate(); DMesh3 mesh = new DMesh3(); boxgen.MakeMesh(mesh); + if (Mesh.HasVertexColors) + mesh.EnableVertexColors(color); AppendMesh(mesh, Mesh.AllocateTriangleGroup()); } public void AppendLine(Segment3d seg, float size) @@ -771,11 +777,22 @@ public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) MeshEditor editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos), size); } + public static void AppendBox(DMesh3 mesh, Vector3d pos, float size, Colorf color) + { + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(new Frame3f(pos), size*Vector3f.One, color); + } public static void AppendBox(DMesh3 mesh, Vector3d pos, Vector3d normal, float size) { MeshEditor editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos, normal), size); } + public static void AppendBox(DMesh3 mesh, Vector3d pos, Vector3d normal, float size, Colorf color) + { + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(new Frame3f(pos, normal), size*Vector3f.One, color); + } + public static void AppendLine(DMesh3 mesh, Segment3d seg, float size) { Frame3f f = new Frame3f(seg.Center); From c5817d4986a63e840cd61c1cada60e2583831fb0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 1 Jun 2018 17:19:05 -0400 Subject: [PATCH 149/225] util fn to erode open spurs from 3D graph --- curve/DGraph3Util.cs | 53 +++++++++++++++++++++++++++++++++++++++++++ geometry3Sharp.csproj | 3 ++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/curve/DGraph3Util.cs b/curve/DGraph3Util.cs index 65c94454..1bf54209 100644 --- a/curve/DGraph3Util.cs +++ b/curve/DGraph3Util.cs @@ -284,5 +284,58 @@ public static List WalkToNextNonRegularVtx(DGraph3 graph, int fromVtx, int + + + /// + /// Erode inwards from open boundary vertices of graph (ie vtx with single edge). + /// Resulting graph is not compact (!) + /// + public static void ErodeOpenSpurs(DGraph3 graph) + { + HashSet used = new HashSet(); // do we need this? + + // find boundary and junction vertices + HashSet boundaries = new HashSet(); + HashSet junctions = new HashSet(); + foreach (int vid in graph.VertexIndices()) { + if (graph.IsBoundaryVertex(vid)) + boundaries.Add(vid); + if (graph.IsJunctionVertex(vid)) + junctions.Add(vid); + } + + // walk paths from boundary vertices + foreach (int start_vid in boundaries) { + if (graph.IsVertex(start_vid) == false) + continue; + + int vid = start_vid; + int eid = graph.GetVtxEdges(vid)[0]; + if (used.Contains(eid)) + continue; + + List pathE = new List(); + if (pathE != null) + pathE.Add(eid); + while (true) { + used.Add(eid); + Index2i next = NextEdgeAndVtx(eid, vid, graph); + eid = next.a; + vid = next.b; + if (boundaries.Contains(vid) || junctions.Contains(vid)) + break; // done! + if (pathE != null) + pathE.Add(eid); + } + + // delete this path + foreach (int path_eid in pathE) + graph.RemoveEdge(path_eid, true); + } + + } + + + } } diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 5a6d1530..76ab14d6 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -22,6 +22,7 @@ prompt 4 true + AnyCPU pdbonly @@ -31,7 +32,7 @@ prompt 4 true - x64 + AnyCPU From f40deca2fb6ed1692023a11ade5d22243dcbd03f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 7 Jun 2018 15:00:14 -0400 Subject: [PATCH 150/225] color util fn --- color/Colorf.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/color/Colorf.cs b/color/Colorf.cs index b021e19c..b113bb4e 100644 --- a/color/Colorf.cs +++ b/color/Colorf.cs @@ -73,7 +73,10 @@ public void Subtract(Colorf o) { r -= o.r; g -= o.g; b -= o.b; a -= o.a; } - + public Colorf WithAlpha(float newAlpha) + { + return new Colorf(r, g, b, newAlpha); + } public static Colorf operator -(Colorf v) From 84a73cff52c3571e7c92c17eaeee3e24fd6bcb55 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 11 Jun 2018 00:34:34 -0400 Subject: [PATCH 151/225] util fn --- mesh/MeshEditor.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 88a7f0d0..c07e68d9 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -355,6 +355,17 @@ public static bool RemoveTriangles(DMesh3 Mesh, IEnumerable triangles, bool } + /// + /// Remove 'loner' triangles that have no connected neighbours. + /// + public static bool RemoveIsolatedTriangles(DMesh3 mesh) + { + MeshEditor editor = new MeshEditor(mesh); + return editor.RemoveTriangles((tid) => { + Index3i tnbrs = mesh.GetTriNeighbourTris(tid); + return (tnbrs.a == DMesh3.InvalidID && tnbrs.b == DMesh3.InvalidID && tnbrs.c == DMesh3.InvalidID); + }, true); + } From 4a0c338c45267ffd841d93699ba963e396a38a84 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 11 Jun 2018 00:35:15 -0400 Subject: [PATCH 152/225] detect cases where fill boundary loop will be incompatible with input boundary loop (should return more info here though) --- mesh_ops/PlanarHoleFiller.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesh_ops/PlanarHoleFiller.cs b/mesh_ops/PlanarHoleFiller.cs index 0d7f5c24..5a8fefd0 100644 --- a/mesh_ops/PlanarHoleFiller.cs +++ b/mesh_ops/PlanarHoleFiller.cs @@ -154,7 +154,8 @@ public bool Fill() if (insert.Apply()) { insert.Simplify(); polyVertices[pi] = insert.CurveVertices; - failed = false; + failed = (insert.Loops.Count != 1) || + (insert.Loops[0].VertexCount != polys[pi].VertexCount); } } if (failed) From 336f88dbd910740c7236479e789843141b3aecf2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 11 Jun 2018 11:07:27 -0400 Subject: [PATCH 153/225] add ImplicitShell3D impicit unary op op --- implicit/Implicit3d.cs | 45 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 424925ff..585eba13 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -171,12 +171,45 @@ public AxisAlignedBox3d Bounds() - /// - /// Boolean Union of two implicit functions, A OR B. - /// Assumption is that both have surface at zero isocontour and - /// negative is inside. - /// - public class ImplicitUnion3d : BoundedImplicitFunction3d + /// + /// remaps values so that values within given interval are negative, + /// and values outside this interval are positive. So, for a distance + /// field, this converts single isocontour into two nested isocontours + /// with zeros at interval a and b, with 'inside' in interval + /// + public class ImplicitShell3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public Interval1d Inside; + + public double Value(ref Vector3d pt) + { + double f = A.Value(ref pt); + if (f < Inside.a) + f = Inside.a - f; + else if (f > Inside.b) + f = f - Inside.b; + else f = -Math.Min(Math.Abs(f - Inside.a), Math.Abs(f - Inside.b)); + return f; + } + + public AxisAlignedBox3d Bounds() + { + AxisAlignedBox3d box = A.Bounds(); + box.Expand(Math.Max(0, Inside.b)); + return box; + } + } + + + + + /// + /// Boolean Union of two implicit functions, A OR B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitUnion3d : BoundedImplicitFunction3d { public BoundedImplicitFunction3d A; public BoundedImplicitFunction3d B; From 7aa8d30b64b58daf0fbda6c7a06a7dda0b4c183a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 11 Jun 2018 14:26:07 -0400 Subject: [PATCH 154/225] indexing util. maybe should be at a higher level because we need this all over the place? --- curve/DCurve3.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/curve/DCurve3.cs b/curve/DCurve3.cs index 3f0f2b6b..2eb8a2a4 100644 --- a/curve/DCurve3.cs +++ b/curve/DCurve3.cs @@ -205,6 +205,25 @@ public Vector3d Centroid(int i) } + public Index2i Neighbours(int i) + { + int NV = vertices.Count; + if (Closed) { + if (i == 0) + return new Index2i(NV-1, 1); + else + return new Index2i(i-1, (i+1) % NV); + } else { + if (i == 0) + return new Index2i(-1, 1); + else if (i == NV-1) + return new Index2i(NV-2, -1); + else + return new Index2i(i-1, i+1); + } + } + + /// /// Compute opening angle at vertex i in degrees /// From aafff988422fc09ab8db325d6c3a0b3f7287e8d5 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 12 Jun 2018 12:56:19 -0400 Subject: [PATCH 155/225] 2D arc length parameterization/sampling of discretized open curve --- curve/ArcLengthParam.cs | 93 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/curve/ArcLengthParam.cs b/curve/ArcLengthParam.cs index 5884a5d9..633cb108 100644 --- a/curve/ArcLengthParam.cs +++ b/curve/ArcLengthParam.cs @@ -92,4 +92,97 @@ protected Vector3d tangent(int i) } } + + + + + + public struct CurveSample2d + { + public Vector2d position; + public Vector2d tangent; + public CurveSample2d(Vector2d p, Vector2d t) + { + position = p; tangent = t; + } + } + + + public interface IArcLengthParam2d + { + double ArcLength { get; } + CurveSample2d Sample(double fArcLen); + } + + + public class SampledArcLengthParam2d : IArcLengthParam2d + { + double[] arc_len; + Vector2d[] positions; + + public SampledArcLengthParam2d(IEnumerable samples, int nCountHint = -1) + { + int N = (nCountHint == -1) ? samples.Count() : nCountHint; + arc_len = new double[N]; + arc_len[0] = 0; + positions = new Vector2d[N]; + + int i = 0; + Vector2d prev = Vector2d.Zero; + foreach (Vector2d v in samples) { + positions[i] = v; + if (i > 0) { + double d = (v - prev).Length; + arc_len[i] = arc_len[i - 1] + d; + } + i++; + prev = v; + } + } + + + public double ArcLength { + get { return arc_len[arc_len.Length - 1]; } + } + + public CurveSample2d Sample(double f) + { + if (f <= 0) + return new CurveSample2d(new Vector2d(positions[0]), tangent(0)); + + int N = arc_len.Length; + if (f >= arc_len[N - 1]) + return new CurveSample2d(new Vector2d(positions[N - 1]), tangent(N - 1)); + + for (int k = 0; k < N; ++k) { + if (f < arc_len[k]) { + int a = k - 1; + int b = k; + if (arc_len[a] == arc_len[b]) + return new CurveSample2d(new Vector2d(positions[a]), tangent(a)); + double t = (f - arc_len[a]) / (arc_len[b] - arc_len[a]); + return new CurveSample2d( + Vector2d.Lerp(positions[a], positions[b], t), + Vector2d.Lerp(tangent(a), tangent(b), t)); + } + } + + throw new ArgumentException("SampledArcLengthParam2d.Sample: somehow arc len is outside any possible range"); + } + + + protected Vector2d tangent(int i) + { + int N = arc_len.Length; + if (i == 0) + return (positions[1] - positions[0]).Normalized; + else if (i == N - 1) + return (positions[N - 1] - positions[N - 2]).Normalized; + else + return (positions[i + 1] - positions[i - 1]).Normalized; + } + } + + + } From 78f9ac643b5a8e10b5f7511b812ef6b6ccdaaab4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 13 Jun 2018 14:53:50 -0400 Subject: [PATCH 156/225] ray normalization --- math/Ray3.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/math/Ray3.cs b/math/Ray3.cs index 97e2ec73..55f1eb61 100644 --- a/math/Ray3.cs +++ b/math/Ray3.cs @@ -14,10 +14,12 @@ public struct Ray3d public Vector3d Origin; public Vector3d Direction; - public Ray3d(Vector3d origin, Vector3d direction) + public Ray3d(Vector3d origin, Vector3d direction, bool bIsNormalized = false) { this.Origin = origin; this.Direction = direction; + if (bIsNormalized == false && Direction.IsNormalized == false) + Direction.Normalize(); } public Ray3d(Vector3f origin, Vector3f direction) @@ -91,10 +93,12 @@ public struct Ray3f public Vector3f Origin; public Vector3f Direction; - public Ray3f(Vector3f origin, Vector3f direction) + public Ray3f(Vector3f origin, Vector3f direction, bool bIsNormalized = false) { this.Origin = origin; this.Direction = direction; + if (bIsNormalized == false && Direction.IsNormalized == false) + Direction.Normalize(); } // parameter is distance along ray From 5bda8e19a494259b384a631a38910d0abfe33d74 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 15 Jun 2018 11:47:03 -0400 Subject: [PATCH 157/225] bugfixes --- curve/DGraph2.cs | 5 +++-- spatial/Polygon2dBoxTree.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/curve/DGraph2.cs b/curve/DGraph2.cs index 46ee4fd4..a3c501ed 100644 --- a/curve/DGraph2.cs +++ b/curve/DGraph2.cs @@ -154,7 +154,8 @@ public void AppendPolyline(PolyLine2d poly, int gid = -1) int N = poly.VertexCount; for (int i = 0; i < N; ++i) { int cur = AppendVertex(poly[i]); - AppendEdge(prev, cur, gid); + if ( i > 0 ) + AppendEdge(prev, cur, gid); prev = cur; } } @@ -164,7 +165,7 @@ public void AppendGraph(DGraph2 graph, int gid = -1) { int[] mapV = new int[graph.MaxVertexID]; foreach ( int vid in graph.VertexIndices()) { - mapV[vid] = this.AppendVertex(graph.GetVertex(vid)); + mapV[vid] = this.AppendVertex(graph.GetVertex(vid)); } foreach ( int eid in graph.EdgeIndices()) { Index2i ev = graph.GetEdgeV(eid); diff --git a/spatial/Polygon2dBoxTree.cs b/spatial/Polygon2dBoxTree.cs index dc5027e6..b400e677 100644 --- a/spatial/Polygon2dBoxTree.cs +++ b/spatial/Polygon2dBoxTree.cs @@ -32,7 +32,8 @@ public double DistanceSquared(Vector2d pt, out int iHoleIndex, out int iNearSeg, { iHoleIndex = -1; double min_dist = OuterTree.SquaredDistance(pt, out iNearSeg, out fNearSegT); - for (int k = 0; k < HoleTrees.Length; ++k) { + int NH = (HoleTrees == null) ? 0 : HoleTrees.Length; + for (int k = 0; k < NH; ++k) { int hole_near_seg; double hole_seg_t; double hole_dist = HoleTrees[k].SquaredDistance(pt, out hole_near_seg, out hole_seg_t, min_dist); if (hole_dist < min_dist) { From a86dcc2f91c8ab8c0f534c9ea7453974c000275c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 15 Jun 2018 11:47:12 -0400 Subject: [PATCH 158/225] util fn --- curve/Polygon2d.cs | 5 +++++ io/SVGWriter.cs | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 7bf37970..dee9cdbb 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -95,6 +95,11 @@ public void AppendVertex(Vector2d v) vertices.Add(v); Timestamp++; } + public void AppendVertices(IEnumerable v) + { + vertices.AddRange(v); + Timestamp++; + } public void RemoveVertex(int idx) { diff --git a/io/SVGWriter.cs b/io/SVGWriter.cs index 549bcf17..1444ea5c 100644 --- a/io/SVGWriter.cs +++ b/io/SVGWriter.cs @@ -244,6 +244,18 @@ public static void QuickWrite(List polygons, string sPath, dou } + + + + public static void QuickWrite(DGraph2 graph, string sPath, double line_width = 1) + { + SVGWriter writer = new SVGWriter(); + Style outer_cw = SVGWriter.Style.Outline("black", (float)line_width); + writer.AddGraph(graph, outer_cw); + writer.Write(sPath); + } + + public static void QuickWrite(List polygons1, string color1, float width1, List polygons2, string color2, float width2, string sPath) From 7310b017ab55d44ed56eeef05ff65c521cdc2f3e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 15 Jun 2018 11:47:52 -0400 Subject: [PATCH 159/225] improvements to DGraph2 processing utils --- curve/DGraph2Util.cs | 154 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 4 deletions(-) diff --git a/curve/DGraph2Util.cs b/curve/DGraph2Util.cs index a53b2eab..0bb8512d 100644 --- a/curve/DGraph2Util.cs +++ b/curve/DGraph2Util.cs @@ -14,7 +14,7 @@ public static class DGraph2Util { - public struct Curves + public class Curves { public List Loops; public List Paths; @@ -76,16 +76,24 @@ public static Curves ExtractCurves(DGraph2 graph) PolyLine2d path = new PolyLine2d(); path.AppendVertex(graph.GetVertex(vid)); + bool is_loop = false; while (true) { used.Add(eid); Index2i next = NextEdgeAndVtx(eid, vid, graph); eid = next.a; vid = next.b; + if ( vid == start_vid ) { + is_loop = true; + break; + } path.AppendVertex(graph.GetVertex(vid)); if (eid == int.MaxValue || junctions.Contains(vid)) - break; // done! + break; } - c.Paths.Add(path); + if (is_loop) + c.Loops.Add(new Polygon2d(path.Vertices)); + else + c.Paths.Add(path); } } @@ -117,7 +125,6 @@ public static Curves ExtractCurves(DGraph2 graph) c.Loops.Add(poly); } - return c; } @@ -125,6 +132,145 @@ public static Curves ExtractCurves(DGraph2 graph) + /// + /// merge members of c.Paths that have unique endpoint pairings. + /// Does *not* extract closed loops that contain junction vertices, + /// unless the 'other' end of those junctions is dangling. + /// Also, horribly innefficient! + /// + public static void ChainOpenPaths(Curves c, double epsilon = MathUtil.Epsilon) + { + List to_process = new List(c.Paths); + c.Paths = new List(); + + // first we separate out 'dangling' curves that have no match on at least one side + List dangling = new List(); + List remaining = new List(); + + bool bContinue = true; + while (bContinue && to_process.Count > 0) { + bContinue = false; + foreach (PolyLine2d p in to_process) { + var matches_start = find_connected_start(p, to_process, epsilon); + var matches_end = find_connected_end(p, to_process, epsilon); + if (matches_start.Count == 0 || matches_end.Count == 0) { + dangling.Add(p); + bContinue = true; + } else + remaining.Add(p); + } + to_process.Clear(); to_process.AddRange(remaining); remaining.Clear(); + } + + //to_process.Clear(); to_process.AddRange(remaining); remaining.Clear(); + + // now incrementally merge together unique matches + // [TODO] this will not match across junctions! + bContinue = true; + while (bContinue && to_process.Count > 0) { + bContinue = false; + restart_itr: + foreach (PolyLine2d p in to_process) { + var matches_start = find_connected_start(p, to_process, epsilon); + var matches_end = find_connected_end(p, to_process, 2*epsilon); + if (matches_start.Count == 1 && matches_end.Count == 1 && + matches_start[0] == matches_end[0]) { + c.Loops.Add(to_loop(p, matches_start[0], epsilon)); + to_process.Remove(p); + to_process.Remove(matches_start[0]); + remaining.Remove(matches_start[0]); + bContinue = true; + goto restart_itr; + } else if (matches_start.Count == 1 && matches_end.Count < 2) { + remaining.Add(merge_paths(matches_start[0], p, 2*epsilon)); + to_process.Remove(p); + to_process.Remove(matches_start[0]); + remaining.Remove(matches_start[0]); + bContinue = true; + goto restart_itr; + } else if (matches_end.Count == 1 && matches_start.Count < 2) { + remaining.Add(merge_paths(p, matches_end[0], 2*epsilon)); + to_process.Remove(p); + to_process.Remove(matches_end[0]); + remaining.Remove(matches_end[0]); + bContinue = true; + goto restart_itr; + } else { + remaining.Add(p); + } + } + to_process.Clear(); to_process.AddRange(remaining); remaining.Clear(); + } + + c.Paths.AddRange(to_process); + + // [TODO] now that we have found all loops, we can chain in dangling curves + + c.Paths.AddRange(dangling); + + } + + + + + + static List find_connected_start(PolyLine2d pTest, List potential, double eps = MathUtil.Epsilon) + { + List result = new List(); + foreach ( var p in potential ) { + if (pTest == p) + continue; + if (pTest.Start.Distance(p.Start) < eps || + pTest.Start.Distance(p.End) < eps) + result.Add(p); + } + return result; + } + static List find_connected_end(PolyLine2d pTest, List potential, double eps = MathUtil.Epsilon) + { + List result = new List(); + foreach (var p in potential) { + if (pTest == p) + continue; + if ( pTest.End.Distance(p.Start) < eps || + pTest.End.Distance(p.End) < eps) + result.Add(p); + } + return result; + } + static Polygon2d to_loop(PolyLine2d p1, PolyLine2d p2, double eps = MathUtil.Epsilon) + { + Polygon2d p = new Polygon2d(p1.Vertices); + if (p1.End.Distance(p2.Start) > eps) + p2.Reverse(); + p.AppendVertices(p2); + return p; + } + static PolyLine2d merge_paths(PolyLine2d p1, PolyLine2d p2, double eps = MathUtil.Epsilon) + { + PolyLine2d pNew; + if (p1.End.Distance(p2.Start) < eps) { + pNew = new PolyLine2d(p1); + pNew.AppendVertices(p2); + } else if (p1.End.Distance(p2.End) < eps) { + pNew = new PolyLine2d(p1); + p2.Reverse(); + pNew.AppendVertices(p2); + } else if (p1.Start.Distance(p2.Start) < eps) { + p2.Reverse(); + pNew = new PolyLine2d(p2); + pNew.AppendVertices(p1); + } else if (p1.Start.Distance(p2.End) < eps) { + pNew = new PolyLine2d(p2); + pNew.AppendVertices(p1); + } else + throw new Exception("shit"); + return pNew; + } + + + + /// /// Find and remove any junction (ie valence>2) vertices of the graph. /// At a junction, the pair of best-aligned (ie straightest) edges are left From c171e655a1f327d8cc29858a048d44a62aa2744c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 15 Jun 2018 11:50:11 -0400 Subject: [PATCH 160/225] resolve duplicate fn --- io/SVGWriter.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/io/SVGWriter.cs b/io/SVGWriter.cs index d7f0ff42..ed6a0115 100644 --- a/io/SVGWriter.cs +++ b/io/SVGWriter.cs @@ -244,19 +244,15 @@ public static void QuickWrite(List polygons, string sPath, dou } - - - public static void QuickWrite(DGraph2 graph, string sPath, double line_width = 1) { SVGWriter writer = new SVGWriter(); - Style outer_cw = SVGWriter.Style.Outline("black", (float)line_width); - writer.AddGraph(graph, outer_cw); + Style style = SVGWriter.Style.Outline("black", (float)line_width); + writer.AddGraph(graph, style); writer.Write(sPath); } - - public static void QuickWrite(List polygons1, string color1, float width1, + public static void QuickWrite(List polygons1, string color1, float width1, List polygons2, string color2, float width2, string sPath) { @@ -280,13 +276,6 @@ public static void QuickWrite(List polygons1, string color1, f - public static void QuickWrite(DGraph2 graph, string sPath, float line_width = 1) - { - SVGWriter writer = new SVGWriter(); - Style style = SVGWriter.Style.Outline("black", line_width); - writer.AddGraph(graph, style); - writer.Write(sPath); - } From b714edfbebb88fcd6dfbd3787420693a8ac74601 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 20 Jun 2018 16:42:15 -0400 Subject: [PATCH 161/225] improve FastCollapsePass --- mesh/Reducer.cs | 66 ++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/mesh/Reducer.cs b/mesh/Reducer.cs index 152e3c1b..99757e6f 100644 --- a/mesh/Reducer.cs +++ b/mesh/Reducer.cs @@ -166,7 +166,7 @@ public virtual void ReduceToEdgeLength(double minEdgeLen) - public virtual void FastCollapsePass(double fMinEdgeLength) + public virtual void FastCollapsePass(double fMinEdgeLength, int nRounds = 1, bool MeshIsClosedHint = false) { if (mesh.TriangleCount == 0) // badness if we don't catch this... return; @@ -180,7 +180,7 @@ public virtual void FastCollapsePass(double fMinEdgeLength) begin_pass(); begin_setup(); - Precompute(); + Precompute(MeshIsClosedHint); if (Cancelled()) return; end_setup(); @@ -190,27 +190,35 @@ public virtual void FastCollapsePass(double fMinEdgeLength) begin_collapse(); int N = mesh.MaxEdgeID; - Vector3d va = Vector3d.Zero, vb = Vector3d.Zero; - for ( int eid = 0; eid < N; ++eid) { - if (!mesh.IsEdge(eid)) - continue; - if (mesh.IsBoundaryEdge(eid)) - continue; - if (Cancelled()) - return; - - mesh.GetEdgeV(eid, ref va, ref vb); - if (va.DistanceSquared(ref vb) > min_sqr) - continue; - - COUNT_ITERATIONS++; - - Vector3d midpoint = (va + vb) * 0.5; - int vKept; - ProcessResult result = CollapseEdge(eid, midpoint, out vKept); - if (result == ProcessResult.Ok_Collapsed) { - // do nothing? + int num_last_pass = 0; + for (int ri = 0; ri < nRounds; ++ri) { + num_last_pass = 0; + + Vector3d va = Vector3d.Zero, vb = Vector3d.Zero; + for (int eid = 0; eid < N; ++eid) { + if (!mesh.IsEdge(eid)) + continue; + if (mesh.IsBoundaryEdge(eid)) + continue; + if (Cancelled()) + return; + + mesh.GetEdgeV(eid, ref va, ref vb); + if (va.DistanceSquared(ref vb) > min_sqr) + continue; + + COUNT_ITERATIONS++; + + Vector3d midpoint = (va + vb) * 0.5; + int vKept; + ProcessResult result = CollapseEdge(eid, midpoint, out vKept); + if (result == ProcessResult.Ok_Collapsed) { + ++num_last_pass; + } } + + if (num_last_pass == 0) // converged + break; } end_collapse(); end_ops(); @@ -420,15 +428,17 @@ protected virtual void Reproject() { protected bool HaveBoundary; protected bool[] IsBoundaryVtxCache; - protected virtual void Precompute() + protected virtual void Precompute(bool bMeshIsClosed = false) { HaveBoundary = false; IsBoundaryVtxCache = new bool[mesh.MaxVertexID]; - foreach ( int eid in mesh.BoundaryEdgeIndices()) { - Index2i ev = mesh.GetEdgeV(eid); - IsBoundaryVtxCache[ev.a] = true; - IsBoundaryVtxCache[ev.b] = true; - HaveBoundary = true; + if (bMeshIsClosed == false) { + foreach (int eid in mesh.BoundaryEdgeIndices()) { + Index2i ev = mesh.GetEdgeV(eid); + IsBoundaryVtxCache[ev.a] = true; + IsBoundaryVtxCache[ev.b] = true; + HaveBoundary = true; + } } } protected bool IsBoundaryV(int vid) From d8c5ebe00da6ff11fecf166002822796faa2b92f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 21 Jun 2018 16:58:28 -0400 Subject: [PATCH 162/225] vertices at the ends of constrained spans should be pinned, right? maybe this should be an option? --- mesh/MeshConstraintUtil.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mesh/MeshConstraintUtil.cs b/mesh/MeshConstraintUtil.cs index 7e3f42ab..7fe7b433 100644 --- a/mesh/MeshConstraintUtil.cs +++ b/mesh/MeshConstraintUtil.cs @@ -172,8 +172,10 @@ public static void ConstrainVtxSpanTo(MeshConstraints cons, DMesh3 mesh, IList Date: Thu, 21 Jun 2018 17:00:39 -0400 Subject: [PATCH 163/225] catch a case in mesh refinement constraints where we were allowing a collapse that would lose a vtx constrained --- mesh/MeshRefinerBase.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mesh/MeshRefinerBase.cs b/mesh/MeshRefinerBase.cs index 0f124892..96ea5075 100644 --- a/mesh/MeshRefinerBase.cs +++ b/mesh/MeshRefinerBase.cs @@ -203,10 +203,15 @@ protected bool can_collapse_vtx(int eid, int a, int b, out int collapse_to) // handle a or b fixed if (ca.Fixed == true && cb.Fixed == false) { + // if b is fixed to a target, and it is different than a's target, we can't collapse + if (cb.Target != null && cb.Target != ca.Target) + return false; collapse_to = a; return true; } if (cb.Fixed == true && ca.Fixed == false) { + if (ca.Target != null && ca.Target != cb.Target) + return false; collapse_to = b; return true; } From 7e6377b5757be445537310613b8a7be2b6b93219 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 24 Jun 2018 15:34:18 -0400 Subject: [PATCH 164/225] asmdef file for unity --- distance/DistRay3Segment3.cs | 2 +- geometry3Sharp.asmdef | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 geometry3Sharp.asmdef diff --git a/distance/DistRay3Segment3.cs b/distance/DistRay3Segment3.cs index e053cc72..a6dc4b6a 100644 --- a/distance/DistRay3Segment3.cs +++ b/distance/DistRay3Segment3.cs @@ -9,7 +9,7 @@ namespace g3 /// Distance between ray and segment /// ported from WildMagic5 /// - class DistRay3Segment3 + public class DistRay3Segment3 { Ray3d ray; public Ray3d Ray diff --git a/geometry3Sharp.asmdef b/geometry3Sharp.asmdef new file mode 100644 index 00000000..20e83fc1 --- /dev/null +++ b/geometry3Sharp.asmdef @@ -0,0 +1,8 @@ +{ + "name": "geometry3Sharp", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true +} \ No newline at end of file From bd90bbe26ac6265afee9b796d1a4dd6216287696 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 25 Jun 2018 12:56:09 -0400 Subject: [PATCH 165/225] minor changes to obj writing --- io/OBJWriter.cs | 4 +++- io/StandardMeshWriter.cs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/io/OBJWriter.cs b/io/OBJWriter.cs index 0e2c0cf1..59708df7 100644 --- a/io/OBJWriter.cs +++ b/io/OBJWriter.cs @@ -65,7 +65,7 @@ public IOWriteResult Write(TextWriter writer, List vMeshes, WriteOpti IMesh mesh = vMeshes[mi].Mesh; if (options.ProgressFunc != null) - options.ProgressFunc(mi, vMeshes.Count - 1); + options.ProgressFunc(mi, vMeshes.Count); bool bVtxColors = options.bPerVertexColors && mesh.HasVertexColors; bool bNormals = options.bPerVertexNormals && mesh.HasVertexNormals; @@ -125,6 +125,8 @@ public IOWriteResult Write(TextWriter writer, List vMeshes, WriteOpti else write_triangles_flat(writer, vMeshes[mi], mapV, uvSet, mapUV, bNormals, bWriteMaterials); + if (options.ProgressFunc != null) + options.ProgressFunc(mi+1, vMeshes.Count); } diff --git a/io/StandardMeshWriter.cs b/io/StandardMeshWriter.cs index 7043a0fd..088fcc38 100644 --- a/io/StandardMeshWriter.cs +++ b/io/StandardMeshWriter.cs @@ -42,6 +42,7 @@ public class StandardMeshWriter : IDisposable /// called on Streams returned by OpenStreamF when we are done with them. /// public Action CloseStreamF = (stream) => { + stream.Close(); stream.Dispose(); }; From 5960868aadd49887c5f6ab2008683d578913169d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 26 Jun 2018 11:03:01 -0400 Subject: [PATCH 166/225] mesh measurement util --- mesh/MeshMeasurements.cs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/mesh/MeshMeasurements.cs b/mesh/MeshMeasurements.cs index 853fa885..5b0a833a 100644 --- a/mesh/MeshMeasurements.cs +++ b/mesh/MeshMeasurements.cs @@ -298,17 +298,38 @@ public static Interval1d ExtentsOnAxis(DMesh3 mesh, Vector3d axis, Func + /// Calculate extents of mesh along an axis, with optional transform + /// + public static Interval1d ExtentsOnAxis(IMesh mesh, Vector3d axis, Func TransformF = null) + { + Interval1d extent = Interval1d.Empty; + if (TransformF == null) { + foreach (int vid in mesh.VertexIndices()) + extent.Contain(mesh.GetVertex(vid).Dot(ref axis)); + } else { + foreach (int vid in mesh.VertexIndices()) { + Vector3d vT = TransformF(mesh.GetVertex(vid)); + extent.Contain(vT.Dot(ref axis)); + } + } + return extent; + } + + + + /// /// Calculate the two most extreme vertices along an axis, with optional transform From 627b255f28788b74ce349b45975cdbffded7c41c Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 26 Jun 2018 20:23:16 -0400 Subject: [PATCH 167/225] added nary-intersect implicit op --- implicit/Implicit3d.cs | 44 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 585eba13..3987ac98 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -311,13 +311,45 @@ public AxisAlignedBox3d Bounds() + /// + /// Boolean Intersection of N implicit functions, A AND B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitNaryIntersection3d : BoundedImplicitFunction3d + { + public List Children; - /// - /// Boolean Difference of N implicit functions, A - Union(B1..BN) - /// Assumption is that both have surface at zero isocontour and - /// negative is inside. - /// - public class ImplicitNaryDifference3d : BoundedImplicitFunction3d + public double Value(ref Vector3d pt) + { + double f = Children[0].Value(ref pt); + int N = Children.Count; + for (int k = 1; k < N; ++k) + f = Math.Max(f, Children[k].Value(ref pt)); + return f; + } + + public AxisAlignedBox3d Bounds() + { + var box = Children[0].Bounds(); + int N = Children.Count; + for (int k = 1; k < N; ++k) { + box = box.Intersect(Children[k].Bounds()); + } + return box; + } + } + + + + + + /// + /// Boolean Difference of N implicit functions, A - Union(B1..BN) + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class ImplicitNaryDifference3d : BoundedImplicitFunction3d { public BoundedImplicitFunction3d A; public List BSet; From a74c6b99c0f7f9cc88aeba1a112b56681ffadffa Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 26 Jun 2018 21:26:36 -0400 Subject: [PATCH 168/225] improve cancel support in SDF when we are calculating full grid --- spatial/MeshSignedDistanceGrid.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 5adc416a..8fd03777 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -414,12 +414,19 @@ void sweep_pass(Vector3f origin, float dx, DenseGrid3f distances, DenseGrid3i closest_tri) { sweep(distances, closest_tri, origin, dx, +1, +1, +1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, -1, -1, -1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, +1, +1, -1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, -1, -1, +1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, +1, -1, +1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, -1, +1, -1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, +1, -1, -1); + if (CancelF()) return; sweep(distances, closest_tri, origin, dx, -1, +1, +1); } @@ -436,6 +443,7 @@ void sweep(DenseGrid3f phi, DenseGrid3i closest_tri, int k0, k1; if (dk > 0) { k0 = 1; k1 = phi.nk; } else { k0 = phi.nk - 2; k1 = -1; } for (int k = k0; k != k1; k += dk) { + if (CancelF()) return; for (int j = j0; j != j1; j += dj) { for (int i = i0; i != i1; i += di) { Vector3d gx = new Vector3d(i * dx + origin[0], j * dx + origin[1], k * dx + origin[2]); @@ -476,12 +484,19 @@ void compute_intersections(Vector3f origin, float dx, int ni, int nj, int nk, De double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; double invdx = 1.0 / dx; + bool cancelled = false; + // this is what we will do for each triangle. There are no grid-reads, only grid-writes, // since we use atomic_increment, it is always thread-safe Action ProcessTriangleF = (tid) => { + if (tid % 100 == 0 && CancelF() == true) + cancelled = true; + if (cancelled) return; + Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); + bool neg_x = false; if (InsideMode == InsideModes.ParityCount) { Vector3d n = MathUtil.FastNormalDirection(ref xp, ref xq, ref xr); @@ -544,6 +559,9 @@ void compute_signs(int ni, int nj, int nk, DenseGrid3f distances, DenseGrid3i in // can process each x-row in parallel AxisAlignedBox2i box = new AxisAlignedBox2i(0, 0, nj, nk); gParallel.ForEach(box.IndicesExclusive(), (vi) => { + if (CancelF()) + return; + int j = vi.x, k = vi.y; int total_count = 0; for (int i = 0; i < ni; ++i) { @@ -557,6 +575,9 @@ void compute_signs(int ni, int nj, int nk, DenseGrid3f distances, DenseGrid3i in } else { for (int k = 0; k < nk; ++k) { + if (CancelF()) + return; + for (int j = 0; j < nj; ++j) { int total_count = 0; for (int i = 0; i < ni; ++i) { From a635f8bf543f5617ff134b2dea1988ca6400d1b3 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 27 Jun 2018 17:25:41 -0400 Subject: [PATCH 169/225] new implicit ops --- implicit/GridImplicits3d.cs | 8 ++- implicit/Implicit3d.cs | 101 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/implicit/GridImplicits3d.cs b/implicit/GridImplicits3d.cs index 97dd08d3..6e2d1fe8 100644 --- a/implicit/GridImplicits3d.cs +++ b/implicit/GridImplicits3d.cs @@ -27,9 +27,15 @@ public DenseGridTrilinearImplicit(DenseGrid3f grid, Vector3d gridOrigin, double GridOrigin = gridOrigin; CellSize = cellSize; } + public DenseGridTrilinearImplicit(MeshSignedDistanceGrid sdf_grid) + { + Grid = sdf_grid.Grid; + GridOrigin = sdf_grid.GridOrigin; + CellSize = sdf_grid.CellSize; + } - public AxisAlignedBox3d Bounds() + public AxisAlignedBox3d Bounds() { return new AxisAlignedBox3d( GridOrigin.x, GridOrigin.y, GridOrigin.z, diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 3987ac98..ba08d345 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -519,4 +519,105 @@ public AxisAlignedBox3d Bounds() + + + + + + /* + * Skeletal implicit ops + */ + + + + /// + /// This class converts the interval [-falloff,falloff] to [0,1], + /// Then applies Wyvill falloff function (1-t^2)^3. + /// The result is a skeletal-primitive-like shape with + /// the distance=0 isocontour lying just before midway in + /// the range (at the .ZeroIsocontour constant) + /// + public class DistanceFieldToSkeletalField : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d DistanceField; + public double FalloffDistance; + public const double ZeroIsocontour = 0.421875; + + public AxisAlignedBox3d Bounds() + { + AxisAlignedBox3d bounds = DistanceField.Bounds(); + bounds.Expand(FalloffDistance); + return bounds; + } + + public double Value(ref Vector3d pt) + { + double d = DistanceField.Value(ref pt); + if (d > FalloffDistance) + return 0; + else if (d < -FalloffDistance) + return 1.0; + double a = (d + FalloffDistance) / (2 * FalloffDistance); + double t = 1 - (a * a); + return t * t * t; + } + } + + + + + + + + /// + /// sum-blend + /// + public class SkeletalBlend3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + + public double Value(ref Vector3d pt) + { + return A.Value(ref pt) + B.Value(ref pt); + } + + public AxisAlignedBox3d Bounds() + { + return A.Bounds(); + } + } + + + + /// + /// Ricci blend + /// + public class SkeletalRicciBlend3d : BoundedImplicitFunction3d + { + public BoundedImplicitFunction3d A; + public BoundedImplicitFunction3d B; + public double BlendPower = 2.0; + + public double Value(ref Vector3d pt) + { + double a = A.Value(ref pt); + double b = B.Value(ref pt); + if (BlendPower == 2.0) { + return Math.Sqrt(a*a + b*b); + } else { + return Math.Pow( Math.Pow(a,BlendPower) + Math.Pow(b,BlendPower), 1.0/BlendPower); + } + } + + public AxisAlignedBox3d Bounds() + { + return A.Bounds(); + } + } + + + + + } From f43c25951a37722476c7dbf91a896d7289e39d50 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 27 Jun 2018 22:30:51 -0400 Subject: [PATCH 170/225] fix bounds for skeletal binary blends --- implicit/Implicit3d.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index ba08d345..2c512aef 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -584,7 +584,10 @@ public double Value(ref Vector3d pt) public AxisAlignedBox3d Bounds() { - return A.Bounds(); + AxisAlignedBox3d box = A.Bounds(); + box.Contain(B.Bounds()); + box.Expand(0.25 * box.MaxDim); + return box; } } @@ -612,7 +615,10 @@ public double Value(ref Vector3d pt) public AxisAlignedBox3d Bounds() { - return A.Bounds(); + AxisAlignedBox3d box = A.Bounds(); + box.Contain(B.Bounds()); + box.Expand(0.25 * box.MaxDim); + return box; } } From 3863b6ebece7de89aa3b790333f1fee7c19e70a4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 27 Jun 2018 23:12:36 -0400 Subject: [PATCH 171/225] nary ricci blend --- implicit/Implicit3d.cs | 51 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 2c512aef..22d99735 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -606,7 +606,9 @@ public double Value(ref Vector3d pt) { double a = A.Value(ref pt); double b = B.Value(ref pt); - if (BlendPower == 2.0) { + if ( BlendPower == 1.0 ) { + return a + b; + } else if (BlendPower == 2.0) { return Math.Sqrt(a*a + b*b); } else { return Math.Pow( Math.Pow(a,BlendPower) + Math.Pow(b,BlendPower), 1.0/BlendPower); @@ -625,5 +627,52 @@ public AxisAlignedBox3d Bounds() + /// + /// Boolean Union of N implicit functions, A OR B. + /// Assumption is that both have surface at zero isocontour and + /// negative is inside. + /// + public class SkeletalRicciNaryBlend3d : BoundedImplicitFunction3d + { + public List Children; + public double BlendPower = 2.0; + + public double Value(ref Vector3d pt) + { + int N = Children.Count; + double f = 0; + if (BlendPower == 1.0) { + for (int k = 0; k < N; ++k) + f += Children[k].Value(ref pt); + } else if (BlendPower == 2.0) { + for (int k = 0; k < N; ++k) { + double v = Children[k].Value(ref pt); + f += v * v; + } + f = Math.Sqrt(f); + } else { + for (int k = 0; k < N; ++k) { + double v = Children[k].Value(ref pt); + f += Math.Pow(v, BlendPower); + } + f = Math.Pow(f, 1.0 / BlendPower); + } + return f; + } + + public AxisAlignedBox3d Bounds() + { + var box = Children[0].Bounds(); + int N = Children.Count; + for (int k = 1; k < N; ++k) + box.Contain(Children[k].Bounds()); + box.Expand(0.25 * box.MaxDim); + return box; + } + } + + + + } From 18baf8f9ff6b582bbbf763d9805ce55e72c891b4 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 30 Jun 2018 00:29:45 -0400 Subject: [PATCH 172/225] unity update, early-out in MeshBoundaryLoops --- mesh_generators/MeshGenerators.cs | 2 ++ mesh_selection/MeshBoundaryLoops.cs | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/mesh_generators/MeshGenerators.cs b/mesh_generators/MeshGenerators.cs index 2d17dd84..eae88b8e 100644 --- a/mesh_generators/MeshGenerators.cs +++ b/mesh_generators/MeshGenerators.cs @@ -295,6 +295,8 @@ public void MakeMesh(Mesh m, bool bRecalcNormals = false, bool bFlipLR = false) m.uv = ToUnityVector2(uv); if (normals != null && WantNormals) m.normals = ToUnityVector3(normals, bFlipLR); + if ( m.vertexCount > 64000 || triangles.Count > 64000 ) + m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; m.triangles = triangles.array; if (bRecalcNormals) m.RecalculateNormals(); diff --git a/mesh_selection/MeshBoundaryLoops.cs b/mesh_selection/MeshBoundaryLoops.cs index 029b30ed..8b256483 100644 --- a/mesh_selection/MeshBoundaryLoops.cs +++ b/mesh_selection/MeshBoundaryLoops.cs @@ -150,6 +150,10 @@ public bool Compute() Loops = new List(); Spans = new List(); + // early-out if we don't actually have boundaries + if (Mesh.CachedIsClosed) + return true; + int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. From a42c9b94fdeb40e3b0997c034a3a3c09532449bb Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 12:15:08 -0400 Subject: [PATCH 173/225] tiny ref optimizations --- distance/DistPoint3Triangle3.cs | 6 +++--- queries/MeshQueries.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/distance/DistPoint3Triangle3.cs b/distance/DistPoint3Triangle3.cs index 5187fba3..3ba058a7 100644 --- a/distance/DistPoint3Triangle3.cs +++ b/distance/DistPoint3Triangle3.cs @@ -63,10 +63,10 @@ public static double DistanceSqr(ref Vector3d point, ref Triangle3d triangle, ou Vector3d edge0 = triangle.V1 - triangle.V0; Vector3d edge1 = triangle.V2 - triangle.V0; double a00 = edge0.LengthSquared; - double a01 = edge0.Dot(edge1); + double a01 = edge0.Dot(ref edge1); double a11 = edge1.LengthSquared; - double b0 = diff.Dot(edge0); - double b1 = diff.Dot(edge1); + double b0 = diff.Dot(ref edge0); + double b1 = diff.Dot(ref edge1); double c = diff.LengthSquared; double det = Math.Abs(a00 * a11 - a01 * a01); double s = a01 * b1 - a11 * b0; diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index 0f56c1c2..9d9ddfb3 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -176,10 +176,10 @@ public static double TriDistanceSqr(DMesh3 mesh, int ti, Vector3d point) Vector3d edge0 = V1 - V0; Vector3d edge1 = V2 - V0; double a00 = edge0.LengthSquared; - double a01 = edge0.Dot(edge1); + double a01 = edge0.Dot(ref edge1); double a11 = edge1.LengthSquared; - double b0 = diff.Dot(edge0); - double b1 = diff.Dot(edge1); + double b0 = diff.Dot(ref edge0); + double b1 = diff.Dot(ref edge1); double c = diff.LengthSquared; double det = Math.Abs(a00 * a11 - a01 * a01); double s = a01 * b1 - a11 * b0; From ccfa794aaa2df514c2cb4814949465d473cb88e7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 12:55:10 -0400 Subject: [PATCH 174/225] add support for using Spatial DS to compute mesh SDF. This is not *always* faster, however it is asymptotically faster as the narrow band gets wider. --- spatial/MeshSignedDistanceGrid.cs | 163 +++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 4 deletions(-) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 8fd03777..0155932b 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -40,6 +40,7 @@ namespace g3 public class MeshSignedDistanceGrid { public DMesh3 Mesh; + public DMeshAABBTree3 Spatial; public float CellSize; // Width of the band around triangles for which exact distances are computed @@ -100,10 +101,11 @@ public enum InsideModes DenseGrid3i closest_tri_grid; DenseGrid3i intersections_grid; - public MeshSignedDistanceGrid(DMesh3 mesh, double cellSize) + public MeshSignedDistanceGrid(DMesh3 mesh, double cellSize, DMeshAABBTree3 spatial = null) { Mesh = mesh; CellSize = (float)cellSize; + Spatial = spatial; } @@ -120,10 +122,14 @@ public void Compute() int nk = (int)((max.z - grid_origin.z) / CellSize) + 1; grid = new DenseGrid3f(); - if ( UseParallel ) - make_level_set3_parallel(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); - else + if (UseParallel) { + if ( Spatial != null ) + make_level_set3_parallel_spatial(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + else + make_level_set3_parallel(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } else { make_level_set3(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } } @@ -409,6 +415,155 @@ void make_level_set3_parallel(Vector3f origin, float dx, + + + + + + + void make_level_set3_parallel_spatial(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f distances, int exact_band) + { + distances.resize(ni, nj, nk); + float upper_bound = (float)((ni + nj + nk) * dx); + distances.assign(upper_bound); // upper bound on distance + + // closest triangle id for each grid cell + DenseGrid3i closest_tri = new DenseGrid3i(ni, nj, nk, -1); + + // intersection_count(i,j,k) is # of tri intersections in (i-1,i]x{j}x{k} + DenseGrid3i intersection_count = new DenseGrid3i(ni, nj, nk, 0); + + if (DebugPrint) System.Console.WriteLine("start"); + + double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; + double invdx = 1.0 / dx; + + // Compute narrow-band distances. For each triangle, we find its grid-coord-bbox, + // and compute exact distances within that box. + + // To compute in parallel, we need to safely update grid cells. Current strategy is + // to use a spinlock to control access to grid. Partitioning the grid into a few regions, + // each w/ a separate spinlock, improves performance somewhat. Have also tried having a + // separate spinlock per-row, this resulted in a few-percent performance improvement. + // Also tried pre-sorting triangles into disjoint regions, this did not help much except + // on "perfect" cases like a sphere. + int wi = ni / 2, wj = nj / 2, wk = nk / 2; + + bool abort = false; + gParallel.ForEach(Mesh.TriangleIndices(), (tid) => { + if (tid % 100 == 0) + abort = CancelF(); + if (abort) + return; + + Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; + Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); + + // real ijk coordinates of xp/xq/xr + double fip = (xp[0] - ox) * invdx, fjp = (xp[1] - oy) * invdx, fkp = (xp[2] - oz) * invdx; + double fiq = (xq[0] - ox) * invdx, fjq = (xq[1] - oy) * invdx, fkq = (xq[2] - oz) * invdx; + double fir = (xr[0] - ox) * invdx, fjr = (xr[1] - oy) * invdx, fkr = (xr[2] - oz) * invdx; + + // clamped integer bounding box of triangle plus exact-band + int i0 = MathUtil.Clamp(((int)MathUtil.Min(fip, fiq, fir)) - exact_band, 0, ni - 1); + int i1 = MathUtil.Clamp(((int)MathUtil.Max(fip, fiq, fir)) + exact_band + 1, 0, ni - 1); + int j0 = MathUtil.Clamp(((int)MathUtil.Min(fjp, fjq, fjr)) - exact_band, 0, nj - 1); + int j1 = MathUtil.Clamp(((int)MathUtil.Max(fjp, fjq, fjr)) + exact_band + 1, 0, nj - 1); + int k0 = MathUtil.Clamp(((int)MathUtil.Min(fkp, fkq, fkr)) - exact_band, 0, nk - 1); + int k1 = MathUtil.Clamp(((int)MathUtil.Max(fkp, fkq, fkr)) + exact_band + 1, 0, nk - 1); + + // compute distance for each tri inside this bounding box + // note: this can be very conservative if the triangle is large and on diagonal to grid axes + for (int k = k0; k <= k1; ++k) { + for (int j = j0; j <= j1; ++j) { + for (int i = i0; i <= i1; ++i) { + distances[i, j, k] = 1; + } + } + } + }); + + + if (DebugPrint) System.Console.WriteLine("done narrow-band tagging"); + + double max_dist = (exact_band+1) * dx; + gParallel.ForEach(grid.Indices(), (idx) => { + if ( distances[idx] == 1 ) { + int i = idx.x, j = idx.y, k = idx.z; + Vector3d p = new Vector3d((float)i * dx + origin[0], (float)j * dx + origin[1], (float)k * dx + origin[2]); + int near_tid = Spatial.FindNearestTriangle(p, max_dist); + if ( near_tid == DMesh3.InvalidID ) { + distances[idx] = upper_bound; + return; + } + Triangle3d tri = new Triangle3d(); + Mesh.GetTriVertices(near_tid, ref tri.V0, ref tri.V1, ref tri.V2); + Vector3d closest = new Vector3d(), bary = new Vector3d(); + double dsqr = DistPoint3Triangle3.DistanceSqr(ref p, ref tri, out closest, out bary); + distances[idx] = (float)Math.Sqrt(dsqr); + closest_tri[idx] = near_tid; + } + }); + + + if (DebugPrint) System.Console.WriteLine("done distances"); + + + if (CancelF()) + return; + + if (ComputeSigns == true) { + + if (DebugPrint) System.Console.WriteLine("done narrow-band"); + + compute_intersections(origin, dx, ni, nj, nk, intersection_count); + if (CancelF()) + return; + + if (DebugPrint) System.Console.WriteLine("done intersections"); + + if (ComputeMode == ComputeModes.FullGrid) { + // and now we fill in the rest of the distances with fast sweeping + for (int pass = 0; pass < 2; ++pass) { + sweep_pass(origin, dx, distances, closest_tri); + if (CancelF()) + return; + } + if (DebugPrint) System.Console.WriteLine("done sweeping"); + } else { + // nothing! + if (DebugPrint) System.Console.WriteLine("skipped sweeping"); + } + + if (DebugPrint) System.Console.WriteLine("done sweeping"); + + // then figure out signs (inside/outside) from intersection counts + compute_signs(ni, nj, nk, distances, intersection_count); + if (CancelF()) + return; + + if (WantIntersectionsGrid) + intersections_grid = intersection_count; + + if (DebugPrint) System.Console.WriteLine("done signs"); + } + + if (WantClosestTriGrid) + closest_tri_grid = closest_tri; + + } // end make_level_set_3 + + + + + + + + + + // sweep through grid in different directions, distances and closest tris void sweep_pass(Vector3f origin, float dx, DenseGrid3f distances, DenseGrid3i closest_tri) From 1442651ce0cba718f2de6d1087992802d6cdc10e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 12:55:17 -0400 Subject: [PATCH 175/225] profiling flag --- core/ProfileUtil.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/ProfileUtil.cs b/core/ProfileUtil.cs index 1db33bc7..d420fd5b 100644 --- a/core/ProfileUtil.cs +++ b/core/ProfileUtil.cs @@ -104,9 +104,9 @@ public void StopAll() } - public void StopAndAccumulate(string label) + public void StopAndAccumulate(string label, bool bReset = false) { - Timers[label].Accumulate(); + Timers[label].Accumulate(bReset); } public void Reset(string label) From c356b123dabf1f485a816656cc63384a5c7e6e35 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 16:05:22 -0400 Subject: [PATCH 176/225] bound was too conservative --- spatial/MeshSignedDistanceGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 0155932b..42a06da8 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -488,7 +488,7 @@ void make_level_set3_parallel_spatial(Vector3f origin, float dx, if (DebugPrint) System.Console.WriteLine("done narrow-band tagging"); - double max_dist = (exact_band+1) * dx; + double max_dist = exact_band * (dx * MathUtil.SqrtTwo); gParallel.ForEach(grid.Indices(), (idx) => { if ( distances[idx] == 1 ) { int i = idx.x, j = idx.y, k = idx.z; From eedae1065b77c29cbc655cd5ac11e02c3df5cf19 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 23:20:51 -0400 Subject: [PATCH 177/225] aaahhh format strings were ignoring seconds, so timing was wrong if anything took > 60 seconds (including accumulated) --- core/ProfileUtil.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/ProfileUtil.cs b/core/ProfileUtil.cs index d420fd5b..87d4e4a6 100644 --- a/core/ProfileUtil.cs +++ b/core/ProfileUtil.cs @@ -50,14 +50,23 @@ public void Reset() public string AccumulatedString { - get { return string.Format("{0:ss}.{0:fffffff}", Accumulated); } + get { return string.Format(TimeFormatString(Accumulated), Accumulated); } } public override string ToString() { TimeSpan t = Watch.Elapsed; - return string.Format("{0:ss}.{0:fffffff}", Watch.Elapsed); + return string.Format(TimeFormatString(Accumulated), Watch.Elapsed); } + public static string TimeFormatString(TimeSpan span) + { + if (span.Minutes > 0) + return minute_format; + else + return second_format; + } + const string minute_format = "{0:mm}:{0:ss}.{0:fffffff}"; + const string second_format = "{0:ss}.{0:fffffff}"; } @@ -139,7 +148,8 @@ public string Elapsed(string label) } public string Accumulated(string label) { - return string.Format("{0:ss}.{0:fffffff}", Timers[label].Accumulated); + TimeSpan accum = Timers[label].Accumulated; + return string.Format(BlockTimer.TimeFormatString(accum), accum); } public string AllTicks(string prefix = "Times:") @@ -169,7 +179,8 @@ public string AllTimes(string prefix = "Times:", string separator = " ") StringBuilder b = new StringBuilder(); b.Append(prefix + " "); foreach ( string label in Order ) { - b.Append(label + ": " + string.Format("{0:ss}.{0:ffffff}", Timers[label].Watch.Elapsed) + separator); + TimeSpan span = Timers[label].Watch.Elapsed; + b.Append(label + ": " + string.Format(BlockTimer.TimeFormatString(span), span) + separator); } return b.ToString(); } @@ -179,7 +190,8 @@ public string AllAccumulatedTimes(string prefix = "Times:", string separator = " StringBuilder b = new StringBuilder(); b.Append(prefix + " "); foreach ( string label in Order ) { - b.Append(label + ": " + string.Format("{0:ss}.{0:ffffff}", Timers[label].Accumulated) + separator); + TimeSpan span = Timers[label].Accumulated; + b.Append(label + ": " + string.Format(BlockTimer.TimeFormatString(span), span) + separator); } return b.ToString(); } From 5b5fb924fad4f875327c9feee99f5c7a39374857 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 23:21:31 -0400 Subject: [PATCH 178/225] be able to query current priority. add inclusive-bounds util fn. --- core/IndexPriorityQueue.cs | 12 ++++++++++++ spatial/DenseGrid3.cs | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/core/IndexPriorityQueue.cs b/core/IndexPriorityQueue.cs index 397c79a7..059c965f 100644 --- a/core/IndexPriorityQueue.cs +++ b/core/IndexPriorityQueue.cs @@ -163,6 +163,18 @@ public void Update(int id, float priority) } + /// + /// Query the priority at node id, assuming it exists in queue + /// + public float GetPriority(int id) + { + if (EnableDebugChecks && Contains(id) == false) + throw new Exception("IndexPriorityQueue.Update: tried to get priorty of node that does not exist in queue!"); + int iNode = id_to_index[id]; + return nodes[iNode].priority; + } + + public IEnumerator GetEnumerator() { diff --git a/spatial/DenseGrid3.cs b/spatial/DenseGrid3.cs index cbe6050c..6407d680 100644 --- a/spatial/DenseGrid3.cs +++ b/spatial/DenseGrid3.cs @@ -147,7 +147,9 @@ public void set_slice(DenseGrid2f slice, int slice_i, int dimension) public AxisAlignedBox3i Bounds { get { return new AxisAlignedBox3i(0, 0, 0, ni, nj, nk); } } - + public AxisAlignedBox3i BoundsInclusive { + get { return new AxisAlignedBox3i(0, 0, 0, ni-1, nj-1, nk-1); } + } public IEnumerable Indices() { From 38ca32a0a9183ab63a83dcf46b53af283d435e67 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 23:37:06 -0400 Subject: [PATCH 179/225] minor refactor bits, no functionality change --- spatial/MeshSignedDistanceGrid.cs | 48 +++++++++++++++++-------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 0155932b..45369ecd 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -176,11 +176,27 @@ public float this[Vector3i idx] { get { return grid[idx.x, idx.y, idx.z]; } } - public Vector3f CellCenter(int i, int j, int k) + public Vector3f CellCenter(int i, int j, int k) { + return cell_center(new Vector3i(i, j, k)); + } + Vector3f cell_center(Vector3i ijk) { - return new Vector3f((float)i * CellSize + grid_origin.x, - (float)j * CellSize + grid_origin.y, - (float)k * CellSize + grid_origin.z); + return new Vector3f((float)ijk.x * CellSize + grid_origin[0], + (float)ijk.y * CellSize + grid_origin[1], + (float)ijk.z * CellSize + grid_origin[2]); + } + + float upper_bound(DenseGrid3f grid) + { + return (float)((grid.ni + grid.nj + grid.nk) * CellSize); + } + + float cell_tri_dist(Vector3i idx, int tid) + { + Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; + Vector3d c = cell_center(idx); + Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); + return (float)point_triangle_distance(ref c, ref xp, ref xq, ref xr); } @@ -191,7 +207,7 @@ void make_level_set3(Vector3f origin, float dx, DenseGrid3f distances, int exact_band) { distances.resize(ni, nj, nk); - distances.assign((ni + nj + nk) * dx); // upper bound on distance + distances.assign(upper_bound(distances)); // upper bound on distance // closest triangle id for each grid cell DenseGrid3i closest_tri = new DenseGrid3i(ni, nj, nk, -1); @@ -288,14 +304,12 @@ void make_level_set3(Vector3f origin, float dx, - - void make_level_set3_parallel(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f distances, int exact_band) { distances.resize(ni, nj, nk); - distances.assign((float)((ni + nj + nk) * dx)); // upper bound on distance + distances.assign(upper_bound(grid)); // upper bound on distance // closest triangle id for each grid cell DenseGrid3i closest_tri = new DenseGrid3i(ni, nj, nk, -1); @@ -367,13 +381,12 @@ void make_level_set3_parallel(Vector3f origin, float dx, } } }); - + if (DebugPrint) System.Console.WriteLine("done narrow-band"); if (CancelF()) return; - if (ComputeSigns == true) { - if (DebugPrint) System.Console.WriteLine("done narrow-band"); + if (ComputeSigns == true) { compute_intersections(origin, dx, ni, nj, nk, intersection_count); if (CancelF()) @@ -416,17 +429,12 @@ void make_level_set3_parallel(Vector3f origin, float dx, - - - - - void make_level_set3_parallel_spatial(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3f distances, int exact_band) { distances.resize(ni, nj, nk); - float upper_bound = (float)((ni + nj + nk) * dx); + float upper_bound = this.upper_bound(distances); distances.assign(upper_bound); // upper bound on distance // closest triangle id for each grid cell @@ -449,8 +457,6 @@ void make_level_set3_parallel_spatial(Vector3f origin, float dx, // separate spinlock per-row, this resulted in a few-percent performance improvement. // Also tried pre-sorting triangles into disjoint regions, this did not help much except // on "perfect" cases like a sphere. - int wi = ni / 2, wj = nj / 2, wk = nk / 2; - bool abort = false; gParallel.ForEach(Mesh.TriangleIndices(), (tid) => { if (tid % 100 == 0) @@ -823,9 +829,9 @@ static public double point_triangle_distance(ref Vector3d x0, ref Vector3d x1, r Vector3d x13 = (x1 - x3); Vector3d x23 = (x2 - x3); Vector3d x03 = (x0 - x3); - double m13 = x13.LengthSquared, m23 = x23.LengthSquared, d = x13.Dot(x23); + double m13 = x13.LengthSquared, m23 = x23.LengthSquared, d = x13.Dot(ref x23); double invdet = 1.0 / Math.Max(m13 * m23 - d * d, 1e-30); - double a = x13.Dot(x03), b = x23.Dot(x03); + double a = x13.Dot(ref x03), b = x23.Dot(ref x03); // the barycentric coordinates themselves double w23 = invdet * (m23 * a - d * b); double w31 = invdet * (m13 * b - d * a); From 9967c56d3628e4e813946b9d68f3b015c808b05e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 3 Jul 2018 23:42:27 -0400 Subject: [PATCH 180/225] add new floodfill compute mode that uses spatial data structure more efficiently (most of the time...) --- spatial/MeshSignedDistanceGrid.cs | 201 ++++++++++++++++++++++++++++-- 1 file changed, 194 insertions(+), 7 deletions(-) diff --git a/spatial/MeshSignedDistanceGrid.cs b/spatial/MeshSignedDistanceGrid.cs index 45369ecd..3bd29478 100644 --- a/spatial/MeshSignedDistanceGrid.cs +++ b/spatial/MeshSignedDistanceGrid.cs @@ -61,10 +61,16 @@ public class MeshSignedDistanceGrid public enum ComputeModes { FullGrid = 0, - NarrowBandOnly = 1 + NarrowBandOnly = 1, + NarrowBand_SpatialFloodFill = 2 } public ComputeModes ComputeMode = ComputeModes.NarrowBandOnly; + // how wide of narrow band should we compute. This value is + // currently only used if there is a spatial data structure, as + // we can efficiently explore the space (in that case ExactBandWidth is not used) + public double NarrowBandMaxDistance = 0; + // should we try to compute signs? if not, grid remains unsigned public bool ComputeSigns = true; @@ -115,6 +121,8 @@ public void Compute() AxisAlignedBox3d bounds = Mesh.CachedBounds; float fBufferWidth = 2 * ExactBandWidth * CellSize; + if (ComputeMode == ComputeModes.NarrowBand_SpatialFloodFill) + fBufferWidth = (float)Math.Max(fBufferWidth, 2 * NarrowBandMaxDistance); grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One - (Vector3f)ExpandBounds; Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One + (Vector3f)ExpandBounds; int ni = (int)((max.x - grid_origin.x) / CellSize) + 1; @@ -122,13 +130,21 @@ public void Compute() int nk = (int)((max.z - grid_origin.z) / CellSize) + 1; grid = new DenseGrid3f(); - if (UseParallel) { - if ( Spatial != null ) - make_level_set3_parallel_spatial(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); - else - make_level_set3_parallel(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + if (ComputeMode == ComputeModes.NarrowBand_SpatialFloodFill) { + if (Spatial == null || NarrowBandMaxDistance == 0 || UseParallel == false) + throw new Exception("MeshSignedDistanceGrid.Compute: must set Spatial data structure and band max distance, and UseParallel=true"); + make_level_set3_parallel_floodfill(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } else { - make_level_set3(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + if (UseParallel) { + if (Spatial != null) { + make_level_set3_parallel_spatial(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } else { + make_level_set3_parallel(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } + } else { + make_level_set3(grid_origin, CellSize, ni, nj, nk, grid, ExactBandWidth); + } } } @@ -570,6 +586,177 @@ void make_level_set3_parallel_spatial(Vector3f origin, float dx, + + + + + void make_level_set3_parallel_floodfill(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f distances, int exact_band) + { + distances.resize(ni, nj, nk); + float upper_bound = this.upper_bound(distances); + distances.assign(upper_bound); // upper bound on distance + + // closest triangle id for each grid cell + DenseGrid3i closest_tri = new DenseGrid3i(ni, nj, nk, -1); + + // intersection_count(i,j,k) is # of tri intersections in (i-1,i]x{j}x{k} + DenseGrid3i intersection_count = new DenseGrid3i(ni, nj, nk, 0); + + if (DebugPrint) System.Console.WriteLine("start"); + + double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; + double invdx = 1.0 / dx; + + // compute values at vertices + + SpinLock grid_lock = new SpinLock(); + List Q = new List(); + bool[] done = new bool[distances.size]; + + bool abort = false; + gParallel.ForEach(Mesh.VertexIndices(), (vid) => { + if (vid % 100 == 0) abort = CancelF(); + if (abort) return; + + Vector3d v = Mesh.GetVertex(vid); + // real ijk coordinates of v + double fi = (v.x-ox)*invdx, fj = (v.y-oy)*invdx, fk = (v.z-oz)*invdx; + Vector3i idx = new Vector3i( + MathUtil.Clamp((int)fi, 0, ni - 1), + MathUtil.Clamp((int)fj, 0, nj - 1), + MathUtil.Clamp((int)fk, 0, nk - 1)); + + if (distances[idx] < upper_bound) + return; + + bool taken = false; + grid_lock.Enter(ref taken); + + Vector3d p = cell_center(idx); + int near_tid = Spatial.FindNearestTriangle(p); + Triangle3d tri = new Triangle3d(); + Mesh.GetTriVertices(near_tid, ref tri.V0, ref tri.V1, ref tri.V2); + Vector3d closest = new Vector3d(), bary = new Vector3d(); + double dsqr = DistPoint3Triangle3.DistanceSqr(ref p, ref tri, out closest, out bary); + distances[idx] = (float)Math.Sqrt(dsqr); + closest_tri[idx] = near_tid; + int idx_linear = distances.to_linear(ref idx); + Q.Add(idx_linear); + done[idx_linear] = true; + grid_lock.Exit(); + }); + if (DebugPrint) System.Console.WriteLine("done vertices"); + if (CancelF()) + return; + + // we could do this parallel w/ some kind of producer-consumer... + List next_Q = new List(); + AxisAlignedBox3i bounds = distances.BoundsInclusive; + double max_dist = NarrowBandMaxDistance; + double max_query_dist = max_dist + (2*dx*MathUtil.SqrtTwo); + int next_pass_count = Q.Count; + while (next_pass_count > 0) { + + next_Q.Clear(); + gParallel.ForEach(Q, (cur_linear_index) => { + Vector3i cur_idx = distances.to_index(cur_linear_index); + foreach (Vector3i idx_offset in gIndices.GridOffsets26) { + Vector3i nbr_idx = cur_idx + idx_offset; + if (bounds.Contains(nbr_idx) == false) + continue; + int nbr_linear_idx = distances.to_linear(ref nbr_idx); + if (done[nbr_linear_idx]) + continue; + + Vector3d p = cell_center(nbr_idx); + int near_tid = Spatial.FindNearestTriangle(p, max_query_dist); + if (near_tid == -1) { + done[nbr_linear_idx] = true; + continue; + } + + Triangle3d tri = new Triangle3d(); + Mesh.GetTriVertices(near_tid, ref tri.V0, ref tri.V1, ref tri.V2); + Vector3d closest = new Vector3d(), bary = new Vector3d(); + double dsqr = DistPoint3Triangle3.DistanceSqr(ref p, ref tri, out closest, out bary); + double dist = Math.Sqrt(dsqr); + + bool taken = false; + grid_lock.Enter(ref taken); + if (done[nbr_linear_idx] == false) { + distances[nbr_linear_idx] = (float)dist; + closest_tri[nbr_linear_idx] = near_tid; + done[nbr_linear_idx] = true; + if (dist < max_dist) + next_Q.Add(nbr_linear_idx); + } + grid_lock.Exit(); + } + }); + // swap lists + var tmp = Q; Q = next_Q; next_Q = tmp; + next_pass_count = Q.Count; + } + if (DebugPrint) System.Console.WriteLine("done floodfill"); + if (CancelF()) + return; + + + if (ComputeSigns == true) { + + if (DebugPrint) System.Console.WriteLine("done narrow-band"); + + compute_intersections(origin, dx, ni, nj, nk, intersection_count); + if (CancelF()) + return; + + if (DebugPrint) System.Console.WriteLine("done intersections"); + + if (ComputeMode == ComputeModes.FullGrid) { + // and now we fill in the rest of the distances with fast sweeping + for (int pass = 0; pass < 2; ++pass) { + sweep_pass(origin, dx, distances, closest_tri); + if (CancelF()) + return; + } + if (DebugPrint) System.Console.WriteLine("done sweeping"); + } else { + // nothing! + if (DebugPrint) System.Console.WriteLine("skipped sweeping"); + } + + if (DebugPrint) System.Console.WriteLine("done sweeping"); + + // then figure out signs (inside/outside) from intersection counts + compute_signs(ni, nj, nk, distances, intersection_count); + if (CancelF()) + return; + + if (WantIntersectionsGrid) + intersections_grid = intersection_count; + + if (DebugPrint) System.Console.WriteLine("done signs"); + } + + if (WantClosestTriGrid) + closest_tri_grid = closest_tri; + + } // end make_level_set_3 + + + + + + + + + + + + + // sweep through grid in different directions, distances and closest tris void sweep_pass(Vector3f origin, float dx, DenseGrid3f distances, DenseGrid3i closest_tri) From 54d0fa5b0fd0b24d9be734081bca503e9da26b16 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 5 Jul 2018 11:23:32 -0400 Subject: [PATCH 181/225] add MeshEditor.RemoveSmallComponents, MeshMeasurements.VolumeArea calc --- mesh/MeshEditor.cs | 30 ++++++++++++++++++++++++++++++ mesh/MeshMeasurements.cs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index c07e68d9..2c8dad67 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -849,6 +849,36 @@ public bool RemoveAllBowtieVertices(bool bRepeatUntilClean) + + /// + /// Remove any connected components with volume < min_volume area lt; min_area + /// + public int RemoveSmallComponents(double min_volume, double min_area) + { + MeshConnectedComponents C = new MeshConnectedComponents(Mesh); + C.FindConnectedT(); + if (C.Count == 1) + return 0; + int nRemoved = 0; + foreach (var comp in C.Components) { + Vector2d vol_area = MeshMeasurements.VolumeArea(Mesh, comp.Indices, Mesh.GetVertex); + if (vol_area.x < min_volume || vol_area.y < min_area) { + MeshEditor.RemoveTriangles(Mesh, comp.Indices); + nRemoved++; + } + } + return nRemoved; + } + public static int RemoveSmallComponents(DMesh3 mesh, double min_volume, double min_area) { + MeshEditor e = new MeshEditor(mesh); return e.RemoveSmallComponents(min_volume, min_area); + } + + + + + + + // this is for backing out changes we have made... bool remove_triangles(int[] tri_list, int count) { diff --git a/mesh/MeshMeasurements.cs b/mesh/MeshMeasurements.cs index 5b0a833a..a4d8aaad 100644 --- a/mesh/MeshMeasurements.cs +++ b/mesh/MeshMeasurements.cs @@ -149,6 +149,42 @@ public static void MassProperties( + /// + /// Compute volume and surface area of triangles of mesh. + /// Return value is (volume,area) + /// Note that if triangles don't define closed region, volume is probably nonsense... + /// + public static Vector2d VolumeArea( DMesh3 mesh, IEnumerable triangles, + Func getVertexF) + { + double mass_integral = 0.0; + double area_sum = 0; + foreach (int tid in triangles) { + Index3i tri = mesh.GetTriangle(tid); + // Get vertices of triangle i. + Vector3d v0 = getVertexF(tri.a); + Vector3d v1 = getVertexF(tri.b); + Vector3d v2 = getVertexF(tri.c); + + // Get cross product of edges and (un-normalized) normal vector. + Vector3d V1mV0 = v1 - v0; + Vector3d V2mV0 = v2 - v0; + Vector3d N = V1mV0.Cross(V2mV0); + + area_sum += 0.5 * N.Length; + + double tmp0 = v0.x + v1.x; + double f1x = tmp0 + v2.x; + mass_integral += N.x * f1x; + } + + return new Vector2d(mass_integral * (1.0/6.0), area_sum); + } + + + + + public static Vector3d Centroid(IEnumerable vertices) { From 996ca11b377ae15c13bfa046e8d0620b61aa7872 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 5 Jul 2018 22:14:18 -0400 Subject: [PATCH 182/225] added MeshEditor.RemoveFinTriangles --- mesh/MeshEditor.cs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 2c8dad67..ef9df316 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -369,6 +369,38 @@ public static bool RemoveIsolatedTriangles(DMesh3 mesh) + /// + /// Remove 'fin' triangles that have only one connected triangle. + /// Removing one fin can create another, by default will keep iterating + /// until all fins removed (in a not very efficient way!). + /// Pass bRepeatToConvergence=false to only do one pass. + /// [TODO] if we are repeating, construct face selection from nbrs of first list and iterate over that on future passes! + /// + public static int RemoveFinTriangles(DMesh3 mesh, bool bRepeatToConvergence = true) + { + MeshEditor editor = new MeshEditor(mesh); + + int nRemoved = 0; + List to_remove = new List(); + repeat: + foreach ( int tid in mesh.TriangleIndices()) { + Index3i nbrs = mesh.GetTriNeighbourTris(tid); + int c = ((nbrs.a != DMesh3.InvalidID)?1:0) + ((nbrs.b != DMesh3.InvalidID)?1:0) + ((nbrs.c != DMesh3.InvalidID)?1:0); + if (c <= 1) + to_remove.Add(tid); + } + if (to_remove.Count == 0) + return nRemoved; + nRemoved += to_remove.Count; + RemoveTriangles(mesh, to_remove, true); + to_remove.Clear(); + if (bRepeatToConvergence) + goto repeat; + return nRemoved; + } + + + /// /// Disconnect the given triangles from their neighbours, by duplicating "boundary" vertices, ie /// vertices on edges for which one triangle is in-set and the other is not. From 2a587ca68e3503fee5f5985789e2282751fb9602 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 16:53:17 -0400 Subject: [PATCH 183/225] catch cases where weights are invalid --- mesh/DMesh3.cs | 6 ++++-- mesh/MeshWeights.cs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/mesh/DMesh3.cs b/mesh/DMesh3.cs index f2e95488..794b7dda 100644 --- a/mesh/DMesh3.cs +++ b/mesh/DMesh3.cs @@ -1628,8 +1628,10 @@ public void VtxOneRingCentroid(int vID, ref Vector3d centroid) centroid.z += vertices[other_idx + 2]; n++; } - double d = 1.0 / n; - centroid.x *= d; centroid.y *= d; centroid.z *= d; + if (n > 0) { + double d = 1.0 / n; + centroid.x *= d; centroid.y *= d; centroid.z *= d; + } } } diff --git a/mesh/MeshWeights.cs b/mesh/MeshWeights.cs index 354e1807..a16b1d06 100644 --- a/mesh/MeshWeights.cs +++ b/mesh/MeshWeights.cs @@ -141,6 +141,8 @@ public static Vector3d MeanValueCentroid(DMesh3 mesh, int v_i) vSum += w_ij * Vj; wSum += w_ij; } + if ( wSum < MathUtil.ZeroTolerance ) + return Vi; return vSum / wSum; } // tan(theta/2) = +/- sqrt( (1-cos(theta)) / (1+cos(theta)) ) From c0bd53539dc86f5d7b68a7f2568d1110403a7aae Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 16:53:38 -0400 Subject: [PATCH 184/225] add MeshEditor.RemoveUnusedVertices --- mesh/MeshEditor.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index ef9df316..97b9b1f3 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -882,6 +882,28 @@ public bool RemoveAllBowtieVertices(bool bRepeatUntilClean) + /// + /// Remove any unused vertices in mesh, ie vertices with no edges. + /// Returns number of removed vertices. + /// + public int RemoveUnusedVertices() + { + int nRemoved = 0; + int NV = Mesh.MaxVertexID; + for ( int vid = 0; vid < NV; ++vid) { + if (Mesh.IsVertex(vid) && Mesh.GetVtxEdgeCount(vid) == 0) { + Mesh.RemoveVertex(vid); + ++nRemoved; + } + } + return nRemoved; + } + + + + + + /// /// Remove any connected components with volume < min_volume area lt; min_area /// From a03ebf478d5c988b8d66aa814da51dc9640312a2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 16:54:21 -0400 Subject: [PATCH 185/225] remove this function. was contributed by user but is not called in any of our code and I'm not sure what it is meant to do. This could now be done in a Remesher subclass. --- mesh/Remesher.cs | 50 ------------------------------------------------ 1 file changed, 50 deletions(-) diff --git a/mesh/Remesher.cs b/mesh/Remesher.cs index be375627..4f784eba 100644 --- a/mesh/Remesher.cs +++ b/mesh/Remesher.cs @@ -676,56 +676,6 @@ protected virtual void FullProjectionPass() } - // Project vertices towards projection target by input alpha, and optionally, don't project vertices too far away - // We can do projection in parallel if we have .net - // [TODO] this code is currently not called - protected virtual void FullProjectionPass(double projectionAlpha, double maxProjectDistance) - { - projectionAlpha = MathUtil.Clamp(projectionAlpha, 0, 1); - - Action project; - - if (maxProjectDistance < double.MaxValue && maxProjectDistance > 0) { - project = (vID) => { - if (vertex_is_constrained(vID)) - return; - if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) - return; - Vector3d curpos = mesh.GetVertex(vID); - Vector3d projected = target.Project(curpos, vID); - - var distance = curpos.Distance(projected); - if (distance < maxProjectDistance) { - projected = Vector3d.Lerp(curpos, projected, projectionAlpha); - double distanceAlpha = distance / maxProjectDistance; - projected = Vector3d.Lerp(projected, curpos, distanceAlpha); - mesh.SetVertex(vID, projected); - } - }; - } else { - project = (vID) => { - if (vertex_is_constrained(vID)) - return; - if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) - return; - Vector3d curpos = mesh.GetVertex(vID); - Vector3d projected = target.Project(curpos, vID); - projected = Vector3d.Lerp(curpos, projected, projectionAlpha); - mesh.SetVertex(vID, projected); - }; - } - - if (EnableParallelProjection) { - gParallel.ForEach(project_vertices(), project); - } else { - foreach (int vid in project_vertices()) - project(vid); - } - } - - - - [Conditional("DEBUG")] void RuntimeDebugCheck(int eid) { From d556dd2e3d53122ca9eb3bc58501a3bbef43288f Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 17:03:03 -0400 Subject: [PATCH 186/225] minor bit --- mesh/MeshEditor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 97b9b1f3..d023807c 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -898,6 +898,9 @@ public int RemoveUnusedVertices() } return nRemoved; } + public static int RemoveUnusedVertices(DMesh3 mesh) { + MeshEditor e = new MeshEditor(mesh); return e.RemoveUnusedVertices(); + } From 3324d8d3c2e4998115433b825fe87a0a6b9da56a Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 17:11:20 -0400 Subject: [PATCH 187/225] add optional filter function to RemoveFinTriangles --- mesh/MeshEditor.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index d023807c..ea0d5b70 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -376,7 +376,7 @@ public static bool RemoveIsolatedTriangles(DMesh3 mesh) /// Pass bRepeatToConvergence=false to only do one pass. /// [TODO] if we are repeating, construct face selection from nbrs of first list and iterate over that on future passes! /// - public static int RemoveFinTriangles(DMesh3 mesh, bool bRepeatToConvergence = true) + public static int RemoveFinTriangles(DMesh3 mesh, Func removeF = null, bool bRepeatToConvergence = true) { MeshEditor editor = new MeshEditor(mesh); @@ -386,8 +386,10 @@ public static int RemoveFinTriangles(DMesh3 mesh, bool bRepeatToConvergence = tr foreach ( int tid in mesh.TriangleIndices()) { Index3i nbrs = mesh.GetTriNeighbourTris(tid); int c = ((nbrs.a != DMesh3.InvalidID)?1:0) + ((nbrs.b != DMesh3.InvalidID)?1:0) + ((nbrs.c != DMesh3.InvalidID)?1:0); - if (c <= 1) - to_remove.Add(tid); + if (c <= 1) { + if (removeF == null || removeF(tid) == true ) + to_remove.Add(tid); + } } if (to_remove.Count == 0) return nRemoved; From 8dac7101f6c1d76ae71d7c304d3a7d9a4b89d8df Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 6 Jul 2018 17:12:44 -0400 Subject: [PATCH 188/225] modified filter func so it passes mesh as argument (hence no capture needed if we use lambda...maybe) --- mesh/MeshEditor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index ea0d5b70..fda24553 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -376,7 +376,7 @@ public static bool RemoveIsolatedTriangles(DMesh3 mesh) /// Pass bRepeatToConvergence=false to only do one pass. /// [TODO] if we are repeating, construct face selection from nbrs of first list and iterate over that on future passes! /// - public static int RemoveFinTriangles(DMesh3 mesh, Func removeF = null, bool bRepeatToConvergence = true) + public static int RemoveFinTriangles(DMesh3 mesh, Func removeF = null, bool bRepeatToConvergence = true) { MeshEditor editor = new MeshEditor(mesh); @@ -387,7 +387,7 @@ public static int RemoveFinTriangles(DMesh3 mesh, Func removeF = null Index3i nbrs = mesh.GetTriNeighbourTris(tid); int c = ((nbrs.a != DMesh3.InvalidID)?1:0) + ((nbrs.b != DMesh3.InvalidID)?1:0) + ((nbrs.c != DMesh3.InvalidID)?1:0); if (c <= 1) { - if (removeF == null || removeF(tid) == true ) + if (removeF == null || removeF(mesh, tid) == true ) to_remove.Add(tid); } } From 528552b4b4d52b9e209481086afa4a95b1f3a523 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 7 Jul 2018 23:10:11 -0400 Subject: [PATCH 189/225] float variant --- spatial/DenseGrid3.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spatial/DenseGrid3.cs b/spatial/DenseGrid3.cs index 6407d680..2b36404c 100644 --- a/spatial/DenseGrid3.cs +++ b/spatial/DenseGrid3.cs @@ -82,6 +82,12 @@ public float this[Vector3i ijk] { set { Buffer[ijk.x + ni * (ijk.y + nj * ijk.z)] = value; } } + public void get_x_pair(int i0, int j, int k, out float a, out float b) + { + int offset = ni * (j + nj * k); + a = Buffer[offset + i0]; + b = Buffer[offset + i0 + 1]; + } public void get_x_pair(int i0, int j, int k, out double a, out double b) { int offset = ni * (j + nj * k); From b0c685749298e8a387fd4314574bc94b97257f02 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 7 Jul 2018 23:10:36 -0400 Subject: [PATCH 190/225] variant of bvtree.FindNearestTriangle that also returns the squared-distance that we have already computed. Otherwise computing again requires a bunch of work. --- spatial/DMeshAABBTree.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 359350f6..de5a770c 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -128,6 +128,20 @@ public virtual int FindNearestTriangle(Vector3d p, double fMaxDist = double.MaxV find_nearest_tri(root_index, p, ref fNearestSqr, ref tNearID); return tNearID; } + /// + /// Find the triangle closest to p, and distance to it, within distance fMaxDist, or return InvalidID + /// Use MeshQueries.TriangleDistance() to get more information + /// + public virtual int FindNearestTriangle(Vector3d p, out double fNearestDistSqr, double fMaxDist = double.MaxValue) + { + if (mesh_timestamp != mesh.ShapeTimestamp) + throw new Exception("DMeshAABBTree3.FindNearestTriangle: mesh has been modified since tree construction"); + + fNearestDistSqr = (fMaxDist < double.MaxValue) ? fMaxDist * fMaxDist : double.MaxValue; + int tNearID = DMesh3.InvalidID; + find_nearest_tri(root_index, p, ref fNearestDistSqr, ref tNearID); + return tNearID; + } protected void find_nearest_tri(int iBox, Vector3d p, ref double fNearestSqr, ref int tID) { int idx = box_to_index[iBox]; From da164ab58deaa53b64a81161c896d502d1b1caef Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sat, 7 Jul 2018 23:11:30 -0400 Subject: [PATCH 191/225] use static function to get point/tri distance, instead of calling TriangleDistance() which returns an instance of PointTriDistance class, much more expensive! --- queries/MeshQueries.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index 9d9ddfb3..032ea13c 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -46,7 +46,11 @@ public static double NearestPointDistance(DMesh3 mesh, ISpatial spatial, Vector3 int tid = spatial.FindNearestTriangle(queryPoint, maxDist); if (tid == DMesh3.InvalidID) return double.MaxValue; - return Math.Sqrt(TriangleDistance(mesh, tid, queryPoint).DistanceSquared); + Triangle3d tri = new Triangle3d(); + mesh.GetTriVertices(tid, ref tri.V0, ref tri.V1, ref tri.V2); + Vector3d closest, bary; + double dist_sqr = DistPoint3Triangle3.DistanceSqr(ref queryPoint, ref tri, out closest, out bary); + return Math.Sqrt(dist_sqr); } From d6debfbea844b8552c1a252842f98a0a1b289a7d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 8 Jul 2018 16:33:18 -0400 Subject: [PATCH 192/225] bit of a hack, but useful --- implicit/Implicit3d.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs index 22d99735..ea6d70cc 100644 --- a/implicit/Implicit3d.cs +++ b/implicit/Implicit3d.cs @@ -636,6 +636,7 @@ public class SkeletalRicciNaryBlend3d : BoundedImplicitFunction3d { public List Children; public double BlendPower = 2.0; + public double FieldShift = 0; public double Value(ref Vector3d pt) { @@ -657,7 +658,7 @@ public double Value(ref Vector3d pt) } f = Math.Pow(f, 1.0 / BlendPower); } - return f; + return f + FieldShift; } public AxisAlignedBox3d Bounds() From b3d79ac3f4ccd404924832663a87c14a39d2d214 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 19 Jul 2018 20:10:56 -0400 Subject: [PATCH 193/225] util fin --- mesh/MeshEditor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 2c8dad67..afc1e716 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -803,6 +803,11 @@ public static void AppendBox(DMesh3 mesh, Vector3d pos, Vector3d normal, float s MeshEditor editor = new MeshEditor(mesh); editor.AppendBox(new Frame3f(pos, normal), size*Vector3f.One, color); } + public static void AppendBox(DMesh3 mesh, Frame3f frame, Vector3f size, Colorf color) + { + MeshEditor editor = new MeshEditor(mesh); + editor.AppendBox(frame, size, color); + } public static void AppendLine(DMesh3 mesh, Segment3d seg, float size) { From 79fb6b2cc5a6a01f5ee7d1c06770b445392b5cc2 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 19 Jul 2018 20:11:38 -0400 Subject: [PATCH 194/225] FaceGroupUtil.SeparateMeshByGroups now returns group IDs, also static convenience version --- math/BoundsUtil.cs | 8 ++++++++ mesh/FaceGroupUtil.cs | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/math/BoundsUtil.cs b/math/BoundsUtil.cs index 33fc873f..5ed01001 100644 --- a/math/BoundsUtil.cs +++ b/math/BoundsUtil.cs @@ -7,6 +7,14 @@ namespace g3 public static class BoundsUtil { + public static AxisAlignedBox3d Bounds(IEnumerable meshes) { + AxisAlignedBox3d bounds = AxisAlignedBox3d.Empty; + foreach (DMesh3 mesh in meshes) + bounds.Contain(mesh.CachedBounds); + return bounds; + } + + public static AxisAlignedBox3d Bounds(IPointSet source) { AxisAlignedBox3d bounds = AxisAlignedBox3d.Empty; foreach (int vid in source.VertexIndices()) diff --git a/mesh/FaceGroupUtil.cs b/mesh/FaceGroupUtil.cs index 6d2f5257..8ee72e58 100644 --- a/mesh/FaceGroupUtil.cs +++ b/mesh/FaceGroupUtil.cs @@ -157,7 +157,7 @@ public static List FindTrianglesByGroup(IMesh mesh, int findGroupID) /// split input mesh into submeshes based on group ID /// **does not** separate disconnected components w/ same group ID /// - public static DMesh3[] SeparateMeshByGroups(DMesh3 mesh) + public static DMesh3[] SeparateMeshByGroups(DMesh3 mesh, out int[] groupIDs) { Dictionary> meshes = new Dictionary>(); foreach ( int tid in mesh.TriangleIndices() ) { @@ -167,18 +167,23 @@ public static DMesh3[] SeparateMeshByGroups(DMesh3 mesh) tris = new List(); meshes[gid] = tris; } - tris.Add(gid); + tris.Add(tid); } DMesh3[] result = new DMesh3[meshes.Count]; + groupIDs = new int[meshes.Count]; int k = 0; - foreach ( var tri_list in meshes.Values) { + foreach ( var pair in meshes ) { + groupIDs[k] = pair.Key; + List tri_list = pair.Value; result[k++] = DSubmesh3.QuickSubmesh(mesh, tri_list); } return result; } - + public static DMesh3[] SeparateMeshByGroups(DMesh3 mesh) { + int[] ids; return SeparateMeshByGroups(mesh, out ids); + } } From d1e70fac8f5dc822f530e567fa6932c7c995b0a7 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 25 Jul 2018 16:43:29 -0400 Subject: [PATCH 195/225] rewrite vertex-correspondence parts, handles partial-loop-merging better and provides more feedback about failures. --- mesh_ops/PlanarHoleFiller.cs | 148 ++++++++++++++++++++++++++++++++--- 1 file changed, 137 insertions(+), 11 deletions(-) diff --git a/mesh_ops/PlanarHoleFiller.cs b/mesh_ops/PlanarHoleFiller.cs index 5a8fefd0..ad4daaf8 100644 --- a/mesh_ops/PlanarHoleFiller.cs +++ b/mesh_ops/PlanarHoleFiller.cs @@ -5,6 +5,24 @@ namespace g3 { + /// + /// Try to fill planar holes in a mesh. The fill is computed by mapping the hole boundary into 2D, + /// filling using 2D algorithms, and then mapping back to 3D. This allows us to properly handle cases like + /// nested holes (eg from slicing a torus in half). + /// + /// PlanarComplex is used to sort the input 2D polyons. + /// + /// MeshInsertUVPolyCurve is used to insert each 2D polygon into a generated planar mesh. + /// The resolution of the generated mesh is controlled by .FillTargetEdgeLen + /// + /// In theory this approach can handle more geometric degeneracies than Delaunay triangluation. + /// However, the current code requires that MeshInsertUVPolyCurve produce output boundary loops that + /// have a 1-1 correspondence with the input polygons. This is not always possible. + /// + /// Currently these failure cases are not handled properly. In that case the loops will + /// not be stitched. + /// + /// public class PlanarHoleFiller { public DMesh3 Mesh; @@ -24,6 +42,14 @@ public class PlanarHoleFiller /// public bool MergeFillBoundary = true; + + /* + * Error feedback + */ + public bool OutputHasCracks = false; + public int FailedInsertions = 0; + public int FailedMerges = 0; + // these will be computed if you don't set them Vector3d PlaneX, PlaneY; @@ -76,6 +102,11 @@ public void AddFillLoops(IEnumerable loops) } + /// + /// Compute the fill mesh and append it. + /// This returns false if anything went wrong. + /// The Error Feedback properties (.OutputHasCracks, etc) will provide more info. + /// public bool Fill() { compute_polygons(); @@ -188,7 +219,6 @@ public bool Fill() //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); // figure out map between new mesh and original edge loops - // [TODO] if # of verts is different, we can still find correspondence, it is just harder // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh // if not, can try to delete nbr tris to repair IndexMap mergeMapV = new IndexMap(true); @@ -203,16 +233,14 @@ public bool Fill() int loopi = ElemToLoopMap[sourceElem]; EdgeLoop sourceLoop = Loops[loopi].edgeLoop; - if (sourceLoop.VertexCount != NV) { - failed_merges.Add(new Index2i(fi, pi)); - continue; - } + // construct vertex-merge map for this loop + List bad_indices = build_merge_map(FillMesh, fillLoopVerts, Mesh, sourceLoop.Vertices, + MathUtil.ZeroTolerancef, mergeMapV); - for (int k = 0; k < NV; ++k) { - Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); - Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); - if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) - mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; + bool errors = (bad_indices != null && bad_indices.Count > 0); + if (errors) { + failed_inserts.Add(new Index2i(fi, pi)); + OutputHasCracks = true; } } } @@ -222,9 +250,12 @@ public bool Fill() int[] mapV; editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); - // [TODO] should verify that we actually merged the loops... + // [TODO] should verify that we actually merged all the loops. If there are bad_indices + // we could fill them } + FailedInsertions = failed_inserts.Count; + FailedMerges = failed_merges.Count; if (failed_inserts.Count > 0 || failed_merges.Count > 0) return false; @@ -233,6 +264,101 @@ public bool Fill() + + /// + /// Construct vertex correspondences between fill mesh boundary loop + /// and input mesh boundary loop. In ideal case there is an easy 1-1 + /// correspondence. If that is not true, then do a brute-force search + /// to find the best correspondences we can. + /// + /// Currently only returns unique correspondences. If any vertex + /// matches with multiple input vertices it is not merged. + /// [TODO] we could do better in many cases... + /// + /// Return value is list of indices into fillLoopV that were not merged + /// + List build_merge_map(DMesh3 fillMesh, int[] fillLoopV, + DMesh3 targetMesh, int[] targetLoopV, + double tol, IndexMap mergeMapV) + { + if (fillLoopV.Length == targetLoopV.Length) { + if (build_merge_map_simple(fillMesh, fillLoopV, targetMesh, targetLoopV, tol, mergeMapV)) + return null; + } + + int NF = fillLoopV.Length, NT = targetLoopV.Length; + bool[] doneF = new bool[NF], doneT = new bool[NT]; + int[] countF = new int[NF], countT = new int[NT]; + List errorV = new List(); + + SmallListSet matchF = new SmallListSet(); matchF.Resize(NF); + + // find correspondences + double tol_sqr = tol*tol; + for (int i = 0; i < NF; ++i ) { + if ( fillMesh.IsVertex(fillLoopV[i]) == false ) { + doneF[i] = true; + errorV.Add(i); + continue; + } + matchF.AllocateAt(i); + Vector3d v = fillMesh.GetVertex(fillLoopV[i]); + for ( int j = 0; j < NT; ++j ) { + Vector3d v2 = targetMesh.GetVertex(targetLoopV[j]); + if ( v.DistanceSquared(ref v2) < tol_sqr ) { + matchF.Insert(i, j); + } + } + } + + for ( int i = 0; i < NF; ++i ) { + if (doneF[i]) continue; + if ( matchF.Count(i) == 1 ) { + int j = matchF.First(i); + mergeMapV[fillLoopV[i]] = targetLoopV[j]; + doneF[i] = true; + } + } + + for ( int i = 0; i < NF; ++i ) { + if (doneF[i] == false) + errorV.Add(i); + } + + return errorV; + } + + + + + /// + /// verifies that there is a 1-1 correspondence between the fill and target loops. + /// If so, adds to mergeMapV and returns true; + /// + bool build_merge_map_simple(DMesh3 fillMesh, int[] fillLoopV, + DMesh3 targetMesh, int[] targetLoopV, + double tol, IndexMap mergeMapV ) + { + if (fillLoopV.Length != targetLoopV.Length) + return false; + int NV = fillLoopV.Length; + for (int k = 0; k < NV; ++k) { + if (!fillMesh.IsVertex(fillLoopV[k])) + return false; + Vector3d fillV = fillMesh.GetVertex(fillLoopV[k]); + Vector3d sourceV = Mesh.GetVertex(targetLoopV[k]); + if (fillV.Distance(sourceV) > tol) + return false; + } + for (int k = 0; k < NV; ++k) + mergeMapV[fillLoopV[k]] = targetLoopV[k]; + return true; + } + + + + + void compute_polygons() { Bounds = AxisAlignedBox2d.Empty; From 0c9801fd3f60968ca0e2888c835a245813e045f9 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 26 Jul 2018 11:13:59 -0400 Subject: [PATCH 196/225] added support for returning STL binary face attributes, and added documentation --- io/STLReader.cs | 69 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/io/STLReader.cs b/io/STLReader.cs index bacc6966..091561b6 100644 --- a/io/STLReader.cs +++ b/io/STLReader.cs @@ -8,23 +8,63 @@ namespace g3 { + /// + /// Read ASCII/Binary STL file and produce set of meshes. + /// + /// Since STL is just a list of disconnected triangles, by default we try to + /// merge vertices together. Use .RebuildStrategy to disable this and/or configure + /// which algorithm is used. If you are using via StandardMeshReader, you can add + /// .StrategyFlag to ReadOptions.CustomFlags to set this flag. + /// + /// TODO: document welding strategies. There is no "best" one, they all fail + /// in some cases, because STL is a stupid and horrible format. + /// + /// STL Binary supports a per-triangle short-int that is usually used to specify color. + /// However since we do not support per-triangle color in DMesh3, this color + /// cannot be directly used. Instead of hardcoding behavior, we return the list of shorts + /// if requested via IMeshBuilder Metadata. Set .WantPerTriAttribs=true or attach flag .PerTriAttribFlag. + /// After the read finishes you can get the face color list via: + /// DVector colors = Builder.Metadata[0][STLReader.PerTriAttribMetadataName] as DVector; + /// (for DMesh3Builder, which is the only builder that supports Metadata) + /// public class STLReader : IMeshReader { public enum Strategy { - NoProcessing = 0, - IdenticalVertexWeld = 1, - TolerantVertexWeld = 2, + NoProcessing = 0, // return triangle soup + IdenticalVertexWeld = 1, // merge identical vertices. Logically sensible but doesn't always work on ASCII STL. + TolerantVertexWeld = 2, // merge vertices within .WeldTolerance - AutoBestResult = 3 + AutoBestResult = 3 // try identical weld first, if there are holes then try tolerant weld, and return "best" result + // ("best" is not well-defined...) } + + /// + /// Which algorithm is used to try to reconstruct mesh topology from STL triangle soup + /// public Strategy RebuildStrategy = Strategy.AutoBestResult; + /// + /// Vertices within this distance are considered "the same" by welding strategies. + /// public double WeldTolerance = MathUtil.ZeroTolerancef; - // connect to this to get warning messages + /// + /// Binary STL supports per-triangle integer attribute, which is often used + /// to store face colors. If this flag is true, we will attach these face + /// colors to the returned mesh via IMeshBuilder.AppendMetaData + /// + public bool WantPerTriAttribs = false; + + /// + /// name argument passed to IMeshBuilder.AppendMetaData + /// + public static string PerTriAttribMetadataName = "tri_attrib"; + + + /// connect to this event to get warning messages public event ParsingMessagesHandler warningEvent; @@ -33,12 +73,21 @@ public enum Strategy + /// ReadOptions.CustomFlags flag for configuring .RebuildStrategy public const string StrategyFlag = "-stl-weld-strategy"; + + /// ReadOptions.CustomFlags flag for configuring .WantPerTriAttribs + public const string PerTriAttribFlag = "-want-tri-attrib"; + + void ParseArguments(CommandArgumentSet args) { if ( args.Integers.ContainsKey(StrategyFlag) ) { RebuildStrategy = (Strategy)args.Integers[StrategyFlag]; } + if (args.Flags.ContainsKey(PerTriAttribFlag)) { + WantPerTriAttribs = true; + } } @@ -48,6 +97,7 @@ protected class STLSolid { public string Name; public DVectorArray3f Vertices = new DVectorArray3f(); + public DVector TriAttribs = null; } @@ -88,6 +138,8 @@ public IOReadResult Read(BinaryReader reader, ReadOptions options, IMeshBuilder stl_triangle tmp = new stl_triangle(); Type tri_type = tmp.GetType(); + DVector tri_attribs = new DVector(); + try { for (int i = 0; i < totalTris; ++i) { byte[] tri_bytes = reader.ReadBytes(50); @@ -100,6 +152,7 @@ public IOReadResult Read(BinaryReader reader, ReadOptions options, IMeshBuilder append_vertex(tri.ax, tri.ay, tri.az); append_vertex(tri.bx, tri.by, tri.bz); append_vertex(tri.cx, tri.cy, tri.cz); + tri_attribs.Add(tri.attrib); } } catch (Exception e) { @@ -108,6 +161,9 @@ public IOReadResult Read(BinaryReader reader, ReadOptions options, IMeshBuilder Marshal.FreeHGlobal(bufptr); + if (Objects.Count == 1) + Objects[0].TriAttribs = tri_attribs; + foreach (STLSolid solid in Objects) BuildMesh(solid, builder); @@ -219,6 +275,9 @@ protected virtual void BuildMesh(STLSolid solid, IMeshBuilder builder) } else { BuildMesh_NoMerge(solid, builder); } + + if (WantPerTriAttribs && solid.TriAttribs != null && builder.SupportsMetaData) + builder.AppendMetaData(PerTriAttribMetadataName, solid.TriAttribs); } From ad5002529f05d8b31e89ace7b1ef618708661131 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 26 Jul 2018 18:01:54 -0400 Subject: [PATCH 197/225] added SimpleStore utility class that makes it easy to read/write test cases --- io/gSerialization.cs | 103 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/io/gSerialization.cs b/io/gSerialization.cs index 5337371c..eaac6169 100644 --- a/io/gSerialization.cs +++ b/io/gSerialization.cs @@ -561,6 +561,109 @@ public static void Restore(ref string[] s, BinaryReader reader) for (int i = 0; i < N; ++i) Restore(ref s[i], reader); } + } + + + + /// + /// Utility class that is intended to support things like writing and reading + /// test cases, etc. You can write out a test case in a single line, eg + /// SimpleStore.Store(path, new object[] { TestMesh, VertexList, PlaneNormal, ... }) + /// The object list will be binned into the relevant sublists automatically. + /// Then you can load this data via: + /// SimpleStore s = SimpleStore.Restore(path) + /// + public class SimpleStore + { + // only ever append to this list! + public List Meshes = new List(); + public List Points = new List(); + public List Strings = new List(); + public List> IntLists = new List>(); + + public SimpleStore() + { + } + + public SimpleStore(object[] objs) + { + Add(objs); + } + + public void Add(object[] objs) + { + foreach (object o in objs) { + if (o is DMesh3) + Meshes.Add(o as DMesh3); + else if (o is string) + Strings.Add(o as String); + else if (o is List) + IntLists.Add(o as List); + else if (o is IEnumerable) + IntLists.Add(new List(o as IEnumerable)); + else if (o is Vector3d) + Points.Add((Vector3d)o); + else + throw new Exception("SimpleStore: unknown type " + o.GetType().ToString()); + } + } + + + public static void Store(string sPath, object[] objs) + { + SimpleStore s = new SimpleStore(objs); + Store(sPath, s); + } + + public static void Store(string sPath, SimpleStore s) + { + using (FileStream stream = new FileStream(sPath, FileMode.Create)) { + using (BinaryWriter w = new BinaryWriter(stream)) { + w.Write(s.Meshes.Count); + for (int k = 0; k < s.Meshes.Count; ++k) + gSerialization.Store(s.Meshes[k], w); + w.Write(s.Points.Count); + for (int k = 0; k < s.Points.Count; ++k) + gSerialization.Store(s.Points[k], w); + w.Write(s.Strings.Count); + for (int k = 0; k < s.Strings.Count; ++k) + gSerialization.Store(s.Strings[k], w); + + w.Write(s.IntLists.Count); + for (int k = 0; k < s.IntLists.Count; ++k) + gSerialization.Store(s.IntLists[k], w); + } + } + } + + + public static SimpleStore Restore(string sPath) + { + SimpleStore s = new SimpleStore(); + using (FileStream stream = new FileStream(sPath, FileMode.Open)) { + using (BinaryReader r = new BinaryReader(stream)) { + int nMeshes = r.ReadInt32(); + for ( int k = 0; k < nMeshes; ++k ) { + DMesh3 m = new DMesh3(); gSerialization.Restore(m, r); s.Meshes.Add(m); + } + int nPoints = r.ReadInt32(); + for (int k = 0; k < nPoints; ++k) { + Vector3d v = Vector3d.Zero; gSerialization.Restore(ref v, r); s.Points.Add(v); + } + int nStrings = r.ReadInt32(); + for (int k = 0; k < nStrings; ++k) { + string str = null; gSerialization.Restore(ref str, r); s.Strings.Add(str); + } + int nIntLists = r.ReadInt32(); + for (int k = 0; k < nIntLists; ++k) { + List l = new List(); gSerialization.Restore(l, r); s.IntLists.Add(l); + } + } + } + return s; + } } + + } From 3507332dfeea34707031d817423e999356216547 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 26 Jul 2018 18:02:49 -0400 Subject: [PATCH 198/225] allow MeshEditor.StitchUnorderedEdges to optionally return partial-success result. Allows us to handle any holes further downstream. Common failure cases is input bowtie vertex, can't do the stitch because it would be non-manifold, but only leaves a one-triangle hole that we can easily repair. --- mesh/MeshEditor.cs | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 9aeb7873..8251b11b 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -182,12 +182,18 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) /// /// Stitch two sets of boundary edges that are provided as unordered pairs of edges, by /// adding triangulated quads between each edge pair. - /// If a failure is encountered during stitching, the triangles added up to that point are removed. + /// If bAbortOnFailure==true and a failure is encountered during stitching, the triangles added up to that point are removed. + /// If bAbortOnFailure==false, failures are ignored and the returned triangle list may contain invalid values! /// - public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id = -1) + public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id, bool bAbortOnFailure, out bool stitch_incomplete) { int N = EdgePairs.Count; int[] new_tris = new int[N * 2]; + if (bAbortOnFailure == false) { + for (int k = 0; k < new_tris.Length; ++k) + new_tris[k] = DMesh3.InvalidID; + } + stitch_incomplete = false; int i = 0; for (; i < N; ++i) { @@ -195,16 +201,20 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id // look up and orient the first edge Index4i edge_a = Mesh.GetEdge(edges.a); - if ( edge_a.d != DMesh3.InvalidID ) - goto operation_failed; + if (edge_a.d != DMesh3.InvalidID) { + if (bAbortOnFailure) goto operation_failed; + else { stitch_incomplete = true; continue; } + } Index3i edge_a_tri = Mesh.GetTriangle(edge_a.c); int a = edge_a.a, b = edge_a.b; IndexUtil.orient_tri_edge(ref a, ref b, edge_a_tri); // look up and orient the second edge Index4i edge_b = Mesh.GetEdge(edges.b); - if (edge_b.d != DMesh3.InvalidID) - goto operation_failed; + if (edge_b.d != DMesh3.InvalidID) { + if (bAbortOnFailure) goto operation_failed; + else { stitch_incomplete = true; continue; } + } Index3i edge_b_tri = Mesh.GetTriangle(edge_b.c); int c = edge_b.a, d = edge_b.b; IndexUtil.orient_tri_edge(ref c, ref d, edge_b_tri); @@ -218,8 +228,10 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); - if (tid1 < 0 || tid2 < 0) - goto operation_failed; + if (tid1 < 0 || tid2 < 0) { + if (bAbortOnFailure) goto operation_failed; + else { stitch_incomplete = true; continue; } + } new_tris[2 * i] = tid1; new_tris[2 * i + 1] = tid2; @@ -235,6 +247,11 @@ public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id } return null; } + public virtual int[] StitchUnorderedEdges(List EdgePairs, int group_id = -1, bool bAbortOnFailure = true) + { + bool incomplete = false; + return StitchUnorderedEdges(EdgePairs, group_id, bAbortOnFailure, out incomplete); + } From 80fd3e0541444a88c504730b2c997798b0aab5cf Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 26 Jul 2018 18:03:16 -0400 Subject: [PATCH 199/225] provide feedback about partial extrude failures (in join stitching) --- mesh_ops/MeshExtrudeFaces.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mesh_ops/MeshExtrudeFaces.cs b/mesh_ops/MeshExtrudeFaces.cs index 482f2c77..13ec1a95 100644 --- a/mesh_ops/MeshExtrudeFaces.cs +++ b/mesh_ops/MeshExtrudeFaces.cs @@ -21,8 +21,6 @@ public class MeshExtrudeFaces public DMesh3 Mesh; public int[] Triangles; - // arguments - public SetGroupBehavior Group = SetGroupBehavior.AutoGenerate; // set new position based on original loop vertex position, normal, and index @@ -32,7 +30,9 @@ public class MeshExtrudeFaces public List EdgePairs; // pairs of edges (original, extruded) that were stitched together public MeshVertexSelection ExtrudeVertices; // vertices of extruded region public int[] JoinTriangles; // triangles generated to connect original end extruded edges together + // may contain invalid triangle IDs if JoinIncomplete=true public int JoinGroupID; // group ID of connection triangles + public bool JoinIncomplete = false; // if true, errors were encountered during the join operation public MeshExtrudeFaces(DMesh3 mesh, int[] triangles, bool bForceCopyArray = false) @@ -69,11 +69,17 @@ public virtual ValidationStatus Validate() } + /// + /// Apply the extrustion operation to input Mesh. + /// Will return false if operation is not completed. + /// However changes are not backed out, so if false is returned, input Mesh is in + /// undefined state (generally means there are some holes) + /// public virtual bool Extrude() { MeshEditor editor = new MeshEditor(Mesh); - editor.SeparateTriangles(Triangles, true, out EdgePairs); + bool bOK = editor.SeparateTriangles(Triangles, true, out EdgePairs); MeshNormals normals = null; bool bHaveNormals = Mesh.HasVertexNormals; @@ -97,9 +103,9 @@ public virtual bool Extrude() Mesh.SetVertex(vid, NewVertices[k++]); JoinGroupID = Group.GetGroupID(Mesh); - JoinTriangles = editor.StitchUnorderedEdges(EdgePairs, JoinGroupID); + JoinTriangles = editor.StitchUnorderedEdges(EdgePairs, JoinGroupID, false, out JoinIncomplete); - return true; + return JoinTriangles != null && JoinIncomplete == false; } From c589fdfd754e1f7f7ffc4a709fc2bec8cb8bdea0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 26 Jul 2018 18:15:02 -0400 Subject: [PATCH 200/225] add MeshFaceSelection.RemoveBowties, removes bowtie vertices in selection. Optionally call in LocalOptimize - defaults to true for parameter-free LocalOptimize(). --- mesh_selection/MeshFaceSelection.cs | 63 +++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/mesh_selection/MeshFaceSelection.cs b/mesh_selection/MeshFaceSelection.cs index c8d8dd89..e9540801 100644 --- a/mesh_selection/MeshFaceSelection.cs +++ b/mesh_selection/MeshFaceSelection.cs @@ -488,28 +488,85 @@ public bool FillEars(bool bFillTinyHoles) } // returns true if selection was modified - public bool LocalOptimize(bool bClipFins, bool bFillEars, bool bFillTinyHoles = true, bool bClipLoners = true) + public bool LocalOptimize(bool bClipFins, bool bFillEars, bool bFillTinyHoles = true, bool bClipLoners = true, bool bRemoveBowties = false) { bool bModified = false; bool done = false; + int count = 0; + HashSet temp_hash = new HashSet(); while ( ! done ) { done = true; + if (count++ == 25) // terminate in case we get stuck + break; if (bClipFins && ClipFins(bClipLoners)) done = false; if (bFillEars && FillEars(bFillTinyHoles)) done = false; + if (bRemoveBowties && remove_bowties(temp_hash)) + done = false; if (done == false) bModified = true; } + if (bRemoveBowties) + remove_bowties(temp_hash); // do a final pass of this because it is usually the most problematic... return bModified; } - public bool LocalOptimize() { - return LocalOptimize(true, true, true, true); + public bool LocalOptimize(bool bRemoveBowties = true) { + return LocalOptimize(true, true, true, true, bRemoveBowties); } + /// + /// Find any "bowtie" vertices - ie vertex v such taht there is multiple spans of triangles + /// selected in v's triangle one-ring - and deselect those one-rings. + /// Returns true if selection was modified. + /// + public bool RemoveBowties() { + return remove_bowties(null); + } + public bool remove_bowties(HashSet tempHash) + { + bool bModified = false; + bool done = false; + HashSet vertices = (tempHash == null) ? new HashSet() : tempHash; + while (!done) { + done = true; + vertices.Clear(); + foreach (int tid in Selected) { + Index3i tv = Mesh.GetTriangle(tid); + vertices.Add(tv.a); vertices.Add(tv.b); vertices.Add(tv.c); + } + + foreach (int vid in vertices) { + if (is_bowtie_vtx(vid)) { + Deselect(Mesh.VtxTrianglesItr(vid)); + done = false; + } + } + if (done == false) + bModified = true; + } + return bModified; + } + private bool is_bowtie_vtx(int vid) + { + int border_edges = 0; + foreach ( int eid in Mesh.VtxEdgesItr(vid) ) { + Index2i et = Mesh.GetEdgeT(eid); + if (et.b != DMesh3.InvalidID) { + bool in_a = IsSelected(et.a); + bool in_b = IsSelected(et.b); + if (in_a != in_b) + border_edges++; + } else { + if (IsSelected(et.a)) + border_edges++; + } + } + return border_edges > 2; + } From 7bff03dac3046cc3b55d84c1bd99bee4f2d71786 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Sun, 29 Jul 2018 15:13:08 -0400 Subject: [PATCH 201/225] add 2D bezier curve, laplacian curve deformer --- curve/BezierCurve2.cs | 246 +++++++++++++++++++++ curve/LaplacianCurveDeformer.cs | 372 ++++++++++++++++++++++++++++++++ 2 files changed, 618 insertions(+) create mode 100644 curve/BezierCurve2.cs create mode 100644 curve/LaplacianCurveDeformer.cs diff --git a/curve/BezierCurve2.cs b/curve/BezierCurve2.cs new file mode 100644 index 00000000..0444f77b --- /dev/null +++ b/curve/BezierCurve2.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace g3 +{ + /// + /// 2D Bezier curve of arbitrary degree + /// Ported from WildMagic5 Wm5BezierCurve2 + /// + public class BezierCurve2 : BaseCurve2, IParametricCurve2d + { + int mDegree; + int mNumCtrlPoints; + Vector2d[] mCtrlPoint; + Vector2d[] mDer1CtrlPoint; + Vector2d[] mDer2CtrlPoint; + Vector2d[] mDer3CtrlPoint; + DenseMatrix mChoose; + + + public int Degree { get { return mDegree; } } + public Vector2d[] ControlPoints { get { return mCtrlPoint; } } + + + public BezierCurve2(int degree, Vector2d[] ctrlPoint, bool bTakeOwnership = false) : base(0, 1) + { + if ( degree < 2 ) + throw new Exception("BezierCurve2() The degree must be three or larger\n"); + + int i, j; + + mDegree = degree; + mNumCtrlPoints = mDegree + 1; + if (bTakeOwnership) { + mCtrlPoint = ctrlPoint; + } else { + mCtrlPoint = new Vector2d[ctrlPoint.Length]; + Array.Copy(ctrlPoint, mCtrlPoint, ctrlPoint.Length); + } + + // Compute first-order differences. + mDer1CtrlPoint = new Vector2d[mNumCtrlPoints - 1]; + for (i = 0; i < mNumCtrlPoints - 1; ++i) { + mDer1CtrlPoint[i] = mCtrlPoint[i + 1] - mCtrlPoint[i]; + } + + // Compute second-order differences. + mDer2CtrlPoint = new Vector2d[mNumCtrlPoints - 2]; + for (i = 0; i < mNumCtrlPoints - 2; ++i) { + mDer2CtrlPoint[i] = mDer1CtrlPoint[i + 1] - mDer1CtrlPoint[i]; + } + + // Compute third-order differences. + if (degree >= 3) { + mDer3CtrlPoint = new Vector2d[mNumCtrlPoints - 3]; + for (i = 0; i < mNumCtrlPoints - 3; ++i) { + mDer3CtrlPoint[i] = mDer2CtrlPoint[i + 1] - mDer2CtrlPoint[i]; + } + } else { + mDer3CtrlPoint = null; + } + + // Compute combinatorial values Choose(N,K), store in mChoose[N,K]. + // The values mChoose[r,c] are invalid for r < c (use only the + // entries for r >= c). + mChoose = new DenseMatrix(mNumCtrlPoints, mNumCtrlPoints); + + mChoose[0,0] = 1.0; + mChoose[1,0] = 1.0; + mChoose[1,1] = 1.0; + for (i = 2; i <= mDegree; ++i) { + mChoose[i,0] = 1.0; + mChoose[i,i] = 1.0; + for (j = 1; j < i; ++j) { + mChoose[i,j] = mChoose[i - 1,j - 1] + mChoose[i - 1,j]; + } + } + } + + + // used in Clone() + protected BezierCurve2() : base(0, 1) + { + } + + + public override Vector2d GetPosition(double t) + { + double oneMinusT = 1 - t; + double powT = t; + Vector2d result = oneMinusT * mCtrlPoint[0]; + + for (int i = 1; i < mDegree; ++i) { + double coeff = mChoose[mDegree,i] * powT; + result = (result + mCtrlPoint[i] * coeff) * oneMinusT; + powT *= t; + } + + result += mCtrlPoint[mDegree] * powT; + + return result; + } + + + public override Vector2d GetFirstDerivative(double t) + { + double oneMinusT = 1 - t; + double powT = t; + Vector2d result = oneMinusT * mDer1CtrlPoint[0]; + + int degreeM1 = mDegree - 1; + for (int i = 1; i < degreeM1; ++i) { + double coeff = mChoose[degreeM1,i] * powT; + result = (result + mDer1CtrlPoint[i] * coeff) * oneMinusT; + powT *= t; + } + + result += mDer1CtrlPoint[degreeM1] * powT; + result *= (double)mDegree; + + return result; + } + + + public override Vector2d GetSecondDerivative(double t) + { + double oneMinusT = 1 - t; + double powT = t; + Vector2d result = oneMinusT * mDer2CtrlPoint[0]; + + int degreeM2 = mDegree - 2; + for (int i = 1; i < degreeM2; ++i) { + double coeff = mChoose[degreeM2,i] * powT; + result = (result + mDer2CtrlPoint[i] * coeff) * oneMinusT; + powT *= t; + } + + result += mDer2CtrlPoint[degreeM2] * powT; + result *= (double)(mDegree * (mDegree - 1)); + + return result; + } + + + public override Vector2d GetThirdDerivative(double t) + { + if (mDegree < 3) { + return Vector2d.Zero; + } + + double oneMinusT = 1 - t; + double powT = t; + Vector2d result = oneMinusT * mDer3CtrlPoint[0]; + + int degreeM3 = mDegree - 3; + for (int i = 1; i < degreeM3; ++i) { + double coeff = mChoose[degreeM3,i] * powT; + result = (result + mDer3CtrlPoint[i] * coeff) * oneMinusT; + powT *= t; + } + + result += mDer3CtrlPoint[degreeM3] * powT; + result *= (double)(mDegree * (mDegree - 1) * (mDegree - 2)); + + return result; + } + + + + /* + * IParametricCurve2d implementation + */ + + // TODO: could support closed bezier? + public bool IsClosed { + get { return false; } + } + + // can call SampleT in range [0,ParamLength] + public double ParamLength { + get { return mTMax - mTMin; } + } + public Vector2d SampleT(double t) + { + return GetPosition(t); + } + + public Vector2d TangentT(double t) + { + return GetFirstDerivative(t).Normalized; + } + + public bool HasArcLength { + get { return true; } + } + public double ArcLength { + get { return GetTotalLength(); } + } + public Vector2d SampleArcLength(double a) + { + double t = GetTime(a); + return GetPosition(t); + } + + public void Reverse() + { + throw new NotSupportedException("NURBSCurve2.Reverse: how to reverse?!?"); + } + + public IParametricCurve2d Clone() + { + BezierCurve2 c2 = new BezierCurve2(); + c2.mDegree = this.mDegree; + c2.mNumCtrlPoints = this.mNumCtrlPoints; + + c2.mCtrlPoint = (Vector2d[])this.mCtrlPoint.Clone(); + c2.mDer1CtrlPoint = (Vector2d[])this.mDer1CtrlPoint.Clone(); + c2.mDer2CtrlPoint = (Vector2d[])this.mDer2CtrlPoint.Clone(); + c2.mDer3CtrlPoint = (Vector2d[])this.mDer3CtrlPoint.Clone(); + c2.mChoose = new DenseMatrix(this.mChoose); + return c2; + } + + + public bool IsTransformable { get { return true; } } + public void Transform(ITransform2 xform) + { + for (int k = 0; k < mCtrlPoint.Length; ++k) + mCtrlPoint[k] = xform.TransformP(mCtrlPoint[k]); + + // update derivatives + for (int i = 0; i < mNumCtrlPoints - 1; ++i) + mDer1CtrlPoint[i] = mCtrlPoint[i+1] - mCtrlPoint[i]; + for (int i = 0; i < mNumCtrlPoints - 2; ++i) + mDer2CtrlPoint[i] = mDer1CtrlPoint[i+1] - mDer1CtrlPoint[i]; + if (mDegree >= 3) { + for (int i = 0; i < mNumCtrlPoints - 3; ++i) + mDer3CtrlPoint[i] = mDer2CtrlPoint[i + 1] - mDer2CtrlPoint[i]; + } + } + + + } +} diff --git a/curve/LaplacianCurveDeformer.cs b/curve/LaplacianCurveDeformer.cs new file mode 100644 index 00000000..4debee3c --- /dev/null +++ b/curve/LaplacianCurveDeformer.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace g3 +{ + /// + /// Variant of LaplacianMeshDeformer that can be applied to 3D curve. + /// + /// Solve in each dimension can be disabled using .SolveX/Y/Z + /// + /// Currently only supports uniform weights (in Initialize) + /// + /// + public class LaplacianCurveDeformer + { + public DCurve3 Curve; + + + public bool SolveX = true; + public bool SolveY = true; + public bool SolveZ = true; + + + // indicates that solve did not converge in at least one dimension + public bool ConvergeFailed = false; + + + // info that is fixed based on mesh + PackedSparseMatrix PackedM; + int N; + int[] ToCurveV, ToIndex; + double[] Px, Py, Pz; + int[] nbr_counts; + double[] MLx, MLy, MLz; + + // constraints + public struct SoftConstraintV + { + public Vector3d Position; + public double Weight; + public bool PostFix; + } + Dictionary SoftConstraints = new Dictionary(); + bool HavePostFixedConstraints = false; + + + // needs to be updated after constraints + bool need_solve_update; + DiagonalMatrix WeightsM; + double[] Cx, Cy, Cz; + double[] Bx, By, Bz; + DiagonalMatrix Preconditioner; + + + // Appendix C from http://sites.fas.harvard.edu/~cs277/papers/deformation_survey.pdf + public bool UseSoftConstraintNormalEquations = true; + + + // result + double[] Sx, Sy, Sz; + + + public LaplacianCurveDeformer(DCurve3 curve) + { + Curve = curve; + } + + + public void SetConstraint(int vID, Vector3d targetPos, double weight, bool bForceToFixedPos = false) + { + SoftConstraints[vID] = new SoftConstraintV() { Position = targetPos, Weight = weight, PostFix = bForceToFixedPos }; + HavePostFixedConstraints = HavePostFixedConstraints || bForceToFixedPos; + need_solve_update = true; + } + + public bool IsConstrained(int vID) { + return SoftConstraints.ContainsKey(vID); + } + + public void ClearConstraints() + { + SoftConstraints.Clear(); + HavePostFixedConstraints = false; + need_solve_update = true; + } + + + public void Initialize() + { + int NV = Curve.VertexCount; + ToCurveV = new int[NV]; + ToIndex = new int[NV]; + + N = 0; + for ( int k = 0; k < NV; k++) { + int vid = k; + ToCurveV[N] = vid; + ToIndex[vid] = N; + N++; + } + + Px = new double[N]; + Py = new double[N]; + Pz = new double[N]; + nbr_counts = new int[N]; + SymmetricSparseMatrix M = new SymmetricSparseMatrix(); + + for (int i = 0; i < N; ++i) { + int vid = ToCurveV[i]; + Vector3d v = Curve.GetVertex(vid); + Px[i] = v.x; Py[i] = v.y; Pz[i] = v.z; + nbr_counts[i] = (i == 0 || i == N-1) ? 1 : 2; + } + + // construct laplacian matrix + for (int i = 0; i < N; ++i) { + int vid = ToCurveV[i]; + int n = nbr_counts[i]; + + Index2i nbrs = Curve.Neighbours(vid); + + double sum_w = 0; + for ( int k = 0; k < 2; ++k ) { + int nbrvid = nbrs[k]; + if (nbrvid == -1) + continue; + int j = ToIndex[nbrvid]; + int n2 = nbr_counts[j]; + + // weight options + double w = -1; + //double w = -1.0 / Math.Sqrt(n + n2); + //double w = -1.0 / n; + + M.Set(i, j, w); + sum_w += w; + } + sum_w = -sum_w; + M.Set(vid, vid, sum_w); + } + + // transpose(L) * L, but matrix is symmetric... + if (UseSoftConstraintNormalEquations) { + //M = M.Multiply(M); + // only works if M is symmetric!! + PackedM = M.SquarePackedParallel(); + } else { + PackedM = new PackedSparseMatrix(M); + } + + // compute laplacian vectors of initial mesh positions + MLx = new double[N]; + MLy = new double[N]; + MLz = new double[N]; + PackedM.Multiply(Px, MLx); + PackedM.Multiply(Py, MLy); + PackedM.Multiply(Pz, MLz); + + // allocate memory for internal buffers + Preconditioner = new DiagonalMatrix(N); + WeightsM = new DiagonalMatrix(N); + Cx = new double[N]; Cy = new double[N]; Cz = new double[N]; + Bx = new double[N]; By = new double[N]; Bz = new double[N]; + Sx = new double[N]; Sy = new double[N]; Sz = new double[N]; + + need_solve_update = true; + UpdateForSolve(); + } + + + + + void UpdateForSolve() + { + if (need_solve_update == false) + return; + + // construct constraints matrix and RHS + WeightsM.Clear(); + Array.Clear(Cx, 0, N); + Array.Clear(Cy, 0, N); + Array.Clear(Cz, 0, N); + foreach ( var constraint in SoftConstraints ) { + int vid = constraint.Key; + int i = ToIndex[vid]; + double w = constraint.Value.Weight; + + if (UseSoftConstraintNormalEquations) + w = w * w; + + WeightsM.Set(i, i, w); + Vector3d pos = constraint.Value.Position; + Cx[i] = w * pos.x; + Cy[i] = w * pos.y; + Cz[i] = w * pos.z; + } + + // add RHS vectors + for (int i = 0; i < N; ++i) { + Bx[i] = MLx[i] + Cx[i]; + By[i] = MLy[i] + Cy[i]; + Bz[i] = MLz[i] + Cz[i]; + } + + // update basic preconditioner + // [RMS] currently not using this...it actually seems to make things worse!! + for ( int i = 0; i < N; i++ ) { + double diag_value = PackedM[i, i] + WeightsM[i, i]; + Preconditioner.Set(i, i, 1.0 / diag_value); + } + + need_solve_update = false; + } + + + + // Result must be as large as Mesh.MaxVertexID + public bool SolveMultipleCG(Vector3d[] Result) + { + if (WeightsM == null) + Initialize(); // force initialize... + + UpdateForSolve(); + + // use initial positions as initial solution. + Array.Copy(Px, Sx, N); + Array.Copy(Py, Sy, N); + Array.Copy(Pz, Sz, N); + + + Action CombinedMultiply = (X, B) => { + //PackedM.Multiply(X, B); + PackedM.Multiply_Parallel(X, B); + + for (int i = 0; i < N; ++i) + B[i] += WeightsM[i, i] * X[i]; + }; + + List Solvers = new List(); + if (SolveX) { + Solvers.Add(new SparseSymmetricCG() { B = Bx, X = Sx, + MultiplyF = CombinedMultiply, PreconditionMultiplyF = Preconditioner.Multiply, + UseXAsInitialGuess = true + }); + } + if (SolveY) { + Solvers.Add(new SparseSymmetricCG() { B = By, X = Sy, + MultiplyF = CombinedMultiply, PreconditionMultiplyF = Preconditioner.Multiply, + UseXAsInitialGuess = true + }); + } + if (SolveZ) { + Solvers.Add(new SparseSymmetricCG() { B = Bz, X = Sz, + MultiplyF = CombinedMultiply, PreconditionMultiplyF = Preconditioner.Multiply, + UseXAsInitialGuess = true + }); + } + bool[] ok = new bool[Solvers.Count]; + + gParallel.ForEach(Interval1i.Range(Solvers.Count), (i) => { + ok[i] = Solvers[i].Solve(); + // preconditioned solve is slower =\ + //ok[i] = solvers[i].SolvePreconditioned(); + }); + + ConvergeFailed = false; + foreach ( bool b in ok ) { + if (b == false) + ConvergeFailed = true; + } + + for ( int i = 0; i < N; ++i ) { + int vid = ToCurveV[i]; + Result[vid] = new Vector3d(Sx[i], Sy[i], Sz[i]); + } + + // apply post-fixed constraints + if (HavePostFixedConstraints) { + foreach (var constraint in SoftConstraints) { + if (constraint.Value.PostFix) { + int vid = constraint.Key; + Result[vid] = constraint.Value.Position; + } + } + } + + return true; + } + + + + + // Result must be as large as Mesh.MaxVertexID + public bool SolveMultipleRHS(Vector3d[] Result) + { + if (WeightsM == null) + Initialize(); // force initialize... + + UpdateForSolve(); + + // use initial positions as initial solution. + double[][] B = BufferUtil.InitNxM(3, N, new double[][] { Bx, By, Bz }); + double[][] X = BufferUtil.InitNxM(3, N, new double[][] { Px, Py, Pz }); + + Action CombinedMultiply = (Xt, Bt) => { + PackedM.Multiply_Parallel_3(Xt, Bt); + gParallel.ForEach(Interval1i.Range(3), (j) => { + BufferUtil.MultiplyAdd(Bt[j], WeightsM.D, Xt[j]); + }); + }; + + SparseSymmetricCGMultipleRHS Solver = new SparseSymmetricCGMultipleRHS() { + B = B, X = X, + MultiplyF = CombinedMultiply, PreconditionMultiplyF = null, + UseXAsInitialGuess = true + }; + + bool ok = Solver.Solve(); + + if (ok == false) + return false; + + for (int i = 0; i < N; ++i) { + int vid = ToCurveV[i]; + Result[vid] = new Vector3d(X[0][i], X[1][i], X[2][i]); + } + + // apply post-fixed constraints + if (HavePostFixedConstraints) { + foreach (var constraint in SoftConstraints) { + if (constraint.Value.PostFix) { + int vid = constraint.Key; + Result[vid] = constraint.Value.Position; + } + } + } + + return true; + } + + + + + + public bool Solve(Vector3d[] Result) + { + // for small problems, faster to use separate CGs? + if ( Curve.VertexCount < 10000 ) + return SolveMultipleCG(Result); + else + return SolveMultipleRHS(Result); + } + + + + public bool SolveAndUpdateCurve() + { + int N = Curve.VertexCount; + Vector3d[] Result = new Vector3d[N]; + if ( Solve(Result) == false ) + return false; + for (int i = 0; i < N; ++i) { + Curve[i] = Result[i]; + } + return true; + } + + + + } +} From 4288537b09def19327b145be85bdab5115747037 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 30 Jul 2018 10:54:23 -0400 Subject: [PATCH 202/225] update readme with new tutorials, new Core classes --- README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1a92a056..053f4529 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Open-Source (Boost-license) C# library for geometric computing. -geometry3Sharp only uses C# language features available in .NET 3.5, so it works with the Mono C# runtime used in Unity 5.x (*NOTE: you must configure Unity for this to work, see note at bottom of this file*). +geometry3Sharp is compatible with Unity. Set the G3_USING_UNITY Scripting Define and you will have transparent interop between g3 and Unity vector types (*see details at the very bottom of this README*). Although the library is written for C# 4.5, if you are using the .NET 3.5 Unity runtime, it will still work, just with a few missing features. Currently there is a small amount of unsafe code, however this code is only used in a few fast-buffer-copy routines, which can be deleted if you need a safe version (eg for Unity web player). @@ -10,6 +10,14 @@ Currently there is a small amount of unsafe code, however this code is only used Questions? Contact Ryan Schmidt [@rms80](http://www.twitter.com/rms80) / [gradientspace](http://www.gradientspace.com) +# Projects using g3Sharp + +* [Gradientspace Cotangent](https://www.cotangent.io/) - 3D printing and Mesh Repair/Modeling Tool +* [Nia Technologies NiaFit](https://niatech.org/technology/niafit/) - 3D-printed prosthetic and orthotic design +* [OrthoVR Project](https://orthovrproject.org/) - 3D-printed lower-leg prosthetic design in VR +* [Archform](https://www.archform.co/) - Clear Dental Aligner design/planning app +* [Your Project Here?](rms@gradientspace.com) - *we are very excited to hear about your project!* + # Credits @@ -23,10 +31,13 @@ The **MeshSignedDistanceGrid** class was implemented based on the C++ [SDFGen](h Several tutorials for using g3Sharp have been posted on the Gradientspace blog: -- [Creating meshes, Mesh File I/O, Ray/Mesh Intersection and Nearest-Point](http://www.gradientspace.com/tutorials/2017/7/20/basic-mesh-creation-with-g3sharp) -- [Mesh Simplification with Reducer class](http://www.gradientspace.com/tutorials/2017/8/30/mesh-simplification) -- [Voxelization/Signed Distance Fields and Marching Cubes Remeshing](http://www.gradientspace.com/tutorials/2017/11/21/signed-distance-fields-tutorial) - +- [Creating meshes, Mesh File I/O, Ray/Mesh Intersection and Nearest-Point](http://www.gradientspace.com/tutorials/2017/7/20/basic-mesh-creation-with-g3sharp) - Explains DMesh3 basics, StandardMeshReader, DMeshAABBTree3 ray and point queries and custom traversals +- [Mesh Simplification with Reducer class](http://www.gradientspace.com/tutorials/2017/8/30/mesh-simplification) - Reducer class, DMesh3.CheckValidity, MeshConstraints +- [Remeshing and Mesh Constraints](http://www.gradientspace.com/tutorials/2018/7/5/remeshing-and-constraints) - Remesher class, projection targets, MeshConstraints, Unity remeshing animations +- [Voxelization/Signed Distance Fields and Marching Cubes Remeshing](http://www.gradientspace.com/tutorials/2017/11/21/signed-distance-fields-tutorial) - MeshSignedDistanceGrid, MarchingCubes, DenseGridTrilinearImplicit, generating 3D lattices +- [3D Bitmaps, Minecraft Cubes, and Mesh Winding Numbers](http://www.gradientspace.com/tutorials/2017/12/14/3d-bitmaps-and-minecraft-meshes) - Bitmap3, VoxelSurfaceGenerator, DMeshAABBTree3 Mesh Winding Number, +- [Implicit Surface Modeling](http://www.gradientspace.com/tutorials/2018/2/20/implicit-surface-modeling) - Implicit primitives, voxel/levelset/functional booleans, offsets, and blending, lattice/lightweighting demo +- [DMesh3: A Dynamic Indexed Triangle Mesh](http://www.gradientspace.com/tutorials/dmesh3) - deep dive into the DMesh3 class's internal data structures and operations # Main Classes @@ -47,6 +58,13 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - **IndexPriorityQueue**: min-heap priority queue for dense situations (ie small or large number of items in queue) - **DijkstraGraphDistance**: compute shortest-path distances between nodes in graph, from seed points. Graph is defined externally by iterators and Func's, so this class can easily be applied to many situations. - **SmallListSet**: efficient allocation of a large number of small lists, with initial fixed-size buffer and "spilling" into linked list. +- **BufferUtil**: utilities for working with arrays. Math on float/double arrays, automatic conversions, byte[] conversions, compression +- **FileSystemUtils**: utilities for filesystem stuff +- *g3Iterators*: IEnumerable utils **ConstantItr**, **RemapItr**, IList hacks **MappedList**, **IntSequence** +- **HashUtil**: **HashBuilder** util for constructing FNV hashes of g3 types +- **MemoryPool**: basic object pool +- *ProfileUtil*: code profiling utility **LocalProfiler** supports multiple timers, accumulating, etc +- *SafeCollections*: **SafeListBuilder** multi-threaded List construction and operator-apply ## Math From c0901251b20bed2a0d3702e6e09bd172b96721e5 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 3 Aug 2018 22:20:49 -0700 Subject: [PATCH 203/225] minor improvement to insert uv-curve --- mesh_ops/MeshInsertUVPolyCurve.cs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/mesh_ops/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs index eace99bb..68676e19 100644 --- a/mesh_ops/MeshInsertUVPolyCurve.cs +++ b/mesh_ops/MeshInsertUVPolyCurve.cs @@ -173,6 +173,7 @@ void insert_corners(HashSet MeshVertsOnCurve) Vector2d vInsert = Curve[i]; bool inserted = false; + // find the triangle that contains this curve point int contain_tid = DMesh3.InvalidID; if (triSpatial != null) { contain_tid = triSpatial.FindContainingTriangle(vInsert, inTriangleF); @@ -190,22 +191,40 @@ void insert_corners(HashSet MeshVertsOnCurve) } } - if (contain_tid != DMesh3.InvalidID ) { + // if we found one, insert the point via face-poke or edge-split, + // unless it is exactly at existing vertex, in which case we can re-use it + if ( contain_tid != DMesh3.InvalidID ) { Index3i tv = Mesh.GetTriangle(contain_tid); Vector3d bary = MathUtil.BarycentricCoords(vInsert, PointF(tv.a), PointF(tv.b), PointF(tv.c)); // SpatialEpsilon is our zero-tolerance, so merge if we are closer than that bool is_existing_v; int vid = insert_corner_from_bary(i, contain_tid, bary, 0.01, 100*SpatialEpsilon, out is_existing_v); - if (vid > 0) { // this should be always happening.. + if (vid > 0) { CurveVertices[i] = vid; if (is_existing_v) MeshVertsOnCurve.Add(vid); inserted = true; - } else { - throw new Exception("MeshInsertUVPolyCurve.insert_corners: failed to insert vertex " + i.ToString()); + } + } + + // if we did not find containing triangle, + // try matching with any existing vertices. + // This can happen if curve point is right on mesh border... + if (inserted == false) { + foreach (int vid in Mesh.VertexIndices()) { + Vector2d v = PointF(vid); + if (vInsert.Distance(v) < SpatialEpsilon) { + CurveVertices[i] = vid; + MeshVertsOnCurve.Add(vid); + inserted = true; + } } } + // TODO: also case where curve point is right on mesh border edge, + // and so it ends up being outside all triangles? + + if (inserted == false) { throw new Exception("MeshInsertUVPolyCurve.insert_corners: curve vertex " + i.ToString() + " is not inside or on any mesh triangle!"); From 932fde9f521c98adc9f148ebf64af8159aaa2dfe Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 11 Sep 2018 17:38:07 -0400 Subject: [PATCH 204/225] initial mesh fast winding number implementation --- geometry3Sharp.csproj | 4 + math/FastWindingMath.cs | 217 +++++++++++++++++++++++++++++++++++++++ mesh/MeshCaches.cs | 40 ++++++++ spatial/DMeshAABBTree.cs | 213 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 474 insertions(+) create mode 100644 math/FastWindingMath.cs create mode 100644 mesh/MeshCaches.cs diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index 76ab14d6..ec3ece6d 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -82,6 +82,7 @@ + @@ -92,6 +93,7 @@ + @@ -124,6 +126,7 @@ + @@ -153,6 +156,7 @@ + diff --git a/math/FastWindingMath.cs b/math/FastWindingMath.cs new file mode 100644 index 00000000..7f9366fd --- /dev/null +++ b/math/FastWindingMath.cs @@ -0,0 +1,217 @@ +using System; +using System.Collections.Generic; + +namespace g3 +{ + + + /// + /// Formulas for triangle winding number approximation + /// + public static class FastTriWinding + { + /// + /// precompute constant coefficients of triangle winding number approximation + /// p: 'center' of expansion for triangles (area-weighted centroid avg) + /// r: max distance from p to triangles + /// order1: first-order vector coeff + /// order2: second-order matrix coeff + /// triCache: optional precomputed triangle centroid/normal/area + /// + public static void ComputeCoeffs(DMesh3 mesh, IEnumerable triangles, + ref Vector3d p, ref double r, + ref Vector3d order1, ref Matrix3d order2, + MeshTriInfoCache triCache = null ) + { + p = Vector3d.Zero; + order1 = Vector3d.Zero; + order2 = Matrix3d.Zero; + r = 0; + + // compute area-weighted centroid of triangles, we use this as the expansion point + Vector3d P0 = Vector3d.Zero, P1 = Vector3d.Zero, P2 = Vector3d.Zero; + double sum_area = 0; + foreach (int tid in triangles) { + if (triCache != null) { + double area = triCache.Areas[tid]; + sum_area += area; + p += area * triCache.Centroids[tid]; + } else { + mesh.GetTriVertices(tid, ref P0, ref P1, ref P2); + double area = MathUtil.Area(ref P0, ref P1, ref P2); + sum_area += area; + p += area * ((P0 + P1 + P2) / 3.0); + } + } + p /= sum_area; + + // compute first and second-order coefficients of FWN taylor expansion, as well as + // 'radius' value r, which is max dist from any tri vertex to p + Vector3d n = Vector3d.Zero, c = Vector3d.Zero; double a = 0; + foreach ( int tid in triangles ) { + mesh.GetTriVertices(tid, ref P0, ref P1, ref P2); + + if (triCache == null) { + c = (1.0 / 3.0) * (P0 + P1 + P2); + n = MathUtil.FastNormalArea(ref P0, ref P1, ref P2, out a); + } else { + triCache.GetTriInfo(tid, ref n, ref a, ref c); + } + + order1 += a * n; + + Vector3d dcp = c - p; + order2 += a * new Matrix3d(ref dcp, ref n); + + // this is just for return value... + double maxdist = MathUtil.Max(P0.DistanceSquared(ref p), P1.DistanceSquared(ref p), P2.DistanceSquared(ref p)); + r = Math.Max(r, Math.Sqrt(maxdist)); + } + } + + + /// + /// Evaluate first-order FWN approximation at point q, relative to center c + /// + public static double EvaluateOrder1Approx(ref Vector3d center, ref Vector3d order1Coeff, ref Vector3d q) + { + Vector3d dpq = (center - q); + double len = dpq.Length; + + return (1.0 / MathUtil.FourPI) * order1Coeff.Dot(dpq / (len * len * len)); + } + + + /// + /// Evaluate second-order FWN approximation at point q, relative to center c + /// + public static double EvaluateOrder2Approx(ref Vector3d center, ref Vector3d order1Coeff, ref Matrix3d order2Coeff, ref Vector3d q) + { + Vector3d dpq = (center - q); + double len = dpq.Length; + double len3 = len * len * len; + double fourPi_len3 = 1.0 / (MathUtil.FourPI * len3); + + double order1 = fourPi_len3 * order1Coeff.Dot(ref dpq); + + // second-order hessian \grad^2(G) + double c = - 3.0 / (MathUtil.FourPI * len3 * len * len); + + // expanded-out version below avoids extra constructors + //Matrix3d xqxq = new Matrix3d(ref dpq, ref dpq); + //Matrix3d hessian = new Matrix3d(fourPi_len3, fourPi_len3, fourPi_len3) - c * xqxq; + Matrix3d hessian = new Matrix3d( + fourPi_len3 + c*dpq.x*dpq.x, c*dpq.x*dpq.y, c*dpq.x*dpq.z, + c*dpq.y*dpq.x, fourPi_len3 + c*dpq.y*dpq.y, c*dpq.y*dpq.z, + c*dpq.z*dpq.x, c*dpq.z*dpq.y, fourPi_len3 + c*dpq.z*dpq.z); + + double order2 = order2Coeff.InnerProduct(ref hessian); + + return order1 + order2; + } + + + + + // triangle-winding-number first-order approximation. + // t is triangle, p is 'center' of cluster of dipoles, q is evaluation point + // (This is really just for testing) + public static double Order1Approx(ref Triangle3d t, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + { + Vector3d at0 = xA * xn; + + Vector3d dpq = (p - q); + double len = dpq.Length; + double len3 = len * len * len; + + return (1.0 / MathUtil.FourPI) * at0.Dot(dpq / (len * len * len)); + } + + + // triangle-winding-number second-order approximation + // t is triangle, p is 'center' of cluster of dipoles, q is evaluation point + // (This is really just for testing) + public static double Order2Approx(ref Triangle3d t, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + { + Vector3d dpq = (p - q); + + double len = dpq.Length; + double len3 = len * len * len; + + // first-order approximation - integrated_normal_area * \grad(G) + double order1 = (xA / MathUtil.FourPI) * xn.Dot(dpq / len3); + + // second-order hessian \grad^2(G) + Matrix3d xqxq = new Matrix3d(ref dpq, ref dpq); + xqxq *= 3.0 / (MathUtil.FourPI * len3 * len * len); + double diag = 1 / (MathUtil.FourPI * len3); + Matrix3d hessian = new Matrix3d(diag, diag, diag) - xqxq; + + // second-order LHS - integrated second-order area matrix (formula 26) + Vector3d centroid = new Vector3d( + (t.V0.x + t.V1.x + t.V2.x) / 3.0, (t.V0.y + t.V1.y + t.V2.y) / 3.0, (t.V0.z + t.V1.z + t.V2.z) / 3.0); + Vector3d dcp = centroid - p; + Matrix3d o2_lhs = new Matrix3d(ref dcp, ref xn); + double order2 = xA * o2_lhs.InnerProduct(ref hessian); + + return order1 + order2; + } + } + + + + + /// + /// Formulas for point-set winding number approximation + /// + public static class FastPointWinding + { + + public static double ExactEval(ref Vector3d x, ref Vector3d xn, ref double xA, ref Vector3d q) + { + Vector3d dv = (x - q); + double len = dv.Length; + return (xA / MathUtil.FourPI) * xn.Dot(dv / (len * len * len)); + } + + // point-winding-number first-order approximation. + // x is dipole point, p is 'center' of cluster of dipoles, q is evaluation point + public static double Order1Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + { + Vector3d dpq = (p - q); + double len = dpq.Length; + double len3 = len * len * len; + + return (xA / MathUtil.FourPI) * xn.Dot(dpq / (len * len * len)); + } + + + // point-winding-number second-order approximation + // x is dipole point, p is 'center' of cluster of dipoles, q is evaluation point + public static double Order2Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + { + Vector3d dpq = (p - q); + Vector3d dxp = (x - p); + + double len = dpq.Length; + double len3 = len * len * len; + + // first-order approximation - area*normal*\grad(G) + double order1 = (xA / MathUtil.FourPI) * xn.Dot(dpq / len3); + + // second-order hessian \grad^2(G) + Matrix3d xqxq = new Matrix3d(ref dpq, ref dpq); + xqxq *= 3.0 / (MathUtil.FourPI * len3 * len * len); + double diag = 1 / (MathUtil.FourPI * len3); + Matrix3d hessian = new Matrix3d(diag, diag, diag) - xqxq; + + // second-order LHS area * \outer(x-p, normal) + Matrix3d o2_lhs = new Matrix3d(ref dxp, ref xn); + double order2 = xA * o2_lhs.InnerProduct(ref hessian); + + return order1 + order2; + } + } + + +} diff --git a/mesh/MeshCaches.cs b/mesh/MeshCaches.cs new file mode 100644 index 00000000..25375a3d --- /dev/null +++ b/mesh/MeshCaches.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace g3 +{ + /* + * Basic cache of per-triangle information for a DMesh3 + */ + public class MeshTriInfoCache + { + public DVector Centroids; + public DVector Normals; + public DVector Areas; + + public MeshTriInfoCache(DMesh3 mesh) + { + int NT = mesh.TriangleCount; + Centroids = new DVector(); Centroids.resize(NT); + Normals = new DVector(); Normals.resize(NT); + Areas = new DVector(); Areas.resize(NT); + gParallel.ForEach(mesh.TriangleIndices(), (tid) => { + Vector3d c, n; double a; + mesh.GetTriInfo(tid, out n, out a, out c); + Centroids[tid] = c; + Normals[tid] = n; + Areas[tid] = a; + }); + } + + public void GetTriInfo(int tid, ref Vector3d n, ref double a, ref Vector3d c) + { + c = Centroids[tid]; + n = Normals[tid]; + a = Areas[tid]; + } + } +} diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index de5a770c..ceae9146 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -1284,6 +1284,219 @@ protected void collect_triangles(int iBox, HashSet triangles) + + + + /* + * Fast Mesh Winding Number computation + */ + + /// + /// FWN beta parameter - is 2.0 in paper + /// + public double FWNBeta = 2.0; + + /// + /// FWN approximation order. can be 1 or 2. 2 is more accurate, obviously. + /// + public int FWNApproxOrder = 2; + + + /// + /// Fast approximation of winding number using far-field approximations + /// + public virtual double FastWindingNumber(Vector3d p) + { + if (mesh_timestamp != mesh.ShapeTimestamp) + throw new Exception("DMeshAABBTree3.FastWindingNumber: mesh has been modified since tree construction"); + + if (FastWindingCache == null || fast_winding_cache_timestamp != mesh.ShapeTimestamp) { + build_fast_winding_cache(); + fast_winding_cache_timestamp = mesh.ShapeTimestamp; + } + + double sum = branch_fast_winding_num(root_index, p); + return sum; + } + + // evaluate winding number contribution for all triangles below iBox + protected double branch_fast_winding_num(int iBox, Vector3d p) + { + Vector3d a = Vector3d.Zero, b = Vector3d.Zero, c = Vector3d.Zero; + double branch_sum = 0; + + int idx = box_to_index[iBox]; + if (idx < triangles_end) { // triange-list case, array is [N t1 t2 ... tN] + int num_tris = index_list[idx]; + for (int i = 1; i <= num_tris; ++i) { + int ti = index_list[idx + i]; + mesh.GetTriVertices(ti, ref a, ref b, ref c); + branch_sum += MathUtil.TriSolidAngle(a, b, c, ref p) / MathUtil.FourPI; + } + + } else { // internal node, either 1 or 2 child boxes + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + + // if we have winding cache, we can more efficiently compute contribution of all triangles + // below this box. Otherwise, recursively descend tree. + bool contained = box_contains(iChild1, p); + if (contained == false && can_use_fast_winding_cache(iChild1, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild1, ref p); + else + branch_sum += branch_fast_winding_num(iChild1, p); + + } else { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = index_list[idx + 1] - 1; + + bool contained1 = box_contains(iChild1, p); + if (contained1 == false && can_use_fast_winding_cache(iChild1, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild1, ref p); + else + branch_sum += branch_fast_winding_num(iChild1, p); + + bool contained2 = box_contains(iChild2, p); + if (contained2 == false && can_use_fast_winding_cache(iChild2, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild2, ref p); + else + branch_sum += branch_fast_winding_num(iChild2, p); + } + } + + return branch_sum; + } + + + struct FWNInfo + { + public Vector3d Center; + public double R; + public Vector3d Order1Vec; + public Matrix3d Order2Mat; + } + + Dictionary FastWindingCache; + int fast_winding_cache_timestamp = -1; + + protected void build_fast_winding_cache() + { + // set this to a larger number to ignore caches if number of triangles is too small. + // (seems to be no benefit to doing this...is holdover from tree-decomposition FWN code) + int WINDING_CACHE_THRESH = 1; + + //MeshTriInfoCache triCache = null; + MeshTriInfoCache triCache = new MeshTriInfoCache(mesh); + + FastWindingCache = new Dictionary(); + HashSet root_hash; + build_fast_winding_cache(root_index, 0, WINDING_CACHE_THRESH, out root_hash, triCache); + } + protected int build_fast_winding_cache(int iBox, int depth, int tri_count_thresh, out HashSet tri_hash, MeshTriInfoCache triCache) + { + tri_hash = null; + + int idx = box_to_index[iBox]; + if (idx < triangles_end) { // triange-list case, array is [N t1 t2 ... tN] + int num_tris = index_list[idx]; + return num_tris; + + } else { // internal node, either 1 or 2 child boxes + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + int num_child_tris = build_fast_winding_cache(iChild1, depth + 1, tri_count_thresh, out tri_hash, triCache); + + // if count in child is large enough, we already built a cache at lower node + return num_child_tris; + + } else { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = index_list[idx + 1] - 1; + + // let each child build its own cache if it wants. If so, it will return the + // list of its child tris + HashSet child2_hash; + int num_tris_1 = build_fast_winding_cache(iChild1, depth + 1, tri_count_thresh, out tri_hash, triCache); + int num_tris_2 = build_fast_winding_cache(iChild2, depth + 1, tri_count_thresh, out child2_hash, triCache); + bool build_cache = (num_tris_1 + num_tris_2 > tri_count_thresh); + + if (depth == 0) + return num_tris_1 + num_tris_2; // cannot build cache at level 0... + + // collect up the triangles we need. there are various cases depending on what children already did + if (tri_hash != null || child2_hash != null || build_cache) { + if (tri_hash == null && child2_hash != null) { + collect_triangles(iChild1, child2_hash); + tri_hash = child2_hash; + } else { + if (tri_hash == null) { + tri_hash = new HashSet(); + collect_triangles(iChild1, tri_hash); + } + if (child2_hash == null) + collect_triangles(iChild2, tri_hash); + else + tri_hash.UnionWith(child2_hash); + } + } + if (build_cache) + make_box_fast_winding_cache(iBox, tri_hash, triCache); + + return (num_tris_1 + num_tris_2); + } + } + } + + + // check if we can use fwn + protected bool can_use_fast_winding_cache(int iBox, ref Vector3d q) + { + FWNInfo cacheInfo; + if (FastWindingCache.TryGetValue(iBox, out cacheInfo) == false) + return false; + + double dist_qp = cacheInfo.Center.Distance(ref q); + if (dist_qp > FWNBeta * cacheInfo.R) + return true; + + return false; + } + + + // compute FWN cache for all triangles underneath this box + protected void make_box_fast_winding_cache(int iBox, IEnumerable triangles, MeshTriInfoCache triCache) + { + Util.gDevAssert(FastWindingCache.ContainsKey(iBox) == false); + + // construct cache + FWNInfo cacheInfo = new FWNInfo(); + FastTriWinding.ComputeCoeffs(Mesh, triangles, ref cacheInfo.Center, ref cacheInfo.R, ref cacheInfo.Order1Vec, ref cacheInfo.Order2Mat, triCache); + + FastWindingCache[iBox] = cacheInfo; + } + + // evaluate the FWN cache for iBox + protected double evaluate_box_fast_winding_cache(int iBox, ref Vector3d q) + { + FWNInfo cacheInfo = FastWindingCache[iBox]; + + if (FWNApproxOrder == 2) + return FastTriWinding.EvaluateOrder2Approx(ref cacheInfo.Center, ref cacheInfo.Order1Vec, ref cacheInfo.Order2Mat, ref q); + else + return FastTriWinding.EvaluateOrder1Approx(ref cacheInfo.Center, ref cacheInfo.Order1Vec, ref q); + } + + + + + + + + + + /// /// Total sum of volumes of all boxes in the tree. Mainly useful to evaluate tree quality. /// From 647a46d587f90b093791411cf2e1d3f07b54c3cc Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 14 Sep 2018 13:28:52 -0400 Subject: [PATCH 205/225] add IPointSet.Timestamp, implement in other mesh/pointset classes --- mesh/IMesh.cs | 2 ++ mesh/MeshPointSets.cs | 14 ++++++++++++++ mesh/SimpleMesh.cs | 28 +++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/mesh/IMesh.cs b/mesh/IMesh.cs index 08d0ce55..11b913f1 100644 --- a/mesh/IMesh.cs +++ b/mesh/IMesh.cs @@ -22,6 +22,8 @@ public interface IPointSet // iterators allow us to work with gaps in index space System.Collections.Generic.IEnumerable VertexIndices(); + + int Timestamp { get; } } diff --git a/mesh/MeshPointSets.cs b/mesh/MeshPointSets.cs index 80139a55..bc866095 100644 --- a/mesh/MeshPointSets.cs +++ b/mesh/MeshPointSets.cs @@ -35,6 +35,13 @@ public IEnumerable VertexIndices() { return Mesh.EdgeIndices(); } + + /// + /// Timestamp is incremented any time any change is made to the mesh + /// + public int Timestamp { + get { return Mesh.Timestamp; } + } } @@ -75,6 +82,13 @@ public IEnumerable VertexIndices() { return Mesh.BoundaryEdgeIndices(); } + + /// + /// Timestamp is incremented any time any change is made to the mesh + /// + public int Timestamp { + get { return Mesh.Timestamp; } + } } diff --git a/mesh/SimpleMesh.cs b/mesh/SimpleMesh.cs index eb45f241..404a6f4b 100644 --- a/mesh/SimpleMesh.cs +++ b/mesh/SimpleMesh.cs @@ -16,6 +16,8 @@ public class SimpleMesh : IDeformableMesh public DVector Triangles; public DVector FaceGroups; + int timestamp = 0; + public SimpleMesh() { Initialize(); @@ -100,6 +102,17 @@ public MeshComponents Components { + /// + /// Timestamp is incremented any time any change is made to the mesh + /// + public int Timestamp { + get { return timestamp; } + } + + void updateTimeStamp() { + timestamp++; + } + /* * Construction @@ -117,6 +130,7 @@ public int AppendVertex(double x, double y, double z) UVs.Add(0); UVs.Add(0); } Vertices.Add(x); Vertices.Add(y); Vertices.Add(z); + updateTimeStamp(); return i; } public int AppendVertex(NewVertexInfo info) @@ -140,6 +154,7 @@ public int AppendVertex(NewVertexInfo info) } Vertices.Add(info.v[0]); Vertices.Add(info.v[1]); Vertices.Add(info.v[2]); + updateTimeStamp(); return i; } @@ -157,6 +172,7 @@ public void AppendVertices(VectorArray3d v, VectorArray3f n = null, VectorArray3 UVs.Add(uv.array); else if (HasVertexUVs) UVs.Add(new float[] { 0, 0 }, v.Count); + updateTimeStamp(); } @@ -167,6 +183,7 @@ public int AppendTriangle(int i, int j, int k, int g = -1) if (HasTriangleGroups) FaceGroups.Add((g == -1) ? 0 : g); Triangles.Add(i); Triangles.Add(j); Triangles.Add(k); + updateTimeStamp(); return ti; } @@ -180,6 +197,7 @@ public void AppendTriangles(int[] vTriangles, int[] vertexMap, int g = -1) for (int ti = 0; ti < vTriangles.Length / 3; ++ti) FaceGroups.Add((g == -1) ? 0 : g); } + updateTimeStamp(); } public void AppendTriangles(IndexArray3i t, int[] groups = null) @@ -191,6 +209,7 @@ public void AppendTriangles(IndexArray3i t, int[] groups = null) else FaceGroups.Add(0, t.Count); } + updateTimeStamp(); } @@ -198,7 +217,7 @@ public void AppendTriangles(IndexArray3i t, int[] groups = null) * Utility / Convenience */ - // [RMS] this is convenience stuff... + // [RMS] this is convenience stuff... public void Translate(double tx, double ty, double tz) { int c = VertexCount; @@ -207,6 +226,7 @@ public void Translate(double tx, double ty, double tz) this.Vertices[3 * i + 1] += ty; this.Vertices[3 * i + 2] += tz; } + updateTimeStamp(); } public void Scale(double sx, double sy, double sz) { @@ -216,10 +236,12 @@ public void Scale(double sx, double sy, double sz) this.Vertices[3 * i + 1] *= sy; this.Vertices[3 * i + 2] *= sz; } + updateTimeStamp(); } public void Scale(double s) { Scale(s, s, s); + updateTimeStamp(); } @@ -382,23 +404,27 @@ public void SetVertex(int i, Vector3d v) { Vertices[3 * i] = v.x; Vertices[3 * i + 1] = v.y; Vertices[3 * i + 2] = v.z; + updateTimeStamp(); } public void SetVertexNormal(int i, Vector3f n) { Normals[3 * i] = n.x; Normals[3 * i + 1] = n.y; Normals[3 * i + 2] = n.z; + updateTimeStamp(); } public void SetVertexColor(int i, Vector3f c) { Colors[3 * i] = c.x; Colors[3 * i + 1] = c.y; Colors[3 * i + 2] = c.z; + updateTimeStamp(); } public void SetVertexUV(int i, Vector2f uv) { UVs[2 * i] = uv.x; UVs[2 * i + 1] = uv.y; + updateTimeStamp(); } From d07de12f8b46ea229b0087e77a22f3a4eb6c0371 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 14 Sep 2018 13:30:40 -0400 Subject: [PATCH 206/225] aabb util fn, mesh one-ring area fn --- math/AxisAlignedBox3d.cs | 31 ++++++++++++++++++++++++++++--- mesh/MeshMeasurements.cs | 12 ++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/math/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs index ed41045d..021bdd60 100644 --- a/math/AxisAlignedBox3d.cs +++ b/math/AxisAlignedBox3d.cs @@ -160,13 +160,38 @@ public void Expand(double fRadius) { Min.x -= fRadius; Min.y -= fRadius; Min.z -= fRadius; Max.x += fRadius; Max.y += fRadius; Max.z += fRadius; } + + //! return this box expanded by radius + public AxisAlignedBox3d Expanded(double fRadius) { + return new AxisAlignedBox3d( + Min.x - fRadius, Min.y - fRadius, Min.z - fRadius, + Max.x + fRadius, Max.y + fRadius, Max.z + fRadius); + } + //! value is added to min and subtracted from max public void Contract(double fRadius) { - Min.x += fRadius; Min.y += fRadius; Min.z += fRadius; - Max.x -= fRadius; Max.y -= fRadius; Max.z -= fRadius; + double w = 2 * fRadius; + if ( w > Max.x-Min.x ) { Min.x = Max.x = 0.5 * (Min.x + Max.x); } + else { Min.x += fRadius; Max.x -= fRadius; } + if ( w > Max.y-Min.y ) { Min.y = Max.y = 0.5 * (Min.y + Max.y); } + else { Min.y += fRadius; Max.y -= fRadius; } + if ( w > Max.z-Min.z ) { Min.z = Max.z = 0.5 * (Min.z + Max.z); } + else { Min.z += fRadius; Max.z -= fRadius; } } - public void Scale(double sx, double sy, double sz) + //! return this box expanded by radius + public AxisAlignedBox3d Contracted(double fRadius) { + AxisAlignedBox3d result = new AxisAlignedBox3d( + Min.x + fRadius, Min.y + fRadius, Min.z + fRadius, + Max.x - fRadius, Max.y - fRadius, Max.z - fRadius); + if (result.Min.x > result.Max.x) { result.Min.x = result.Max.x = 0.5 * (Min.x + Max.x); } + if (result.Min.y > result.Max.y) { result.Min.y = result.Max.y = 0.5 * (Min.y + Max.y); } + if (result.Min.z > result.Max.z) { result.Min.z = result.Max.z = 0.5 * (Min.z + Max.z); } + return result; + } + + + public void Scale(double sx, double sy, double sz) { Vector3d c = Center; Vector3d e = Extents; e.x *= sx; e.y *= sy; e.z *= sz; diff --git a/mesh/MeshMeasurements.cs b/mesh/MeshMeasurements.cs index a4d8aaad..4a7cf0c5 100644 --- a/mesh/MeshMeasurements.cs +++ b/mesh/MeshMeasurements.cs @@ -183,6 +183,18 @@ public static Vector2d VolumeArea( DMesh3 mesh, IEnumerable triangles, + /// + /// Compute area of one-ring of mesh vertex by summing triangle areas. + /// If bDisjoint = true, we multiple each triangle area by 1/3 + /// + public static double VertexOneRingArea( DMesh3 mesh, int vid, bool bDisjoint = true ) + { + double sum = 0; + double mul = (bDisjoint) ? (1.0/3.0) : 1.0; + foreach (int tid in mesh.VtxTrianglesItr(vid)) + sum += mesh.GetTriArea(tid) * mul; + return sum; + } From 73aa879ae3bc7244a9b306c48e27db38260d7711 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Fri, 14 Sep 2018 13:41:19 -0400 Subject: [PATCH 207/225] implementation of fast point winding number --- math/FastWindingMath.cs | 95 ++++++++++++- spatial/DMeshAABBTree.cs | 7 + spatial/PointAABBTree3.cs | 273 +++++++++++++++++++++++++++++++++++++- 3 files changed, 366 insertions(+), 9 deletions(-) diff --git a/math/FastWindingMath.cs b/math/FastWindingMath.cs index 7f9366fd..1ff678f6 100644 --- a/math/FastWindingMath.cs +++ b/math/FastWindingMath.cs @@ -166,8 +166,97 @@ public static double Order2Approx(ref Triangle3d t, ref Vector3d p, ref Vector3d /// public static class FastPointWinding { + /// + /// precompute constant coefficients of point winding number approximation + /// pointAreas must be provided, and pointSet must have vertex normals! + /// p: 'center' of expansion for points (area-weighted point avg) + /// r: max distance from p to points + /// order1: first-order vector coeff + /// order2: second-order matrix coeff + /// + public static void ComputeCoeffs( + IPointSet pointSet, IEnumerable points, double[] pointAreas, + ref Vector3d p, ref double r, + ref Vector3d order1, ref Matrix3d order2 ) + { + if (pointSet.HasVertexNormals == false) + throw new Exception("FastPointWinding.ComputeCoeffs: point set does not have normals!"); + + p = Vector3d.Zero; + order1 = Vector3d.Zero; + order2 = Matrix3d.Zero; + r = 0; + + // compute area-weighted centroid of points, we use this as the expansion point + double sum_area = 0; + foreach (int vid in points) { + sum_area += pointAreas[vid]; + p += pointAreas[vid] * pointSet.GetVertex(vid); + } + p /= sum_area; + + // compute first and second-order coefficients of FWN taylor expansion, as well as + // 'radius' value r, which is max dist from any tri vertex to p + foreach (int vid in points) { + Vector3d p_i = pointSet.GetVertex(vid); + Vector3d n_i = pointSet.GetVertexNormal(vid); + double a_i = pointAreas[vid]; + + order1 += a_i * n_i; + + Vector3d dcp = p_i - p; + order2 += a_i * new Matrix3d(ref dcp, ref n_i); + + // this is just for return value... + r = Math.Max(r, p_i.Distance(p)); + } + } + + + /// + /// Evaluate first-order FWN approximation at point q, relative to center c + /// + public static double EvaluateOrder1Approx(ref Vector3d center, ref Vector3d order1Coeff, ref Vector3d q) + { + Vector3d dpq = (center - q); + double len = dpq.Length; + + return (1.0 / MathUtil.FourPI) * order1Coeff.Dot(dpq / (len * len * len)); + } + + + + /// + /// Evaluate second-order FWN approximation at point q, relative to center c + /// + public static double EvaluateOrder2Approx(ref Vector3d center, ref Vector3d order1Coeff, ref Matrix3d order2Coeff, ref Vector3d q) + { + Vector3d dpq = (center - q); + double len = dpq.Length; + double len3 = len * len * len; + double fourPi_len3 = 1.0 / (MathUtil.FourPI * len3); + + double order1 = fourPi_len3 * order1Coeff.Dot(ref dpq); + + // second-order hessian \grad^2(G) + double c = -3.0 / (MathUtil.FourPI * len3 * len * len); + + // expanded-out version below avoids extra constructors + //Matrix3d xqxq = new Matrix3d(ref dpq, ref dpq); + //Matrix3d hessian = new Matrix3d(fourPi_len3, fourPi_len3, fourPi_len3) - c * xqxq; + Matrix3d hessian = new Matrix3d( + fourPi_len3 + c * dpq.x * dpq.x, c * dpq.x * dpq.y, c * dpq.x * dpq.z, + c * dpq.y * dpq.x, fourPi_len3 + c * dpq.y * dpq.y, c * dpq.y * dpq.z, + c * dpq.z * dpq.x, c * dpq.z * dpq.y, fourPi_len3 + c * dpq.z * dpq.z); + + double order2 = order2Coeff.InnerProduct(ref hessian); + + return order1 + order2; + } + + - public static double ExactEval(ref Vector3d x, ref Vector3d xn, ref double xA, ref Vector3d q) + public static double ExactEval(ref Vector3d x, ref Vector3d xn, double xA, ref Vector3d q) { Vector3d dv = (x - q); double len = dv.Length; @@ -176,7 +265,7 @@ public static double ExactEval(ref Vector3d x, ref Vector3d xn, ref double xA, r // point-winding-number first-order approximation. // x is dipole point, p is 'center' of cluster of dipoles, q is evaluation point - public static double Order1Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + public static double Order1Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, double xA, ref Vector3d q) { Vector3d dpq = (p - q); double len = dpq.Length; @@ -188,7 +277,7 @@ public static double Order1Approx(ref Vector3d x, ref Vector3d p, ref Vector3d x // point-winding-number second-order approximation // x is dipole point, p is 'center' of cluster of dipoles, q is evaluation point - public static double Order2Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, ref double xA, ref Vector3d q) + public static double Order2Approx(ref Vector3d x, ref Vector3d p, ref Vector3d xn, double xA, ref Vector3d q) { Vector3d dpq = (p - q); Vector3d dxp = (x - p); diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index ceae9146..94da4367 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -1530,6 +1530,13 @@ public double TotalExtentSum() } + /// + /// Root bounding box of tree (note: tree must be generated by calling a query function first!) + /// + public AxisAlignedBox3d Bounds { + get { return get_box(root_index); } + } + // diff --git a/spatial/PointAABBTree3.cs b/spatial/PointAABBTree3.cs index 93b38841..2b8af555 100644 --- a/spatial/PointAABBTree3.cs +++ b/spatial/PointAABBTree3.cs @@ -16,7 +16,7 @@ namespace g3 public class PointAABBTree3 { IPointSet points; - //int points_timestamp; + int points_timestamp; public PointAABBTree3(IPointSet pointsIn, bool autoBuild = true) { @@ -59,7 +59,7 @@ public void Build(BuildStrategy eStrategy = BuildStrategy.TopDownMidpoint) else if (eStrategy == BuildStrategy.Default) build_top_down(false); - //points_timestamp = points.Timestamp; + points_timestamp = points.Timestamp; } @@ -69,8 +69,8 @@ public void Build(BuildStrategy eStrategy = BuildStrategy.TopDownMidpoint) /// public virtual int FindNearestPoint(Vector3d p, double fMaxDist = double.MaxValue) { - //if (points_timestamp != points.Timestamp) - // throw new Exception("PointAABBTree3.FindNearestPoint: mesh has been modified since tree construction"); + if (points_timestamp != points.Timestamp) + throw new Exception("PointAABBTree3.FindNearestPoint: mesh has been modified since tree construction"); double fNearestSqr = (fMaxDist < double.MaxValue) ? fMaxDist * fMaxDist : double.MaxValue; int tNearID = DMesh3.InvalidID; @@ -151,8 +151,8 @@ public class TreeTraversal /// public virtual void DoTraversal(TreeTraversal traversal) { - //if (points_timestamp != points.Timestamp) - // throw new Exception("PointAABBTree3.FindNearestPoint: mesh has been modified since tree construction"); + if (points_timestamp != points.Timestamp) + throw new Exception("PointAABBTree3.FindNearestPoint: mesh has been modified since tree construction"); tree_traversal(root_index, 0, traversal); } @@ -193,6 +193,248 @@ protected virtual void tree_traversal(int iBox, int depth, TreeTraversal travers + + + + + + /* + * Fast Mesh Winding Number computation + */ + + /// + /// FWN beta parameter - is 2.0 in paper + /// + public double FWNBeta = 2.0; + + /// + /// FWN approximation order. can be 1 or 2. 2 is more accurate, obviously. + /// + public int FWNApproxOrder = 2; + + /// + /// Replace this with function that returns proper area estimate + /// + public Func FWNAreaEstimateF = (vid) => { return 1.0; }; + + + /// + /// Fast approximation of winding number using far-field approximations + /// + public virtual double FastWindingNumber(Vector3d p) + { + if (points_timestamp != points.Timestamp) + throw new Exception("PointAABBTree3.FindNearestPoint: mesh has been modified since tree construction"); + + if (FastWindingCache == null || fast_winding_cache_timestamp != points.Timestamp) { + build_fast_winding_cache(); + fast_winding_cache_timestamp = points.Timestamp; + } + + double sum = branch_fast_winding_num(root_index, p); + return sum; + } + + // evaluate winding number contribution for all points below iBox + protected double branch_fast_winding_num(int iBox, Vector3d p) + { + double branch_sum = 0; + + int idx = box_to_index[iBox]; + if (idx < points_end) { // point-list case, array is [N t1 t2 ... tN] + int num_pts = index_list[idx]; + for (int i = 1; i <= num_pts; ++i) { + int pi = index_list[idx + i]; + Vector3d v = Points.GetVertex(pi); + Vector3d n = Points.GetVertexNormal(pi); + double a = FastWindingAreaCache[pi]; + branch_sum += FastPointWinding.ExactEval(ref v, ref n, a, ref p); + } + + } else { // internal node, either 1 or 2 child boxes + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + + // if we have winding cache, we can more efficiently compute contribution of all points + // below this box. Otherwise, recursively descend tree. + bool contained = box_contains(iChild1, p); + if (contained == false && can_use_fast_winding_cache(iChild1, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild1, ref p); + else + branch_sum += branch_fast_winding_num(iChild1, p); + + } else { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = index_list[idx + 1] - 1; + + bool contained1 = box_contains(iChild1, p); + if (contained1 == false && can_use_fast_winding_cache(iChild1, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild1, ref p); + else + branch_sum += branch_fast_winding_num(iChild1, p); + + bool contained2 = box_contains(iChild2, p); + if (contained2 == false && can_use_fast_winding_cache(iChild2, ref p)) + branch_sum += evaluate_box_fast_winding_cache(iChild2, ref p); + else + branch_sum += branch_fast_winding_num(iChild2, p); + } + } + + return branch_sum; + } + + + struct FWNInfo + { + public Vector3d Center; + public double R; + public Vector3d Order1Vec; + public Matrix3d Order2Mat; + } + + Dictionary FastWindingCache; + double[] FastWindingAreaCache; + int fast_winding_cache_timestamp = -1; + + protected void build_fast_winding_cache() + { + // set this to a larger number to ignore caches if number of points is too small. + // (seems to be no benefit to doing this...is holdover from tree-decomposition FWN code) + int WINDING_CACHE_THRESH = 1; + + FastWindingAreaCache = new double[Points.MaxVertexID]; + foreach (int vid in Points.VertexIndices()) + FastWindingAreaCache[vid] = FWNAreaEstimateF(vid); + + FastWindingCache = new Dictionary(); + HashSet root_hash; + build_fast_winding_cache(root_index, 0, WINDING_CACHE_THRESH, out root_hash); + } + protected int build_fast_winding_cache(int iBox, int depth, int pt_count_thresh, out HashSet pts_hash) + { + pts_hash = null; + + int idx = box_to_index[iBox]; + if (idx < points_end) { // point-list case, array is [N t1 t2 ... tN] + int num_pts = index_list[idx]; + return num_pts; + + } else { // internal node, either 1 or 2 child boxes + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + iChild1 = (-iChild1) - 1; + int num_child_pts = build_fast_winding_cache(iChild1, depth + 1, pt_count_thresh, out pts_hash); + + // if count in child is large enough, we already built a cache at lower node + return num_child_pts; + + } else { // 2 children, descend closest first + iChild1 = iChild1 - 1; + int iChild2 = index_list[idx + 1] - 1; + + // let each child build its own cache if it wants. If so, it will return the + // list of its child points + HashSet child2_hash; + int num_pts_1 = build_fast_winding_cache(iChild1, depth + 1, pt_count_thresh, out pts_hash); + int num_pts_2 = build_fast_winding_cache(iChild2, depth + 1, pt_count_thresh, out child2_hash); + bool build_cache = (num_pts_1 + num_pts_2 > pt_count_thresh); + + if (depth == 0) + return num_pts_1 + num_pts_2; // cannot build cache at level 0... + + // collect up the points we need. there are various cases depending on what children already did + if (pts_hash != null || child2_hash != null || build_cache) { + if (pts_hash == null && child2_hash != null) { + collect_points(iChild1, child2_hash); + pts_hash = child2_hash; + } else { + if (pts_hash == null) { + pts_hash = new HashSet(); + collect_points(iChild1, pts_hash); + } + if (child2_hash == null) + collect_points(iChild2, pts_hash); + else + pts_hash.UnionWith(child2_hash); + } + } + if (build_cache) + make_box_fast_winding_cache(iBox, pts_hash); + + return (num_pts_1 + num_pts_2); + } + } + } + + + // check if we can use fwn + protected bool can_use_fast_winding_cache(int iBox, ref Vector3d q) + { + FWNInfo cacheInfo; + if (FastWindingCache.TryGetValue(iBox, out cacheInfo) == false) + return false; + + double dist_qp = cacheInfo.Center.Distance(ref q); + if (dist_qp > FWNBeta * cacheInfo.R) + return true; + + return false; + } + + + // compute FWN cache for all points underneath this box + protected void make_box_fast_winding_cache(int iBox, IEnumerable pointIndices) + { + Util.gDevAssert(FastWindingCache.ContainsKey(iBox) == false); + + // construct cache + FWNInfo cacheInfo = new FWNInfo(); + FastPointWinding.ComputeCoeffs(points, pointIndices, FastWindingAreaCache, + ref cacheInfo.Center, ref cacheInfo.R, ref cacheInfo.Order1Vec, ref cacheInfo.Order2Mat); + + FastWindingCache[iBox] = cacheInfo; + } + + // evaluate the FWN cache for iBox + protected double evaluate_box_fast_winding_cache(int iBox, ref Vector3d q) + { + FWNInfo cacheInfo = FastWindingCache[iBox]; + + if (FWNApproxOrder == 2) + return FastPointWinding.EvaluateOrder2Approx(ref cacheInfo.Center, ref cacheInfo.Order1Vec, ref cacheInfo.Order2Mat, ref q); + else + return FastPointWinding.EvaluateOrder1Approx(ref cacheInfo.Center, ref cacheInfo.Order1Vec, ref q); + } + + + // collect all the triangles below iBox in a hash + protected void collect_points(int iBox, HashSet points) + { + int idx = box_to_index[iBox]; + if (idx < points_end) { // triange-list case, array is [N t1 t2 ... tN] + int num_tris = index_list[idx]; + for (int i = 1; i <= num_tris; ++i) + points.Add(index_list[idx + i]); + } else { + int iChild1 = index_list[idx]; + if (iChild1 < 0) { // 1 child, descend if nearer than cur min-dist + collect_points((-iChild1) - 1, points); + } else { // 2 children, descend closest first + collect_points(iChild1 - 1, points); + collect_points(index_list[idx + 1] - 1, points); + } + } + } + + + + + + + + /// /// Total sum of volumes of all boxes in the tree. Mainly useful to evaluate tree quality. /// @@ -226,6 +468,13 @@ public double TotalExtentSum() } + /// + /// Root bounding box of tree (note: tree must be generated by calling a query function first!) + /// + public AxisAlignedBox3d Bounds { + get { return get_box(root_index); } + } + // @@ -481,6 +730,8 @@ int split_point_set_midpoint(int[] pt_indices, Vector3d[] positions, } + const double box_eps = 50.0 * MathUtil.Epsilon; + AxisAlignedBox3d get_box(int iBox) { @@ -505,6 +756,16 @@ double box_distance_sqr(int iBox, ref Vector3d p) } + protected bool box_contains(int iBox, Vector3d p) + { + // [TODO] this could be way faster... + Vector3d c = (Vector3d)box_centers[iBox]; + Vector3d e = box_extents[iBox]; + AxisAlignedBox3d box = new AxisAlignedBox3d(ref c, e.x + box_eps, e.y + box_eps, e.z + box_eps); + return box.Contains(p); + } + + // 1) make sure we can reach every point through tree (also demo of how to traverse tree...) // 2) make sure that points are contained in parent boxes From 19abdd7d537a282bb50bc9222eaeb9e986e68825 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 17 Sep 2018 11:19:42 -0400 Subject: [PATCH 208/225] circle util functions --- curve/Circle2.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/curve/Circle2.cs b/curve/Circle2.cs index 41b5a60b..47024a90 100644 --- a/curve/Circle2.cs +++ b/curve/Circle2.cs @@ -8,6 +8,12 @@ public class Circle2d : IParametricCurve2d public double Radius; public bool IsReversed; // use ccw orientation instead of cw + public Circle2d(double radius) { + IsReversed = false; + Center = Vector2d.Zero; + Radius = radius; + } + public Circle2d(Vector2d center, double radius) { IsReversed = false; @@ -137,5 +143,14 @@ public double Distance(Vector2d pt) return Math.Abs(d - Radius); } + + + public static double RadiusArea(double r) { + return Math.PI * r * r; + } + public static double RadiusCircumference(double r) { + return MathUtil.TwoPI * r; + } + } } From 39d3e8ed0d649b048d413d80f401f92d3d39a9e1 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 17 Sep 2018 11:27:16 -0400 Subject: [PATCH 209/225] readme update --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 053f4529..953a4bf3 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - [3D Bitmaps, Minecraft Cubes, and Mesh Winding Numbers](http://www.gradientspace.com/tutorials/2017/12/14/3d-bitmaps-and-minecraft-meshes) - Bitmap3, VoxelSurfaceGenerator, DMeshAABBTree3 Mesh Winding Number, - [Implicit Surface Modeling](http://www.gradientspace.com/tutorials/2018/2/20/implicit-surface-modeling) - Implicit primitives, voxel/levelset/functional booleans, offsets, and blending, lattice/lightweighting demo - [DMesh3: A Dynamic Indexed Triangle Mesh](http://www.gradientspace.com/tutorials/dmesh3) - deep dive into the DMesh3 class's internal data structures and operations +- [Surfacing Point Sets with Fast Winding Numbers](http://www.gradientspace.com/tutorials/2018/9/14/point-set-fast-winding) - tutorial on the Fast Mesh/PointSet Winding Number, and how to use the g3Sharp implementation # Main Classes @@ -260,12 +261,14 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: ## Spatial Data Structures -- **DMeshAABBTree**: triangle mesh axis-aligned bounding box tree +- **DMeshAABBTree3**: triangle mesh axis-aligned bounding box tree - bottom-up construction using mesh topology to accelerate leaf node layer - generic traversal interface DoTraversal(TreeTraversal) - - Queries for NearestTriangle(point), FindNearestHitTriangle(ray) and FindAllHitTriangles(ray) - - TestIntersection(triangle), TestIntersection(other_tree), FindIntersections(other_tree) - - IsInside(point) + - FindNearestTriangle(point), FindNearestHitTriangle(ray) and FindAllHitTriangles(ray), FindNearestVertex(point) + - FindNearestTriangles(other_tree) + - TestIntersection(triangle), TestIntersection(other_tree), FindAllIntersections(other_tree) + - IsInside(point), WindingNumber(point), FastWindingNumber(point) +- **PointAABBTree3**: point variant of DMeshAABBTree3, with PointSet Fast Winding Number - **Polygon2dBoxTree**: 2D segment bbox-tree, distance query - **PointHashGrid2d**, **SegmentHashGrid2d**: hash tables for 2D geometry elements - **PointHashGrid3d**: hash tables for 3D geometry elements From 05c27769cc2634f2b3428b866517c108c39b64bd Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 17 Sep 2018 12:29:51 -0400 Subject: [PATCH 210/225] util fns --- mesh/MeshEditor.cs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 8251b11b..19cc95bb 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace g3 @@ -834,6 +835,12 @@ public void AppendLine(Segment3d seg, float size) f.AlignAxis(2, (Vector3f)seg.Direction); AppendBox(f, new Vector3f(size, size, seg.Extent)); } + public void AppendLine(Segment3d seg, float size, Colorf color) + { + Frame3f f = new Frame3f(seg.Center); + f.AlignAxis(2, (Vector3f)seg.Direction); + AppendBox(f, new Vector3f(size, size, seg.Extent), color); + } public static void AppendBox(DMesh3 mesh, Vector3d pos, float size) { MeshEditor editor = new MeshEditor(mesh); @@ -871,6 +878,22 @@ public static void AppendLine(DMesh3 mesh, Segment3d seg, float size) + public void AppendPathSolid(IEnumerable vertices, double radius, Colorf color) + { + TubeGenerator tubegen = new TubeGenerator() { + Vertices = new List(vertices), + Polygon = Polygon2d.MakeCircle(radius, 6), + NoSharedVertices = false + }; + DMesh3 mesh = tubegen.Generate().MakeDMesh(); + if (Mesh.HasVertexColors) + mesh.EnableVertexColors(color); + AppendMesh(mesh, Mesh.AllocateTriangleGroup()); + } + + + + /// /// Remove all bowtie vertices in mesh. Makes one pass unless /// bRepeatUntilClean = true, in which case repeats until no more bowties found From aa4f55d5c622868e48f97591247ececa02b462d6 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 25 Sep 2018 18:03:41 -0400 Subject: [PATCH 211/225] some minor optimizations to ray/aabb intersection, does make a difference though... --- intersection/IntrRay3AxisAlignedBox3.cs | 76 +++++++++++++++++-------- spatial/DMeshAABBTree.cs | 8 +-- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/intersection/IntrRay3AxisAlignedBox3.cs b/intersection/IntrRay3AxisAlignedBox3.cs index d0de1c05..1c2ddb5d 100644 --- a/intersection/IntrRay3AxisAlignedBox3.cs +++ b/intersection/IntrRay3AxisAlignedBox3.cs @@ -87,53 +87,53 @@ public static bool Intersects(ref Ray3d ray, ref AxisAlignedBox3d box, double ex Vector3d AWdU = Vector3d.Zero; Vector3d DdU = Vector3d.Zero; Vector3d ADdU = Vector3d.Zero; - Vector3d AWxDdU = Vector3d.Zero; double RHS; Vector3d diff = ray.Origin - box.Center; Vector3d extent = box.Extents + expandExtents; - WdU[0] = ray.Direction.x; // ray.Direction.Dot(Vector3d.AxisX); - AWdU[0] = Math.Abs(WdU[0]); - DdU[0] = diff.x; // diff.Dot(Vector3d.AxisX); - ADdU[0] = Math.Abs(DdU[0]); - if (ADdU[0] > extent.x && DdU[0] * WdU[0] >= (double)0) { + WdU.x = ray.Direction.x; // ray.Direction.Dot(Vector3d.AxisX); + AWdU.x = Math.Abs(WdU.x); + DdU.x = diff.x; // diff.Dot(Vector3d.AxisX); + ADdU.x = Math.Abs(DdU.x); + if (ADdU.x > extent.x && DdU.x * WdU.x >= (double)0) { return false; } - WdU[1] = ray.Direction.y; // ray.Direction.Dot(Vector3d.AxisY); - AWdU[1] = Math.Abs(WdU[1]); - DdU[1] = diff.y; // diff.Dot(Vector3d.AxisY); - ADdU[1] = Math.Abs(DdU[1]); - if (ADdU[1] > extent.y && DdU[1] * WdU[1] >= (double)0) { + WdU.y = ray.Direction.y; // ray.Direction.Dot(Vector3d.AxisY); + AWdU.y = Math.Abs(WdU.y); + DdU.y = diff.y; // diff.Dot(Vector3d.AxisY); + ADdU.y = Math.Abs(DdU.y); + if (ADdU.y > extent.y && DdU.y * WdU.y >= (double)0) { return false; } - WdU[2] = ray.Direction.z; // ray.Direction.Dot(Vector3d.AxisZ); - AWdU[2] = Math.Abs(WdU[2]); - DdU[2] = diff.z; // diff.Dot(Vector3d.AxisZ); - ADdU[2] = Math.Abs(DdU[2]); - if (ADdU[2] > extent.z && DdU[2] * WdU[2] >= (double)0) { + WdU.z = ray.Direction.z; // ray.Direction.Dot(Vector3d.AxisZ); + AWdU.z = Math.Abs(WdU.z); + DdU.z = diff.z; // diff.Dot(Vector3d.AxisZ); + ADdU.z = Math.Abs(DdU.z); + if (ADdU.z > extent.z && DdU.z * WdU.z >= (double)0) { return false; } Vector3d WxD = ray.Direction.Cross(diff); + Vector3d AWxDdU = Vector3d.Zero; - AWxDdU[0] = Math.Abs(WxD.x); // Math.Abs(WxD.Dot(Vector3d.AxisX)); - RHS = extent.y * AWdU[2] + extent.z * AWdU[1]; - if (AWxDdU[0] > RHS) { + AWxDdU.x = Math.Abs(WxD.x); // Math.Abs(WxD.Dot(Vector3d.AxisX)); + RHS = extent.y * AWdU.z + extent.z * AWdU.y; + if (AWxDdU.x > RHS) { return false; } - AWxDdU[1] = Math.Abs(WxD.y); // Math.Abs(WxD.Dot(Vector3d.AxisY)); - RHS = extent.x * AWdU[2] + extent.z * AWdU[0]; - if (AWxDdU[1] > RHS) { + AWxDdU.y = Math.Abs(WxD.y); // Math.Abs(WxD.Dot(Vector3d.AxisY)); + RHS = extent.x * AWdU.z + extent.z * AWdU.x; + if (AWxDdU.y > RHS) { return false; } - AWxDdU[2] = Math.Abs(WxD.z); // Math.Abs(WxD.Dot(Vector3d.AxisZ)); - RHS = extent.x * AWdU[1] + extent.y * AWdU[0]; - if (AWxDdU[2] > RHS) { + AWxDdU.z = Math.Abs(WxD.z); // Math.Abs(WxD.Dot(Vector3d.AxisZ)); + RHS = extent.x * AWdU.y + extent.y * AWdU.x; + if (AWxDdU.z > RHS) { return false; } @@ -141,6 +141,32 @@ public static bool Intersects(ref Ray3d ray, ref AxisAlignedBox3d box, double ex } + /// + /// Find intersection of ray with AABB, without having to construct any new classes. + /// Returns ray T-value of first intersection (or double.MaxVlaue on miss) + /// + public static bool FindRayIntersectT(ref Ray3d ray, ref AxisAlignedBox3d box, out double RayParam) + { + double RayParam0 = 0.0; + double RayParam1 = double.MaxValue; + int Quantity = 0; + Vector3d Point0 = Vector3d.Zero; + Vector3d Point1 = Vector3d.Zero; + IntersectionType Type = IntersectionType.Empty; + IntrLine3AxisAlignedBox3.DoClipping(ref RayParam0, ref RayParam1, ref ray.Origin, ref ray.Direction, ref box, + true, ref Quantity, ref Point0, ref Point1, ref Type); + + if (Type != IntersectionType.Empty) { + RayParam = RayParam0; + return true; + } else { + RayParam = double.MaxValue; + return false; + } + } + + + } } diff --git a/spatial/DMeshAABBTree.cs b/spatial/DMeshAABBTree.cs index 94da4367..b28dcdef 100644 --- a/spatial/DMeshAABBTree.cs +++ b/spatial/DMeshAABBTree.cs @@ -25,6 +25,7 @@ namespace g3 /// - FindNearestTriangles(otherAABBTree, maxdist) /// - IsInside(point) /// - WindingNumber(point) + /// - FastWindingNumber(point) /// - DoTraversal(generic_traversal_object) /// /// @@ -2238,11 +2239,10 @@ double box_ray_intersect_t(int iBox, Ray3d ray) Vector3f e = box_extents[iBox]; AxisAlignedBox3d box = new AxisAlignedBox3d(ref c, e.x + box_eps, e.y + box_eps, e.z + box_eps); - IntrRay3AxisAlignedBox3 intr = new IntrRay3AxisAlignedBox3(ray, box); - if (intr.Find()) { - return intr.RayParam0; + double ray_t = double.MaxValue; + if (IntrRay3AxisAlignedBox3.FindRayIntersectT(ref ray, ref box, out ray_t)) { + return ray_t; } else { - Debug.Assert(intr.Result != IntersectionResult.InvalidQuery); return double.MaxValue; } } From b375ce5effb215f8f67534f7cb6f0ddf1c2a9d8d Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 3 Oct 2018 14:18:13 -0400 Subject: [PATCH 212/225] utli fn --- curve/Circle2.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/curve/Circle2.cs b/curve/Circle2.cs index 47024a90..3cab0789 100644 --- a/curve/Circle2.cs +++ b/curve/Circle2.cs @@ -152,5 +152,12 @@ public static double RadiusCircumference(double r) { return MathUtil.TwoPI * r; } + /// + /// Radius of n-sided regular polygon that contains circle of radius r + /// + public static double BoundingPolygonRadius(double r, int n) { + double theta = (MathUtil.TwoPI / (double)n) / 2.0; + return r / Math.Cos(theta); + } } } From 21665bb586fd73913abce3998fcdd35c71b84671 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 5 Nov 2018 16:05:04 -0500 Subject: [PATCH 213/225] utility function to try to improve vtx loop stitching --- mesh/MeshEditor.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 19cc95bb..62aa0bde 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -180,6 +180,48 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) + + + /// + /// Trivial back-and-forth stitch between two vertex loops with same length. + /// If nearest vertices of input loops would not be matched, cycles loops so + /// that this is the case. + /// Loops must have appropriate orientation. + /// + public virtual int[] StitchVertexLoops_NearestV(int[] loop0, int[] loop1, int group_id = -1) + { + int N = loop0.Length; + Index2i iBestPair = Index2i.Zero; + double best_dist = double.MaxValue; + for (int i = 0; i < N; ++i) { + Vector3d v0 = Mesh.GetVertex(loop0[i]); + for (int j = 0; j < N; ++j) { + double dist_sqr = v0.DistanceSquared(Mesh.GetVertex(loop1[j])); + if (dist_sqr < best_dist) { + best_dist = dist_sqr; + iBestPair = new Index2i(i, j); + } + } + } + if (iBestPair.a != iBestPair.b) { + int[] newLoop0 = new int[N]; + int[] newLoop1 = new int[N]; + for (int i = 0; i < N; ++i) { + newLoop0[i] = loop0[(iBestPair.a + i) % N]; + newLoop1[i] = loop1[(iBestPair.b + i) % N]; + } + return StitchLoop(newLoop0, newLoop1, group_id); + } else { + return StitchLoop(loop0, loop1, group_id); + } + + } + + + + + + /// /// Stitch two sets of boundary edges that are provided as unordered pairs of edges, by /// adding triangulated quads between each edge pair. From 25649aa04a34b70249c5031949b026900de99aa0 Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Wed, 7 Nov 2018 09:45:42 -0500 Subject: [PATCH 214/225] util fn --- queries/MeshQueries.cs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/queries/MeshQueries.cs b/queries/MeshQueries.cs index 032ea13c..1bfc8dae 100644 --- a/queries/MeshQueries.cs +++ b/queries/MeshQueries.cs @@ -54,6 +54,30 @@ public static double NearestPointDistance(DMesh3 mesh, ISpatial spatial, Vector3 } + + /// + /// find distance between two triangles, with optional + /// transform on second triangle + /// + public static DistTriangle3Triangle3 TriangleTriangleDistance(DMesh3 mesh1, int ti, DMesh3 mesh2, int tj, Func TransformF = null) + { + if (mesh1.IsTriangle(ti) == false || mesh2.IsTriangle(tj) == false) + return null; + Triangle3d tri1 = new Triangle3d(), tri2 = new Triangle3d(); + mesh1.GetTriVertices(ti, ref tri1.V0, ref tri1.V1, ref tri1.V2); + mesh2.GetTriVertices(tj, ref tri2.V0, ref tri2.V1, ref tri2.V2); + if (TransformF != null) { + tri2.V0 = TransformF(tri2.V0); + tri2.V1 = TransformF(tri2.V1); + tri2.V2 = TransformF(tri2.V2); + } + DistTriangle3Triangle3 dist = new DistTriangle3Triangle3(tri1, tri2); + dist.Compute(); + return dist; + } + + + /// /// convenience function to construct a IntrRay3Triangle3 object for a mesh triangle /// From 838b04024730038fd3e22e349f431884e1ba47bd Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Tue, 13 Nov 2018 11:10:06 -0500 Subject: [PATCH 215/225] improve failure for stitchloops, in case where one triangle works and the other doesn't --- mesh/MeshEditor.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mesh/MeshEditor.cs b/mesh/MeshEditor.cs index 62aa0bde..939c7cc3 100644 --- a/mesh/MeshEditor.cs +++ b/mesh/MeshEditor.cs @@ -156,12 +156,11 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) int tid1 = Mesh.AppendTriangle(t1, group_id); int tid2 = Mesh.AppendTriangle(t2, group_id); + new_tris[2 * i] = tid1; + new_tris[2 * i + 1] = tid2; if (tid1 < 0 || tid2 < 0) goto operation_failed; - - new_tris[2 * i] = tid1; - new_tris[2 * i + 1] = tid2; } return new_tris; @@ -170,7 +169,7 @@ public virtual int[] StitchLoop(int[] vloop1, int[] vloop2, int group_id = -1) operation_failed: // remove what we added so far if (i > 0) { - if (remove_triangles(new_tris, 2*(i-1)) == false) + if (remove_triangles(new_tris, 2*i+1) == false) throw new Exception("MeshEditor.StitchLoop: failed to add all triangles, and also failed to back out changes."); } return null; @@ -1029,6 +1028,8 @@ public static int RemoveSmallComponents(DMesh3 mesh, double min_volume, double m bool remove_triangles(int[] tri_list, int count) { for (int i = 0; i < count; ++i) { + if (Mesh.IsTriangle(tri_list[i]) == false) + continue; MeshResult result = Mesh.RemoveTriangle(tri_list[i], false, false); if (result != MeshResult.Ok) return false; From fea873167a1545e433e272470eab5fd6dd085570 Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Mon, 19 Nov 2018 14:08:28 -0800 Subject: [PATCH 216/225] Add Polygon2d.Contains(Segment2d) method --- .gitignore | 3 +++ curve/Polygon2d.cs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 01d29097..90e3cfe7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Folders +.vs/ + # Compiled Object files *.slo *.lo diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index dee9cdbb..066c3221 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -333,8 +333,20 @@ public bool Contains(Polygon2d o) { return true; } + public bool Contains(Segment2d o) + { + if (Contains(o.P0) == false || Contains(o.P1) == false) + return false; + + foreach (Segment2d seg in SegmentItr()) + { + if (seg.Intersects(o)) + return false; + } + return true; + } - public bool Intersects(Polygon2d o) { + public bool Intersects(Polygon2d o) { if ( ! this.GetBounds().Intersects( o.GetBounds() ) ) return false; From a6dd51d4a46e4ae54a1bb757f4b42972db075244 Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Mon, 19 Nov 2018 17:53:23 -0800 Subject: [PATCH 217/225] Add additional methods for checking Segment2d --- curve/GeneralPolygon2d.cs | 13 +++++++++++++ curve/Polygon2d.cs | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/curve/GeneralPolygon2d.cs b/curve/GeneralPolygon2d.cs index e53c40a1..d6199633 100644 --- a/curve/GeneralPolygon2d.cs +++ b/curve/GeneralPolygon2d.cs @@ -189,6 +189,19 @@ public bool Contains(Polygon2d poly) { } + public bool Contains(Segment2d seg) + { + if (outer.Contains(seg) == false) + return false; + foreach (var h in holes) + { + if (h.Intersects(seg)) + return false; + } + return true; + } + + public bool Intersects(Polygon2d poly) { if (outer.Intersects(poly)) diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index 066c3221..ef053563 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -335,6 +335,7 @@ public bool Contains(Polygon2d o) { public bool Contains(Segment2d o) { + // [TODO] Add bbox check if (Contains(o.P0) == false || Contains(o.P1) == false) return false; @@ -359,8 +360,24 @@ public bool Intersects(Polygon2d o) { return false; } + public bool Intersects(Segment2d o) + { + // [TODO] Add bbox check + if (Contains(o.P0) == true || Contains(o.P1) == true) + return true; + + // [TODO] Add bbox check + foreach (Segment2d seg in SegmentItr()) + { + if (seg.Intersects(o)) + return true; + } + return false; + } + + - public List FindIntersections(Polygon2d o) { + public List FindIntersections(Polygon2d o) { List v = new List(); if ( ! this.GetBounds().Intersects( o.GetBounds() ) ) return v; From d358f3e046c13bc41be514271a3d9d4e5c4376be Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Mon, 26 Nov 2018 17:34:16 -0800 Subject: [PATCH 218/225] Add comment summary strings. --- curve/GeneralPolygon2d.cs | 5 ++++- curve/Polygon2d.cs | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/curve/GeneralPolygon2d.cs b/curve/GeneralPolygon2d.cs index d6199633..cc15dffa 100644 --- a/curve/GeneralPolygon2d.cs +++ b/curve/GeneralPolygon2d.cs @@ -188,7 +188,10 @@ public bool Contains(Polygon2d poly) { return true; } - + /// + /// Checks that all points on a segment are within the area defined by the GeneralPolygon2d; + /// holes are included in the calculation. + /// public bool Contains(Segment2d seg) { if (outer.Contains(seg) == false) diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs index ef053563..b74287ca 100644 --- a/curve/Polygon2d.cs +++ b/curve/Polygon2d.cs @@ -333,6 +333,9 @@ public bool Contains(Polygon2d o) { return true; } + /// + /// Checks that all points on a segment are within the area defined by the Polygon2d. + /// public bool Contains(Segment2d o) { // [TODO] Add bbox check @@ -360,6 +363,9 @@ public bool Intersects(Polygon2d o) { return false; } + /// + /// Checks if any point on a segment is within the area defined by the Polygon2d. + /// public bool Intersects(Segment2d o) { // [TODO] Add bbox check From 4b65c4be48345396ed32a922c87ef45c7275deb4 Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Sun, 13 Jan 2019 23:50:37 -0700 Subject: [PATCH 219/225] Add Snapping methods for snapping high or low --- core/Snapping.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/Snapping.cs b/core/Snapping.cs index 9314f79e..23ca6b17 100644 --- a/core/Snapping.cs +++ b/core/Snapping.cs @@ -29,5 +29,31 @@ public static double SnapToNearbyIncrement(double fValue, double fIncrement, dou return fValue; } + private static double SnapToIncrementSigned(double fValue, double fIncrement, bool low) + { + if (!MathUtil.IsFinite(fValue)) + return 0; + double sign = Math.Sign(fValue); + fValue = Math.Abs(fValue); + int nInc = (int)(fValue / fIncrement); + + if (low && sign < 0) + ++nInc; + else if (!low && sign > 0) + ++nInc; + + return sign * (double)nInc * fIncrement; + + } + + public static double SnapToIncrementLow(double fValue, double fIncrement) + { + return SnapToIncrementSigned(fValue, fIncrement, true); + } + + public static double SnapToIncrementHigh(double fValue, double fIncrement) + { + return SnapToIncrementSigned(fValue, fIncrement, false); + } } } From 190c59f2cc539dd85400ced48cfab838abda2fbd Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Sun, 13 Jan 2019 23:52:35 -0700 Subject: [PATCH 220/225] Add .vs pattern to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 90e3cfe7..51bef3c3 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ geometry3Sharp/obj geometry3Sharp.csproj.user bin obj +.vs *.meta From 539daf1dccd512593d26ec22548f8b8ba727e3c2 Mon Sep 17 00:00:00 2001 From: Greg Meess Date: Mon, 14 Jan 2019 00:11:49 -0700 Subject: [PATCH 221/225] Add optional offset parameter to Snapping methodsw --- core/Snapping.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/Snapping.cs b/core/Snapping.cs index 23ca6b17..e7c04eb5 100644 --- a/core/Snapping.cs +++ b/core/Snapping.cs @@ -5,17 +5,18 @@ namespace g3 public class Snapping { - public static double SnapToIncrement(double fValue, double fIncrement) + public static double SnapToIncrement(double fValue, double fIncrement, double offset = 0) { if (!MathUtil.IsFinite(fValue)) return 0; + fValue -= offset; double sign = Math.Sign(fValue); fValue = Math.Abs(fValue); int nInc = (int)(fValue / fIncrement); double fRem = fValue % fIncrement; if (fRem > fIncrement / 2) ++nInc; - return sign * (double)nInc * fIncrement; + return sign * (double)nInc * fIncrement + offset; } @@ -46,14 +47,14 @@ private static double SnapToIncrementSigned(double fValue, double fIncrement, bo } - public static double SnapToIncrementLow(double fValue, double fIncrement) + public static double SnapToIncrementLow(double fValue, double fIncrement, double offset=0) { - return SnapToIncrementSigned(fValue, fIncrement, true); + return SnapToIncrementSigned(fValue - offset, fIncrement, true) + offset; } - public static double SnapToIncrementHigh(double fValue, double fIncrement) + public static double SnapToIncrementHigh(double fValue, double fIncrement, double offset = 0) { - return SnapToIncrementSigned(fValue, fIncrement, false); + return SnapToIncrementSigned(fValue - offset, fIncrement, false) + offset; } } } From 359238f3aa04285c19a99b336ae98be9459637de Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 7 Mar 2019 00:34:04 -0500 Subject: [PATCH 222/225] copied over files from gsGeometry and released under Boost License --- geometry3Sharp.csproj | 7 + implicit/CachingGridImplicit3d.cs | 208 +++++ implicit/CachingMeshSDF.cs | 587 +++++++++++++ implicit/FalloffFunctions.cs | 81 ++ mesh/RemesherPro.cs | 731 ++++++++++++++++ mesh_generators/MarchingCubesPro.cs | 1058 ++++++++++++++++++++++++ mesh_ops/AutoHoleFill.cs | 364 ++++++++ mesh_ops/MergeCoincidentEdges.cs | 159 ++++ mesh_ops/MeshAssembly.cs | 131 +++ mesh_ops/MeshAutoRepair.cs | 388 +++++++++ mesh_ops/MeshBoolean.cs | 152 ++++ mesh_ops/MeshInsertProjectedPolygon.cs | 260 ++++++ mesh_ops/MeshMeshCut.cs | 664 +++++++++++++++ mesh_ops/MeshRepairOrientation.cs | 177 ++++ mesh_ops/MeshSpatialSort.cs | 292 +++++++ mesh_ops/MeshStitchLoops.cs | 190 +++++ mesh_ops/MeshTopology.cs | 229 +++++ mesh_ops/MeshTrimLoop.cs | 167 ++++ mesh_ops/MinimalHoleFill.cs | 489 +++++++++++ mesh_ops/PlanarSpansFiller.cs | 210 +++++ mesh_ops/RemoveDuplicateTriangles.cs | 136 +++ mesh_ops/RemoveOccludedTriangles.cs | 195 +++++ mesh_ops/SmoothedHoleFill.cs | 263 ++++++ spatial/EditMeshSpatial.cs | 105 +++ spatial/MeshScalarSamplingGrid.cs | 344 ++++++++ spatial/MeshWindingNumberGrid.cs | 349 ++++++++ spatial/PointSetHashtable.cs | 114 +++ 27 files changed, 8050 insertions(+) create mode 100644 implicit/CachingGridImplicit3d.cs create mode 100644 implicit/CachingMeshSDF.cs create mode 100644 implicit/FalloffFunctions.cs create mode 100644 mesh/RemesherPro.cs create mode 100644 mesh_generators/MarchingCubesPro.cs create mode 100644 mesh_ops/AutoHoleFill.cs create mode 100644 mesh_ops/MergeCoincidentEdges.cs create mode 100644 mesh_ops/MeshAssembly.cs create mode 100644 mesh_ops/MeshAutoRepair.cs create mode 100644 mesh_ops/MeshBoolean.cs create mode 100644 mesh_ops/MeshInsertProjectedPolygon.cs create mode 100644 mesh_ops/MeshMeshCut.cs create mode 100644 mesh_ops/MeshRepairOrientation.cs create mode 100644 mesh_ops/MeshSpatialSort.cs create mode 100644 mesh_ops/MeshStitchLoops.cs create mode 100644 mesh_ops/MeshTopology.cs create mode 100644 mesh_ops/MeshTrimLoop.cs create mode 100644 mesh_ops/MinimalHoleFill.cs create mode 100644 mesh_ops/PlanarSpansFiller.cs create mode 100644 mesh_ops/RemoveDuplicateTriangles.cs create mode 100644 mesh_ops/RemoveOccludedTriangles.cs create mode 100644 mesh_ops/SmoothedHoleFill.cs create mode 100644 spatial/EditMeshSpatial.cs create mode 100644 spatial/MeshScalarSamplingGrid.cs create mode 100644 spatial/MeshWindingNumberGrid.cs create mode 100644 spatial/PointSetHashtable.cs diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj index ec3ece6d..a43ff4a0 100644 --- a/geometry3Sharp.csproj +++ b/geometry3Sharp.csproj @@ -106,6 +106,9 @@ + + + @@ -304,6 +307,9 @@ + + + @@ -311,6 +317,7 @@ + diff --git a/implicit/CachingGridImplicit3d.cs b/implicit/CachingGridImplicit3d.cs new file mode 100644 index 00000000..e04ffdb5 --- /dev/null +++ b/implicit/CachingGridImplicit3d.cs @@ -0,0 +1,208 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; + + +namespace g3 +{ + + /// + /// [RMS] variant of DenseGridTrilinearImplicit that does lazy evaluation + /// of Grid values. + /// + /// Tri-linear interpolant for a 3D dense grid. Supports grid translation + /// via GridOrigin, but does not support scaling or rotation. If you need those, + /// you can wrap this in something that does the xform. + /// + public class CachingDenseGridTrilinearImplicit : BoundedImplicitFunction3d + { + public DenseGrid3f Grid; + public double CellSize; + public Vector3d GridOrigin; + + public ImplicitFunction3d AnalyticF; + + // value to return if query point is outside grid (in an SDF + // outside is usually positive). Need to do math with this value, + // so don't use double.MaxValue or square will overflow + public double Outside = Math.Sqrt(Math.Sqrt(double.MaxValue)); + + public float Invalid = float.MaxValue; + + public CachingDenseGridTrilinearImplicit(Vector3d gridOrigin, double cellSize, Vector3i gridDimensions) + { + Grid = new DenseGrid3f(gridDimensions.x, gridDimensions.y, gridDimensions.z, Invalid); + GridOrigin = gridOrigin; + CellSize = cellSize; + } + + public AxisAlignedBox3d Bounds() + { + return new AxisAlignedBox3d( + GridOrigin.x, GridOrigin.y, GridOrigin.z, + GridOrigin.x + CellSize * Grid.ni, + GridOrigin.y + CellSize * Grid.nj, + GridOrigin.z + CellSize * Grid.nk); + } + + + public double Value(ref Vector3d pt) + { + Vector3d gridPt = new Vector3d( + ((pt.x - GridOrigin.x) / CellSize), + ((pt.y - GridOrigin.y) / CellSize), + ((pt.z - GridOrigin.z) / CellSize)); + + // compute integer coordinates + int x0 = (int)gridPt.x; + int y0 = (int)gridPt.y, y1 = y0 + 1; + int z0 = (int)gridPt.z, z1 = z0 + 1; + + // clamp to grid + if (x0 < 0 || (x0+1) >= Grid.ni || + y0 < 0 || y1 >= Grid.nj || + z0 < 0 || z1 >= Grid.nk) + return Outside; + + // convert double coords to [0,1] range + double fAx = gridPt.x - (double)x0; + double fAy = gridPt.y - (double)y0; + double fAz = gridPt.z - (double)z0; + double OneMinusfAx = 1.0 - fAx; + + // compute trilinear interpolant. The code below tries to do this with the fewest + // number of variables, in hopes that optimizer will be clever about re-using registers, etc. + // Commented code at bottom is fully-expanded version. + // [TODO] it is possible to implement lerps here as a+(b-a)*t, saving a multiply and a variable. + // This is numerically worse, but since the grid values are floats and + // we are computing in doubles, does it matter? + double xa, xb; + + get_value_pair(x0, y0, z0, out xa, out xb); + double yz = (1 - fAy) * (1 - fAz); + double sum = (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y0, z1, out xa, out xb); + yz = (1 - fAy) * (fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y1, z0, out xa, out xb); + yz = (fAy) * (1 - fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y1, z1, out xa, out xb); + yz = (fAy) * (fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + return sum; + + // fV### is grid cell corner index + //return + // fV000 * (1 - fAx) * (1 - fAy) * (1 - fAz) + + // fV001 * (1 - fAx) * (1 - fAy) * (fAz) + + // fV010 * (1 - fAx) * (fAy) * (1 - fAz) + + // fV011 * (1 - fAx) * (fAy) * (fAz) + + // fV100 * (fAx) * (1 - fAy) * (1 - fAz) + + // fV101 * (fAx) * (1 - fAy) * (fAz) + + // fV110 * (fAx) * (fAy) * (1 - fAz) + + // fV111 * (fAx) * (fAy) * (fAz); + } + + + + void get_value_pair(int i, int j, int k, out double a, out double b) + { + float fa, fb; + Grid.get_x_pair(i, j, k, out fa, out fb); + + if (fa == Invalid) { + Vector3d p = grid_position(i, j, k); + a = AnalyticF.Value(ref p); + Grid[i, j, k] = (float)a; + } else + a = fa; + + if (fb == Invalid) { + Vector3d p = grid_position(i+1, j, k); + b = AnalyticF.Value(ref p); + Grid[i+1, j, k] = (float)b; + } else + b = fb; + } + + + Vector3d grid_position(int i, int j, int k) { + return new Vector3d( GridOrigin.x + CellSize * i, GridOrigin.y + CellSize * j, GridOrigin.z + CellSize*k ); + } + + + public Vector3d Gradient(ref Vector3d pt) + { + Vector3d gridPt = new Vector3d( + ((pt.x - GridOrigin.x) / CellSize), + ((pt.y - GridOrigin.y) / CellSize), + ((pt.z - GridOrigin.z) / CellSize)); + + // clamp to grid + if (gridPt.x < 0 || gridPt.x >= Grid.ni - 1 || + gridPt.y < 0 || gridPt.y >= Grid.nj - 1 || + gridPt.z < 0 || gridPt.z >= Grid.nk - 1) + return Vector3d.Zero; + + // compute integer coordinates + int x0 = (int)gridPt.x; + int y0 = (int)gridPt.y, y1 = y0 + 1; + int z0 = (int)gridPt.z, z1 = z0 + 1; + + // convert double coords to [0,1] range + double fAx = gridPt.x - (double)x0; + double fAy = gridPt.y - (double)y0; + double fAz = gridPt.z - (double)z0; + + double fV000, fV100; + get_value_pair(x0, y0, z0, out fV000, out fV100); + double fV010, fV110; + get_value_pair(x0, y1, z0, out fV010, out fV110); + double fV001, fV101; + get_value_pair(x0, y0, z1, out fV001, out fV101); + double fV011, fV111; + get_value_pair(x0, y1, z1, out fV011, out fV111); + + // [TODO] can re-order this to vastly reduce number of ops! + double gradX = + -fV000 * (1 - fAy) * (1 - fAz) + + -fV001 * (1 - fAy) * (fAz) + + -fV010 * (fAy) * (1 - fAz) + + -fV011 * (fAy) * (fAz) + + fV100 * (1 - fAy) * (1 - fAz) + + fV101 * (1 - fAy) * (fAz) + + fV110 * (fAy) * (1 - fAz) + + fV111 * (fAy) * (fAz); + + double gradY = + -fV000 * (1 - fAx) * (1 - fAz) + + -fV001 * (1 - fAx) * (fAz) + + fV010 * (1 - fAx) * (1 - fAz) + + fV011 * (1 - fAx) * (fAz) + + -fV100 * (fAx) * (1 - fAz) + + -fV101 * (fAx) * (fAz) + + fV110 * (fAx) * (1 - fAz) + + fV111 * (fAx) * (fAz); + + double gradZ = + -fV000 * (1 - fAx) * (1 - fAy) + + fV001 * (1 - fAx) * (1 - fAy) + + -fV010 * (1 - fAx) * (fAy) + + fV011 * (1 - fAx) * (fAy) + + -fV100 * (fAx) * (1 - fAy) + + fV101 * (fAx) * (1 - fAy) + + -fV110 * (fAx) * (fAy) + + fV111 * (fAx) * (fAy); + + return new Vector3d(gradX, gradY, gradZ); + } + + } + +} diff --git a/implicit/CachingMeshSDF.cs b/implicit/CachingMeshSDF.cs new file mode 100644 index 00000000..e2931bb9 --- /dev/null +++ b/implicit/CachingMeshSDF.cs @@ -0,0 +1,587 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; + + + +namespace g3 +{ + + /// + /// [RMS] this is variant of MeshSignedDistanceGrid that does lazy evaluation of actual distances, + /// using mesh spatial data structure. This is much faster if we are doing continuation-method + /// marching cubes as only values on surface will be computed! + /// + /// + /// + /// Compute discretely-sampled (ie gridded) signed distance field for a mesh + /// The basic approach is, first compute exact distances in a narrow band, and then + /// extend out to rest of grid using fast "sweeping" (ie like a distance transform). + /// The resulting unsigned grid is then signed using ray-intersection counting, which + /// is also computed on the grid, so no BVH is necessary + /// + /// If you set ComputeMode to NarrowBandOnly, result is a narrow-band signed distance field. + /// This is quite a bit faster as the sweeping is the most computationally-intensive step. + /// + /// Caveats: + /// - the "narrow band" is based on triangle bounding boxes, so it is not necessarily + /// that "narrow" if you have large triangles on a diagonal to grid axes + /// + /// + /// Potential optimizations: + /// - Often we have a spatial data structure that would allow faster computation of the + /// narrow-band distances (which become quite expensive if we want a wider band!) + /// Not clear how to take advantage of this though. Perhaps we could have a binary + /// grid that, in first pass, we set bits inside triangle bboxes to 1? Or perhaps + /// same as current code, but we use spatial-dist, and so for each ijk we only compute once? + /// (then have to test for computed value at each cell of each triangle...) + /// + /// + /// This code is based on the C++ implementation found at https://github.com/christopherbatty/SDFGen + /// Original license was public domain. + /// Permission granted by Christopher Batty to include C# port under Boost license. + /// + public class CachingMeshSDF + { + public DMesh3 Mesh; + public DMeshAABBTree3 Spatial; + public float CellSize; + + // Bounds of grid will be expanded this much in positive and negative directions. + // Useful for if you want field to extend outwards. + public Vector3d ExpandBounds = Vector3d.Zero; + + // max distance away from surface that we might need to evaluate + public float MaxOffsetDistance = 0; + + // Most of this parallelizes very well, makes a huge speed difference + public bool UseParallel = true; + + // should we try to compute signs? if not, grid remains unsigned + public bool ComputeSigns = true; + + // What counts as "inside" the mesh. Crossing count does not use triangle + // orientation, so inverted faces are fine, but overlapping shells or self intersections + // will be filled using even/odd rules (as seen along X axis...) + // Parity count is basically mesh winding number, handles overlap shells and + // self-intersections, but inverted shells are 'subtracted', and inverted faces are a disaster. + // Both modes handle internal cavities, neither handles open sheets. + public enum InsideModes + { + CrossingCount = 0, + ParityCount = 1 + } + public InsideModes InsideMode = InsideModes.ParityCount; + + // Implementation computes the triangle closest to each grid cell, can + // return this grid if desired (only reason not to is avoid hanging onto memory) + public bool WantClosestTriGrid = false; + + // grid of per-cell crossing or parity counts + public bool WantIntersectionsGrid = false; + + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + + + public bool DebugPrint = false; + + + // computed results + Vector3f grid_origin; + DenseGrid3f grid; + DenseGrid3i closest_tri_grid; + DenseGrid3i intersections_grid; + + public CachingMeshSDF(DMesh3 mesh, double cellSize, DMeshAABBTree3 spatial) + { + Mesh = mesh; + CellSize = (float)cellSize; + Spatial = spatial; + } + + + float UpperBoundDistance; + double MaxDistQueryDist; + + + public void Initialize() + { + // figure out origin & dimensions + AxisAlignedBox3d bounds = Mesh.CachedBounds; + + float fBufferWidth = (float)Math.Max(4*CellSize, 2*MaxOffsetDistance + 2*CellSize); + grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One - (Vector3f)ExpandBounds; + Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One + (Vector3f)ExpandBounds; + int ni = (int)((max.x - grid_origin.x) / CellSize) + 1; + int nj = (int)((max.y - grid_origin.y) / CellSize) + 1; + int nk = (int)((max.z - grid_origin.z) / CellSize) + 1; + + UpperBoundDistance = (float)((ni+nj+nk) * CellSize); + grid = new DenseGrid3f(ni, nj, nk, UpperBoundDistance); + + MaxDistQueryDist = MaxOffsetDistance + (2*CellSize*MathUtil.SqrtTwo); + + // closest triangle id for each grid cell + if ( WantClosestTriGrid ) + closest_tri_grid = new DenseGrid3i(ni, nj, nk, -1); + + // intersection_count(i,j,k) is # of tri intersections in (i-1,i]x{j}x{k} + DenseGrid3i intersection_count = new DenseGrid3i(ni, nj, nk, 0); + + + if (ComputeSigns == true) { + compute_intersections(grid_origin, CellSize, ni, nj, nk, intersection_count); + if (CancelF()) + return; + + // then figure out signs (inside/outside) from intersection counts + compute_signs(ni, nj, nk, grid, intersection_count); + if (CancelF()) + return; + + if (WantIntersectionsGrid) + intersections_grid = intersection_count; + } + } + + + public float GetValue(Vector3i idx) + { + float f = grid[idx]; + if ( f == UpperBoundDistance || f == -UpperBoundDistance ) { + Vector3d p = cell_center(idx); + + float sign = Math.Sign(f); + + double dsqr; + int near_tid = Spatial.FindNearestTriangle(p, out dsqr, MaxDistQueryDist); + //int near_tid = Spatial.FindNearestTriangle(p, out dsqr); + if ( near_tid == DMesh3.InvalidID ) { + f += 0.0001f; + } else { + f = sign * (float)Math.Sqrt(dsqr); + } + + grid[idx] = f; + if (closest_tri_grid != null) + closest_tri_grid[idx] = near_tid; + } + return f; + } + + + + + + + + + public Vector3i Dimensions { + get { return new Vector3i(grid.ni, grid.nj, grid.nk); } + } + + /// + /// SDF grid available after calling Compute() + /// + public DenseGrid3f Grid { + get { return grid; } + } + + /// + /// Origin of the SDF grid, in same coordinates as mesh + /// + public Vector3f GridOrigin { + get { return grid_origin; } + } + + + public DenseGrid3i ClosestTriGrid { + get { + if ( WantClosestTriGrid == false) + throw new Exception("Set WantClosestTriGrid=true to return this value"); + return closest_tri_grid; + } + } + public DenseGrid3i IntersectionsGrid { + get { + if (WantIntersectionsGrid == false) + throw new Exception("Set WantIntersectionsGrid=true to return this value"); + return intersections_grid; + } + } + + + public float this[int i, int j, int k] { + get { return grid[i, j, k]; } + } + public float this[Vector3i idx] { + get { return grid[idx.x, idx.y, idx.z]; } + } + + public Vector3f CellCenter(int i, int j, int k) { + return cell_center(new Vector3i(i, j, k)); + } + Vector3f cell_center(Vector3i ijk) + { + return new Vector3f((float)ijk.x * CellSize + grid_origin[0], + (float)ijk.y * CellSize + grid_origin[1], + (float)ijk.z * CellSize + grid_origin[2]); + } + + + + + + + + // fill the intersection grid w/ number of intersections in each cell + void compute_intersections(Vector3f origin, float dx, int ni, int nj, int nk, DenseGrid3i intersection_count) + { + double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; + double invdx = 1.0 / dx; + + bool cancelled = false; + + // this is what we will do for each triangle. There are no grid-reads, only grid-writes, + // since we use atomic_increment, it is always thread-safe + Action ProcessTriangleF = (tid) => { + if (tid % 100 == 0 && CancelF() == true) + cancelled = true; + if (cancelled) return; + + Vector3d xp = Vector3d.Zero, xq = Vector3d.Zero, xr = Vector3d.Zero; + Mesh.GetTriVertices(tid, ref xp, ref xq, ref xr); + + + bool neg_x = false; + if (InsideMode == InsideModes.ParityCount) { + Vector3d n = MathUtil.FastNormalDirection(ref xp, ref xq, ref xr); + neg_x = n.x > 0; + } + + // real ijk coordinates of xp/xq/xr + double fip = (xp[0] - ox) * invdx, fjp = (xp[1] - oy) * invdx, fkp = (xp[2] - oz) * invdx; + double fiq = (xq[0] - ox) * invdx, fjq = (xq[1] - oy) * invdx, fkq = (xq[2] - oz) * invdx; + double fir = (xr[0] - ox) * invdx, fjr = (xr[1] - oy) * invdx, fkr = (xr[2] - oz) * invdx; + + // recompute j/k integer bounds of triangle w/o exact band + int j0 = MathUtil.Clamp((int)Math.Ceiling(MathUtil.Min(fjp, fjq, fjr)), 0, nj - 1); + int j1 = MathUtil.Clamp((int)Math.Floor(MathUtil.Max(fjp, fjq, fjr)), 0, nj - 1); + int k0 = MathUtil.Clamp((int)Math.Ceiling(MathUtil.Min(fkp, fkq, fkr)), 0, nk - 1); + int k1 = MathUtil.Clamp((int)Math.Floor(MathUtil.Max(fkp, fkq, fkr)), 0, nk - 1); + + // and do intersection counts + for (int k = k0; k <= k1; ++k) { + for (int j = j0; j <= j1; ++j) { + double a, b, c; + if (point_in_triangle_2d(j, k, fjp, fkp, fjq, fkq, fjr, fkr, out a, out b, out c)) { + double fi = a * fip + b * fiq + c * fir; // intersection i coordinate + int i_interval = (int)(Math.Ceiling(fi)); // intersection is in (i_interval-1,i_interval] + if (i_interval < 0) { + intersection_count.atomic_incdec(0, j, k, neg_x); + } else if (i_interval < ni) { + intersection_count.atomic_incdec(i_interval, j, k, neg_x); + } else { + // we ignore intersections that are beyond the +x side of the grid + } + } + } + } + }; + + if (UseParallel) { + gParallel.ForEach(Mesh.TriangleIndices(), ProcessTriangleF); + } else { + foreach (int tid in Mesh.TriangleIndices()) { + ProcessTriangleF(tid); + } + } + + } + + + + + + // iterate through each x-row of grid and set unsigned distances to be negative + // inside the mesh, based on the intersection_counts + void compute_signs(int ni, int nj, int nk, DenseGrid3f distances, DenseGrid3i intersection_counts) + { + Func isInsideF = (count) => { return count % 2 == 1; }; + if (InsideMode == InsideModes.ParityCount) + isInsideF = (count) => { return count > 0; }; + + if (UseParallel) { + // can process each x-row in parallel + AxisAlignedBox2i box = new AxisAlignedBox2i(0, 0, nj, nk); + gParallel.ForEach(box.IndicesExclusive(), (vi) => { + if (CancelF()) + return; + + int j = vi.x, k = vi.y; + int total_count = 0; + for (int i = 0; i < ni; ++i) { + total_count += intersection_counts[i, j, k]; + if (isInsideF(total_count)) { // if parity of intersections so far is odd, + distances[i, j, k] = -distances[i, j, k]; // we are inside the mesh + } + } + }); + + } else { + + for (int k = 0; k < nk; ++k) { + if (CancelF()) + return; + + for (int j = 0; j < nj; ++j) { + int total_count = 0; + for (int i = 0; i < ni; ++i) { + total_count += intersection_counts[i, j, k]; + if (isInsideF(total_count)) { // if parity of intersections so far is odd, + distances[i, j, k] = -distances[i, j, k]; // we are inside the mesh + } + } + } + } + } + } + + + + + + // calculate twice signed area of triangle (0,0)-(x1,y1)-(x2,y2) + // return an SOS-determined sign (-1, +1, or 0 only if it's a truly degenerate triangle) + static public int orientation(double x1, double y1, double x2, double y2, out double twice_signed_area) + { + twice_signed_area = y1 * x2 - x1 * y2; + if (twice_signed_area > 0) return 1; + else if (twice_signed_area < 0) return -1; + else if (y2 > y1) return 1; + else if (y2 < y1) return -1; + else if (x1 > x2) return 1; + else if (x1 < x2) return -1; + else return 0; // only true when x1==x2 and y1==y2 + } + + + // robust test of (x0,y0) in the triangle (x1,y1)-(x2,y2)-(x3,y3) + // if true is returned, the barycentric coordinates are set in a,b,c. + static public bool point_in_triangle_2d(double x0, double y0, + double x1, double y1, double x2, double y2, double x3, double y3, + out double a, out double b, out double c) + { + a = b = c = 0; + x1 -= x0; x2 -= x0; x3 -= x0; + y1 -= y0; y2 -= y0; y3 -= y0; + int signa = orientation(x2, y2, x3, y3, out a); + if (signa == 0) return false; + int signb = orientation(x3, y3, x1, y1, out b); + if (signb != signa) return false; + int signc = orientation(x1, y1, x2, y2, out c); + if (signc != signa) return false; + double sum = a + b + c; + // if the SOS signs match and are nonzero, there's no way all of a, b, and c are zero. + if (sum == 0) + throw new Exception("MakeNarrowBandLevelSet.point_in_triangle_2d: badness!"); + a /= sum; + b /= sum; + c /= sum; + return true; + } + + } + + + + + + + + + + + + + /// + /// Tri-linear interpolant for a 3D dense grid. Supports grid translation + /// via GridOrigin, but does not support scaling or rotation. If you need those, + /// you can wrap this in something that does the xform. + /// + public class CachingMeshSDFImplicit : BoundedImplicitFunction3d + { + public CachingMeshSDF SDF; + public double CellSize; + public Vector3d GridOrigin; + + // value to return if query point is outside grid (in an SDF + // outside is usually positive). Need to do math with this value, + // so don't use double.MaxValue or square will overflow + public double Outside = Math.Sqrt(Math.Sqrt(double.MaxValue)); + + public CachingMeshSDFImplicit(CachingMeshSDF sdf) + { + SDF = sdf; + GridOrigin = sdf.GridOrigin; + CellSize = sdf.CellSize; + } + + public AxisAlignedBox3d Bounds() + { + return new AxisAlignedBox3d( + GridOrigin.x, GridOrigin.y, GridOrigin.z, + GridOrigin.x + CellSize * SDF.Grid.ni, + GridOrigin.y + CellSize * SDF.Grid.nj, + GridOrigin.z + CellSize * SDF.Grid.nk); + } + + + public double Value(ref Vector3d pt) + { + Vector3d gridPt = new Vector3d( + ((pt.x - GridOrigin.x) / CellSize), + ((pt.y - GridOrigin.y) / CellSize), + ((pt.z - GridOrigin.z) / CellSize)); + + // compute integer coordinates + int x0 = (int)gridPt.x; + int y0 = (int)gridPt.y, y1 = y0 + 1; + int z0 = (int)gridPt.z, z1 = z0 + 1; + + // clamp to grid + if (x0 < 0 || (x0 + 1) >= SDF.Grid.ni || + y0 < 0 || y1 >= SDF.Grid.nj || + z0 < 0 || z1 >= SDF.Grid.nk) + return Outside; + + // convert double coords to [0,1] range + double fAx = gridPt.x - (double)x0; + double fAy = gridPt.y - (double)y0; + double fAz = gridPt.z - (double)z0; + double OneMinusfAx = 1.0 - fAx; + + // compute trilinear interpolant. The code below tries to do this with the fewest + // number of variables, in hopes that optimizer will be clever about re-using registers, etc. + // Commented code at bottom is fully-expanded version. + // [TODO] it is possible to implement lerps here as a+(b-a)*t, saving a multiply and a variable. + // This is numerically worse, but since the grid values are floats and + // we are computing in doubles, does it matter? + double xa, xb; + + get_value_pair(x0, y0, z0, out xa, out xb); + double yz = (1 - fAy) * (1 - fAz); + double sum = (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y0, z1, out xa, out xb); + yz = (1 - fAy) * (fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y1, z0, out xa, out xb); + yz = (fAy) * (1 - fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + get_value_pair(x0, y1, z1, out xa, out xb); + yz = (fAy) * (fAz); + sum += (OneMinusfAx * xa + fAx * xb) * yz; + + return sum; + + // fV### is grid cell corner index + //return + // fV000 * (1 - fAx) * (1 - fAy) * (1 - fAz) + + // fV001 * (1 - fAx) * (1 - fAy) * (fAz) + + // fV010 * (1 - fAx) * (fAy) * (1 - fAz) + + // fV011 * (1 - fAx) * (fAy) * (fAz) + + // fV100 * (fAx) * (1 - fAy) * (1 - fAz) + + // fV101 * (fAx) * (1 - fAy) * (fAz) + + // fV110 * (fAx) * (fAy) * (1 - fAz) + + // fV111 * (fAx) * (fAy) * (fAz); + } + + + + void get_value_pair(int i, int j, int k, out double a, out double b) + { + a = SDF.GetValue(new Vector3i(i,j,k)); + b = SDF.GetValue(new Vector3i(i+1,j,k)); + } + + + + public Vector3d Gradient(ref Vector3d pt) + { + Vector3d gridPt = new Vector3d( + ((pt.x - GridOrigin.x) / CellSize), + ((pt.y - GridOrigin.y) / CellSize), + ((pt.z - GridOrigin.z) / CellSize)); + + // clamp to grid + if (gridPt.x < 0 || gridPt.x >= SDF.Grid.ni - 1 || + gridPt.y < 0 || gridPt.y >= SDF.Grid.nj - 1 || + gridPt.z < 0 || gridPt.z >= SDF.Grid.nk - 1) + return Vector3d.Zero; + + // compute integer coordinates + int x0 = (int)gridPt.x; + int y0 = (int)gridPt.y, y1 = y0 + 1; + int z0 = (int)gridPt.z, z1 = z0 + 1; + + // convert double coords to [0,1] range + double fAx = gridPt.x - (double)x0; + double fAy = gridPt.y - (double)y0; + double fAz = gridPt.z - (double)z0; + + double fV000, fV100; + get_value_pair(x0, y0, z0, out fV000, out fV100); + double fV010, fV110; + get_value_pair(x0, y1, z0, out fV010, out fV110); + double fV001, fV101; + get_value_pair(x0, y0, z1, out fV001, out fV101); + double fV011, fV111; + get_value_pair(x0, y1, z1, out fV011, out fV111); + + // [TODO] can re-order this to vastly reduce number of ops! + double gradX = + -fV000 * (1 - fAy) * (1 - fAz) + + -fV001 * (1 - fAy) * (fAz) + + -fV010 * (fAy) * (1 - fAz) + + -fV011 * (fAy) * (fAz) + + fV100 * (1 - fAy) * (1 - fAz) + + fV101 * (1 - fAy) * (fAz) + + fV110 * (fAy) * (1 - fAz) + + fV111 * (fAy) * (fAz); + + double gradY = + -fV000 * (1 - fAx) * (1 - fAz) + + -fV001 * (1 - fAx) * (fAz) + + fV010 * (1 - fAx) * (1 - fAz) + + fV011 * (1 - fAx) * (fAz) + + -fV100 * (fAx) * (1 - fAz) + + -fV101 * (fAx) * (fAz) + + fV110 * (fAx) * (1 - fAz) + + fV111 * (fAx) * (fAz); + + double gradZ = + -fV000 * (1 - fAx) * (1 - fAy) + + fV001 * (1 - fAx) * (1 - fAy) + + -fV010 * (1 - fAx) * (fAy) + + fV011 * (1 - fAx) * (fAy) + + -fV100 * (fAx) * (1 - fAy) + + fV101 * (fAx) * (1 - fAy) + + -fV110 * (fAx) * (fAy) + + fV111 * (fAx) * (fAy); + + return new Vector3d(gradX, gradY, gradZ); + } + + } + + + +} diff --git a/implicit/FalloffFunctions.cs b/implicit/FalloffFunctions.cs new file mode 100644 index 00000000..27b7e0ac --- /dev/null +++ b/implicit/FalloffFunctions.cs @@ -0,0 +1,81 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + public interface IFalloffFunction + { + /// + /// t is value in range [0,1], returns value in range [0,1] + /// + double FalloffT(double t); + + /// + /// In most cases, users of IFalloffFunction will make a local copy + /// + IFalloffFunction Duplicate(); + } + + + + /// + /// returns 1 in range [0,ConstantRange], and then falls off to 0 in range [ConstantRange,1] + /// + public class LinearFalloff : IFalloffFunction + { + public double ConstantRange = 0; + + public double FalloffT(double t) + { + t = MathUtil.Clamp(t, 0.0, 1.0); + if (ConstantRange <= 0) + return 1.0 - t; + else + return (t < ConstantRange) ? 1.0 : 1.0 - ((t - ConstantRange) / (1 - ConstantRange)); + } + + + public IFalloffFunction Duplicate() + { + return new WyvillFalloff() { + ConstantRange = this.ConstantRange + }; + } + } + + + + /// + /// returns 1 in range [0,ConstantRange], and then falls off to 0 in range [ConstantRange,1] + /// + public class WyvillFalloff : IFalloffFunction + { + public double ConstantRange = 0; + + public double FalloffT(double t) + { + t = MathUtil.Clamp(t, 0.0, 1.0); + if (ConstantRange <= 0) + return MathUtil.WyvillFalloff01(t); + else + return MathUtil.WyvillFalloff(t, ConstantRange, 1.0); + } + + + public IFalloffFunction Duplicate() + { + return new WyvillFalloff() { + ConstantRange = this.ConstantRange + }; + } + + } + + + +} diff --git a/mesh/RemesherPro.cs b/mesh/RemesherPro.cs new file mode 100644 index 00000000..53cf6f51 --- /dev/null +++ b/mesh/RemesherPro.cs @@ -0,0 +1,731 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Threading; +using g3; + +namespace gs +{ + /// + /// Extension to Remesher that is smarter about which edges/vertices to touch: + /// - queue tracks edges that were affected on last pass, and hence might need to be updated + /// - FastSplitIteration() just does splits, to reach target edge length as quickly as possible + /// - RemeshIteration() applies remesh pass for modified edges + /// - TrackedSmoothPass() smooths all vertices but only adds to queue if edge changes enough + /// - TrackedProjectionPass() same + /// + /// + public class RemesherPro : Remesher + { + + public bool UseFaceAlignedProjection = false; + public int FaceProjectionPassesPerIteration = 1; + + + + public RemesherPro(DMesh3 m) : base(m) + { + } + + + HashSet modified_edges; + SpinLock modified_edges_lock = new SpinLock(); + + + protected IEnumerable EdgesIterator() + { + int cur_eid = start_edges(); + bool done = false; + do { + yield return cur_eid; + cur_eid = next_edge(cur_eid, out done); + } while (done == false); + } + + + void queue_one_ring_safe(int vid) { + if ( mesh.IsVertex(vid) ) { + bool taken = false; + modified_edges_lock.Enter(ref taken); + + foreach (int eid in mesh.VtxEdgesItr(vid)) + modified_edges.Add(eid); + + modified_edges_lock.Exit(); + } + } + + void queue_one_ring(int vid) { + if ( mesh.IsVertex(vid) ) { + foreach (int eid in mesh.VtxEdgesItr(vid)) + modified_edges.Add(eid); + } + } + + void queue_edge_safe(int eid) { + bool taken = false; + modified_edges_lock.Enter(ref taken); + + modified_edges.Add(eid); + + modified_edges_lock.Exit(); + } + + void queue_edge(int eid) { + modified_edges.Add(eid); + } + + + + Action SplitF = null; + + + protected override void OnEdgeSplit(int edgeID, int va, int vb, DMesh3.EdgeSplitInfo splitInfo) + { + if (SplitF != null) + SplitF(edgeID, va, vb, splitInfo.vNew); + } + + + + /// + /// Converge on remeshed result as quickly as possible + /// + public void FastestRemesh(int nMaxIterations = 25, bool bDoFastSplits = true) + { + ResetQueue(); + + // first we do fast splits to hit edge length target + // ?? should we do project in fastsplit? will result in more splits, and + // we are going to project in first remesh pass anyway... + // (but that might result in larger queue in first remesh pass?) + int fastsplit_i = 0; + int max_fastsplits = nMaxIterations; + if (bDoFastSplits) { + if (Cancelled()) + return; + + bool bContinue = true; + while ( bContinue ) { + int nSplits = FastSplitIteration(); + if (fastsplit_i++ > max_fastsplits) + bContinue = false; + if ((double)nSplits / (double)mesh.EdgeCount < 0.01) + bContinue = false; + if (Cancelled()) + return; + }; + ResetQueue(); + } + + // should we do a fast collapse pass? more dangerous... + + // now do queued remesh iterations. + // disable projection every other iteration to improve speed + var saveMode = this.ProjectionMode; + for (int k = 0; k < nMaxIterations - 1; ++k) { + if (Cancelled()) + break; + ProjectionMode = (k % 2 == 0) ? TargetProjectionMode.NoProjection : saveMode; + RemeshIteration(); + } + + // final pass w/ full projection + ProjectionMode = saveMode; + + if (Cancelled()) + return; + + RemeshIteration(); + } + + + + + + + /// + /// This is a remesh that tries to recover sharp edges by aligning triangles to face normals + /// of our projection target (similar to Ohtake RZN-flow). + /// + public void SharpEdgeReprojectionRemesh(int nRemeshIterations, int nTuneIterations, bool bDoFastSplits = true) + { + if (ProjectionTarget == null || ProjectionTarget is IOrientedProjectionTarget == false) + throw new Exception("RemesherPro.SharpEdgeReprojectionRemesh: cannot call this without a ProjectionTarget that has normals"); + + ResetQueue(); + + // first we do fast splits to hit edge length target + // ?? should we do project in fastsplit? will result in more splits, and + // we are going to project in first remesh pass anyway... + // (but that might result in larger queue in first remesh pass?) + int fastsplit_i = 0; + int max_fastsplits = nRemeshIterations; + if (bDoFastSplits) { + if (Cancelled()) + return; + + bool bContinue = true; + while (bContinue) { + int nSplits = FastSplitIteration(); + if (fastsplit_i++ > max_fastsplits) + bContinue = false; + if ((double)nSplits / (double)mesh.EdgeCount < 0.01) + bContinue = false; + if (Cancelled()) + return; + }; + ResetQueue(); + } + + bool save_use_face_aligned = UseFaceAlignedProjection; + UseFaceAlignedProjection = true; + FaceProjectionPassesPerIteration = 1; + + // should we do a fast collapse pass? more dangerous but would get rid of all the tiny + // edges we might have just created, and/or get us closer to target resolution + + // now do queued remesh iterations. As we proceed we slowly step + // down the smoothing factor, this helps us get triangles closer + // to where they will ultimately want to go + double smooth_speed = SmoothSpeedT; + for (int k = 0; k < nRemeshIterations; ++k) { + if (Cancelled()) + break; + RemeshIteration(); + if ( k > nRemeshIterations/2 ) + SmoothSpeedT *= 0.9f; + } + + // [TODO] would like to still do splits and maybe sometimes flips here. + // Perhaps this could be something more combinatorial? Like, test all the + // edges we queued in the projection pass, if we can get better alignment + // after a with flip or split, do it + //SmoothSpeedT = 0; + //MinEdgeLength = MinEdgeLength * 0.1; + //EnableFlips = false; + for (int k = 0; k < nTuneIterations; ++k) { + if (Cancelled()) + break; + TrackedFaceProjectionPass(); + //RemeshIteration(); + } + + SmoothSpeedT = smooth_speed; + UseFaceAlignedProjection = save_use_face_aligned; + + //TrackedProjectionPass(true); + } + + + + + + + + + + + + /// + /// Reset tracked-edges queue. Should be called if mesh is modified by external functions + /// between passes, and also between different types of passes (eg FastSplitIteration vs RemeshIteration) + /// + public void ResetQueue() + { + if (modified_edges != null) { + modified_edges.Clear(); + modified_edges = null; + } + } + + List edges_buffer = new List(); + + + /// + /// This pass only does edge splits. Returns number of split edges. + /// Tracks previously-split + /// + public int FastSplitIteration() + { + if (mesh.TriangleCount == 0) // badness if we don't catch this... + return 0; + + PushState(); + EnableFlips = EnableCollapses = EnableSmoothing = false; + ProjectionMode = TargetProjectionMode.NoProjection; + + begin_pass(); + + // Iterate over all edges in the mesh at start of pass. + // Some may be removed, so we skip those. + // However, some old eid's may also be re-used, so we will touch + // some new edges. Can't see how we could efficiently prevent this. + // + begin_ops(); + + IEnumerable edgesItr = EdgesIterator(); + if (modified_edges == null) { + modified_edges = new HashSet(); + } else { + edges_buffer.Clear(); edges_buffer.AddRange(modified_edges); + edgesItr = edges_buffer; + modified_edges.Clear(); + } + + int startEdges = Mesh.EdgeCount; + int splitEdges = 0; + + // When we split an edge, we need to check it and the adjacent ones we added. + // Because of overhead in ProcessEdge, it is worth it to do a distance-check here + double max_edge_len_sqr = MaxEdgeLength * MaxEdgeLength; + SplitF = (edgeID, a, b, vNew) => { + Vector3d v = Mesh.GetVertex(vNew); + foreach (int eid in Mesh.VtxEdgesItr(vNew)) { + Index2i ev = Mesh.GetEdgeV(eid); + int othervid = (ev.a == vNew) ? ev.b : ev.a; + if (mesh.GetVertex(othervid).DistanceSquared(ref v) > max_edge_len_sqr) + queue_edge(eid); + } + //queue_one_ring(vNew); + }; + + + ModifiedEdgesLastPass = 0; + int processedLastPass = 0; + foreach (int cur_eid in edgesItr) { + if (Cancelled()) + goto abort_compute; + + if (mesh.IsEdge(cur_eid)) { + Index2i ev = mesh.GetEdgeV(cur_eid); + Index2i ov = mesh.GetEdgeOpposingV(cur_eid); + + processedLastPass++; + ProcessResult result = ProcessEdge(cur_eid); + if (result == ProcessResult.Ok_Split) { + // new edges queued by SplitF + ModifiedEdgesLastPass++; + splitEdges++; + } + } + } + end_ops(); + + //System.Console.WriteLine("FastSplitIteration: start {0} end {1} processed: {2} modified: {3} queue: {4}", + // startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count); + + abort_compute: + SplitF = null; + PopState(); + + end_pass(); + + return splitEdges; + } + + + + + + + public virtual void RemeshIteration() + { + if (mesh.TriangleCount == 0) // badness if we don't catch this... + return; + + begin_pass(); + + // Iterate over all edges in the mesh at start of pass. + // Some may be removed, so we skip those. + // However, some old eid's may also be re-used, so we will touch + // some new edges. Can't see how we could efficiently prevent this. + // + begin_ops(); + + IEnumerable edgesItr = EdgesIterator(); + if (modified_edges == null) { + modified_edges = new HashSet(); + } else { + edges_buffer.Clear(); edges_buffer.AddRange(modified_edges); + edgesItr = edges_buffer; + modified_edges.Clear(); + } + + int startEdges = Mesh.EdgeCount; + int flips = 0, splits = 0, collapes = 0; + + ModifiedEdgesLastPass = 0; + int processedLastPass = 0; + foreach (int cur_eid in edgesItr) { + if (Cancelled()) + return; + + if (mesh.IsEdge(cur_eid)) { + Index2i ev = mesh.GetEdgeV(cur_eid); + Index2i ov = mesh.GetEdgeOpposingV(cur_eid); + + // TODO: optimize the queuing here, are over-doing it! + // TODO: be able to queue w/o flip (eg queue from smooth never requires flip check) + + processedLastPass++; + ProcessResult result = ProcessEdge(cur_eid); + if (result == ProcessResult.Ok_Collapsed) { + queue_one_ring(ev.a); queue_one_ring(ev.b); + queue_one_ring(ov.a); queue_one_ring(ov.b); + ModifiedEdgesLastPass++; + collapes++; + } else if (result == ProcessResult.Ok_Split) { + queue_one_ring(ev.a); queue_one_ring(ev.b); + queue_one_ring(ov.a); queue_one_ring(ov.b); + ModifiedEdgesLastPass++; + splits++; + } else if (result == ProcessResult.Ok_Flipped) { + queue_one_ring(ev.a); queue_one_ring(ev.b); + queue_one_ring(ov.a); queue_one_ring(ov.b); + ModifiedEdgesLastPass++; + flips++; + } + } + } + end_ops(); + + //System.Console.WriteLine("RemeshIteration: start {0} end {1} processed: {2} modified: {3} queue: {4}", + // startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count); + //System.Console.WriteLine(" flips {0} splits {1} collapses {2}", flips, splits, collapes); + + if (Cancelled()) + return; + + begin_smooth(); + if (EnableSmoothing && SmoothSpeedT > 0) { + TrackedSmoothPass(EnableParallelSmooth); + DoDebugChecks(); + } + end_smooth(); + + if (Cancelled()) + return; + + begin_project(); + if (ProjectionTarget != null && ProjectionMode == TargetProjectionMode.AfterRefinement) { + //FullProjectionPass(); + + if (UseFaceAlignedProjection) { + for ( int i = 0; i < FaceProjectionPassesPerIteration; ++i ) + TrackedFaceProjectionPass(); + } else { + TrackedProjectionPass(EnableParallelProjection); + } + DoDebugChecks(); + } + end_project(); + + end_pass(); + } + + + protected virtual void TrackedSmoothPass(bool bParallel) + { + InitializeVertexBufferForPass(); + + Func smoothFunc = MeshUtil.UniformSmooth; + if (CustomSmoothF != null) { + smoothFunc = CustomSmoothF; + } else { + if (SmoothType == SmoothTypes.MeanValue) + smoothFunc = MeshUtil.MeanValueSmooth; + else if (SmoothType == SmoothTypes.Cotan) + smoothFunc = MeshUtil.CotanSmooth; + } + + Action smooth = (vID) => { + Vector3d vCur = Mesh.GetVertex(vID); + bool bModified = false; + Vector3d vSmoothed = ComputeSmoothedVertexPos(vID, smoothFunc, out bModified); + //if (vCur.EpsilonEqual(vSmoothed, MathUtil.ZeroTolerancef)) + // bModified = false; + if (bModified) { + vModifiedV[vID] = true; + vBufferV[vID] = vSmoothed; + + foreach (int eid in mesh.VtxEdgesItr(vID)) { + Index2i ev = Mesh.GetEdgeV(eid); + int othervid = (ev.a == vID) ? ev.b : ev.a; + Vector3d otherv = mesh.GetVertex(othervid); + double old_len = vCur.Distance(otherv); + double new_len = vSmoothed.Distance(otherv); + if (new_len < MinEdgeLength || new_len > MaxEdgeLength) + queue_edge_safe(eid); + } + } + }; + + + if (bParallel) { + gParallel.ForEach(smooth_vertices(), smooth); + } else { + foreach (int vID in smooth_vertices()) + smooth(vID); + } + + ApplyVertexBuffer(bParallel); + //System.Console.WriteLine("Smooth Pass: queue: {0}", modified_edges.Count); + } + + + + + + // [TODO] projection pass + // - only project vertices modified by smooth pass? + // - and/or verts in set of modified edges? + protected virtual void TrackedProjectionPass(bool bParallel) + { + InitializeVertexBufferForPass(); + + Action project = (vID) => { + Vector3d vCur = Mesh.GetVertex(vID); + bool bModified = false; + Vector3d vProjected = ComputeProjectedVertexPos(vID, out bModified); + if (vCur.EpsilonEqual(vProjected, MathUtil.ZeroTolerancef)) + bModified = false; + if (bModified) { + vModifiedV[vID] = true; + vBufferV[vID] = vProjected; + + foreach (int eid in mesh.VtxEdgesItr(vID)) { + Index2i ev = Mesh.GetEdgeV(eid); + int othervid = (ev.a == vID) ? ev.b : ev.a; + Vector3d otherv = mesh.GetVertex(othervid); + double old_len = vCur.Distance(otherv); + double new_len = vProjected.Distance(otherv); + if (new_len < MinEdgeLength || new_len > MaxEdgeLength) + queue_edge_safe(eid); + } + } + }; + + + if (bParallel) { + gParallel.ForEach(smooth_vertices(), project); + } else { + foreach (int vID in smooth_vertices()) + project(vID); + } + + ApplyVertexBuffer(bParallel); + //System.Console.WriteLine("Projection Pass: queue: {0}", modified_edges.Count); + } + + + + + /// + /// This computes projected position w/ proper constraints/etc. + /// Does not modify mesh. + /// + protected virtual Vector3d ComputeProjectedVertexPos(int vID, out bool bModified) + { + bModified = false; + + if (vertex_is_constrained(vID)) + return Mesh.GetVertex(vID); + if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) + return Mesh.GetVertex(vID); + + Vector3d curpos = mesh.GetVertex(vID); + Vector3d projected = ProjectionTarget.Project(curpos, vID); + bModified = true; + return projected; + } + + + + + + + + /* + * Implementation of face-aligned projection. Combined with rest of remesh + * this is basically an RZN-flow-type algorithm. + */ + + + + protected DVector vBufferVWeights = new DVector(); + + protected virtual void InitializeBuffersForFacePass() + { + base.InitializeVertexBufferForPass(); + if (vBufferVWeights.size < vBufferV.size) + vBufferVWeights.resize(vBufferV.size); + + int NV = mesh.MaxVertexID; + for (int i = 0; i < NV; ++i) { + vBufferV[i] = Vector3d.Zero; + vBufferVWeights[i] = 0; + } + } + + + + // [TODO] projection pass + // - only project vertices modified by smooth pass? + // - and/or verts in set of modified edges? + protected virtual void TrackedFaceProjectionPass() + { + IOrientedProjectionTarget normalTarget = ProjectionTarget as IOrientedProjectionTarget; + if (normalTarget == null) + throw new Exception("RemesherPro.TrackedFaceProjectionPass: projection target does not have normals!"); + + InitializeBuffersForFacePass(); + + SpinLock buffer_lock = new SpinLock(); + + // this function computes rotated position of triangle, such that it + // aligns with face normal on target surface. We accumulate weighted-average + // of vertex positions, which we will then use further down where possible. + Action process_triangle = (tid) => { + Vector3d normal; double area; Vector3d centroid; + mesh.GetTriInfo(tid, out normal, out area, out centroid); + + Vector3d projNormal; + Vector3d projPos = normalTarget.Project(centroid, out projNormal); + + Index3i tv = mesh.GetTriangle(tid); + Vector3d v0 = mesh.GetVertex(tv.a), v1 = mesh.GetVertex(tv.b), v2 = mesh.GetVertex(tv.c); + + // ugh could probably do this more efficiently... + Frame3f triF = new Frame3f(centroid, normal); + v0 = triF.ToFrameP(ref v0); v1 = triF.ToFrameP(ref v1); v2 = triF.ToFrameP(ref v2); + triF.AlignAxis(2, (Vector3f)projNormal); + triF.Origin = (Vector3f)projPos; + v0 = triF.FromFrameP(ref v0); v1 = triF.FromFrameP(ref v1); v2 = triF.FromFrameP(ref v2); + + double dot = normal.Dot(projNormal); + dot = MathUtil.Clamp(dot, 0, 1.0); + double w = area * (dot * dot * dot); + + bool taken = false; + buffer_lock.Enter(ref taken); + vBufferV[tv.a] += w * v0; vBufferVWeights[tv.a] += w; + vBufferV[tv.b] += w * v1; vBufferVWeights[tv.b] += w; + vBufferV[tv.c] += w * v2; vBufferVWeights[tv.c] += w; + buffer_lock.Exit(); + }; + + // compute face-aligned vertex positions + gParallel.ForEach(mesh.TriangleIndices(), process_triangle); + + + // ok now we filter out all the positions we can't change, as well as vertices that + // did not actually move. We also queue any edges that moved far enough to fall + // under min/max edge length thresholds + gParallel.ForEach(mesh.VertexIndices(), (vID) => { + vModifiedV[vID] = false; + if (vBufferVWeights[vID] < MathUtil.ZeroTolerance) + return; + if (vertex_is_constrained(vID)) + return; + if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0) + return; + + Vector3d curpos = mesh.GetVertex(vID); + Vector3d projPos = vBufferV[vID] / vBufferVWeights[vID]; + if (curpos.EpsilonEqual(projPos, MathUtil.ZeroTolerancef)) + return; + + vModifiedV[vID] = true; + vBufferV[vID] = projPos; + + foreach (int eid in mesh.VtxEdgesItr(vID)) { + Index2i ev = Mesh.GetEdgeV(eid); + int othervid = (ev.a == vID) ? ev.b : ev.a; + Vector3d otherv = mesh.GetVertex(othervid); + double old_len = curpos.Distance(otherv); + double new_len = projPos.Distance(otherv); + if (new_len < MinEdgeLength || new_len > MaxEdgeLength) + queue_edge_safe(eid); + } + + }); + + + // update vertices + ApplyVertexBuffer(true); + } + + + + + + + + + + + + + + + + struct SettingState + { + public bool EnableFlips; + public bool EnableCollapses; + public bool EnableSplits; + public bool EnableSmoothing; + + public double MinEdgeLength; + public double MaxEdgeLength; + + public double SmoothSpeedT; + public SmoothTypes SmoothType; + public TargetProjectionMode ProjectionMode; + } + List stateStack = new List(); + + public void PushState() + { + SettingState s = new SettingState() { + EnableFlips = this.EnableFlips, + EnableCollapses = this.EnableCollapses, + EnableSplits = this.EnableSplits, + EnableSmoothing = this.EnableSmoothing, + MinEdgeLength = this.MinEdgeLength, + MaxEdgeLength = this.MaxEdgeLength, + SmoothSpeedT = this.SmoothSpeedT, + SmoothType = this.SmoothType, + ProjectionMode = this.ProjectionMode + }; + stateStack.Add(s); + } + + public void PopState() + { + SettingState s = stateStack.Last(); + stateStack.RemoveAt(stateStack.Count - 1); + + this.EnableFlips = s.EnableFlips; + this.EnableCollapses = s.EnableCollapses; + this.EnableSplits = s.EnableSplits; + this.EnableSmoothing = s.EnableSmoothing; + this.MinEdgeLength = s.MinEdgeLength; + this.MaxEdgeLength = s.MaxEdgeLength; + this.SmoothSpeedT = s.SmoothSpeedT; + this.SmoothType = s.SmoothType; + this.ProjectionMode = s.ProjectionMode; + } + + + + + + } +} diff --git a/mesh_generators/MarchingCubesPro.cs b/mesh_generators/MarchingCubesPro.cs new file mode 100644 index 00000000..60f3da0a --- /dev/null +++ b/mesh_generators/MarchingCubesPro.cs @@ -0,0 +1,1058 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using g3; + +namespace gs +{ + public class MarchingCubesPro + { + /// + /// this is the function we will evaluate + /// + public ImplicitFunction3d Implicit; + + /// + /// mesh surface will be at this isovalue. Normally 0 unless you want + /// offset surface or field is not a distance-field. + /// + public double IsoValue = 0; + + /// bounding-box we will mesh inside of. We use the min-corner and + /// the width/height/depth, but do not clamp vertices to stay within max-corner, + /// we may spill one cell over + public AxisAlignedBox3d Bounds; + + /// + /// Length of edges of cubes that are marching. + /// currently, # of cells along axis = (int)(bounds_dimension / CellSize) + 1 + /// + public double CubeSize = 0.1; + + /// + /// Use multi-threading? Generally a good idea unless problem is very small or + /// you are multi-threading at a higher level (which may be more efficient as + /// we currently use very fine-grained spinlocks to synchronize) + /// + public bool ParallelCompute = true; + + public enum RootfindingModes { SingleLerp, LerpSteps, Bisection } + + /// + /// Which rootfinding method will be used to converge on surface along edges + /// + public RootfindingModes RootMode = RootfindingModes.SingleLerp; + + /// + /// number of iterations of rootfinding method (ignored for SingleLerp) + /// + public int RootModeSteps = 5; + + + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + + /* + * Outputs + */ + + // cube indices range from [Origin,CellDimensions) + public Vector3i CellDimensions; + + // computed mesh + public DMesh3 Mesh; + + + + public MarchingCubesPro() + { + // initialize w/ a basic sphere example + Implicit = new ImplicitSphere3d(); + Bounds = new AxisAlignedBox3d(Vector3d.Zero, 8); + CubeSize = 0.25; + } + + + + /// + /// Run MC algorithm and generate Output mesh + /// + public void Generate() + { + Mesh = new DMesh3(); + + int nx = (int)(Bounds.Width / CubeSize) + 1; + int ny = (int)(Bounds.Height / CubeSize) + 1; + int nz = (int)(Bounds.Depth / CubeSize) + 1; + CellDimensions = new Vector3i(nx, ny, nz); + GridBounds = new AxisAlignedBox3i(Vector3i.Zero, CellDimensions); + + corner_values_grid = new DenseGrid3f(nx+1, ny+1, nz+1, float.MaxValue); + edge_vertices = new Dictionary(); + corner_values = new Dictionary(); + + if (ParallelCompute) { + generate_parallel(); + } else { + generate_basic(); + } + } + + + public void GenerateContinuation(IEnumerable seeds) + { + Mesh = new DMesh3(); + + int nx = (int)(Bounds.Width / CubeSize) + 1; + int ny = (int)(Bounds.Height / CubeSize) + 1; + int nz = (int)(Bounds.Depth / CubeSize) + 1; + CellDimensions = new Vector3i(nx, ny, nz); + GridBounds = new AxisAlignedBox3i(Vector3i.Zero, CellDimensions); + + if (LastGridBounds != GridBounds) { + corner_values_grid = new DenseGrid3f(nx + 1, ny + 1, nz + 1, float.MaxValue); + edge_vertices = new Dictionary(); + corner_values = new Dictionary(); + if (ParallelCompute) + done_cells = new DenseGrid3i(CellDimensions.x, CellDimensions.y, CellDimensions.z, 0); + } else { + edge_vertices.Clear(); + corner_values.Clear(); + corner_values_grid.assign(float.MaxValue); + if (ParallelCompute) + done_cells.assign(0); + } + + if (ParallelCompute) { + generate_continuation_parallel(seeds); + } else { + generate_continuation(seeds); + } + + LastGridBounds = GridBounds; + } + + + + AxisAlignedBox3i GridBounds; + AxisAlignedBox3i LastGridBounds; + + + // we pass Cells around, this makes code cleaner + class GridCell + { + public Vector3i[] i; // indices of corners of cell + public double[] f; // field values at corners + + public GridCell() + { + // TODO we do not actually need to store i, we just need the min-corner! + i = new Vector3i[8]; + f = new double[8]; + } + + } + + + + void corner_pos(ref Vector3i ijk, ref Vector3d p) + { + p.x = Bounds.Min.x + CubeSize * ijk.x; + p.y = Bounds.Min.y + CubeSize * ijk.y; + p.z = Bounds.Min.z + CubeSize * ijk.z; + } + Vector3d corner_pos(ref Vector3i ijk) + { + return new Vector3d(Bounds.Min.x + CubeSize * ijk.x, + Bounds.Min.y + CubeSize * ijk.y, + Bounds.Min.z + CubeSize * ijk.z); + } + Vector3i cell_index(Vector3d pos) + { + return new Vector3i( + (int)((pos.x - Bounds.Min.x) / CubeSize), + (int)((pos.y - Bounds.Min.y) / CubeSize), + (int)((pos.z - Bounds.Min.z) / CubeSize)); + } + + + + // + // corner and edge hash functions, these pack the coordinate + // integers into 16-bits, so max of 65536 in any dimension. + // + + + long corner_hash(ref Vector3i idx) { + return ((long)idx.x&0xFFFF) | (((long)idx.y&0xFFFF) << 16) | (((long)idx.z&0xFFFF) << 32); + } + long corner_hash(int x, int y, int z) + { + return ((long)x & 0xFFFF) | (((long)y & 0xFFFF) << 16) | (((long)z & 0xFFFF) << 32); + } + + const int EDGE_X = 1 << 60; + const int EDGE_Y = 1 << 61; + const int EDGE_Z = 1 << 62; + + long edge_hash(ref Vector3i idx1, ref Vector3i idx2) + { + if ( idx1.x != idx2.x ) { + int xlo = Math.Min(idx1.x, idx2.x); + return corner_hash(xlo, idx1.y, idx1.z) | EDGE_X; + } else if ( idx1.y != idx2.y ) { + int ylo = Math.Min(idx1.y, idx2.y); + return corner_hash(idx1.x, ylo, idx1.z) | EDGE_Y; + } else { + int zlo = Math.Min(idx1.z, idx2.z); + return corner_hash(idx1.x, idx1.y, zlo) | EDGE_Z; + } + } + + + + // + // Hash table for edge vertices + // + + Dictionary edge_vertices = new Dictionary(); + SpinLock edge_vertices_lock = new SpinLock(); + + int edge_vertex_id(ref Vector3i idx1, ref Vector3i idx2, double f1, double f2) + { + long hash = edge_hash(ref idx1, ref idx2); + + int vid = DMesh3.InvalidID; + bool taken = false; + edge_vertices_lock.Enter(ref taken); + bool found = edge_vertices.TryGetValue(hash, out vid); + edge_vertices_lock.Exit(); + + if (found) + return vid; + + // ok this is a bit messy. We do not want to lock the entire hash table + // while we do find_iso. However it is possible that during this time we + // are unlocked we have re-entered with the same edge. So when we + // re-acquire the lock we need to check again that we have not already + // computed this edge, otherwise we will end up with duplicate vertices! + + Vector3d pa = Vector3d.Zero, pb = Vector3d.Zero; + corner_pos(ref idx1, ref pa); + corner_pos(ref idx2, ref pb); + Vector3d pos = Vector3d.Zero; + find_iso(ref pa, ref pb, f1, f2, ref pos); + + taken = false; + edge_vertices_lock.Enter(ref taken); + if (edge_vertices.TryGetValue(hash, out vid) == false) { + vid = append_vertex(pos); + edge_vertices[hash] = vid; + } + edge_vertices_lock.Exit(); + + return vid; + } + + + + + + + // + // Store corner values in hash table. This doesn't make + // sense if we are evaluating entire grid, way too slow. + // + + Dictionary corner_values = new Dictionary(); + SpinLock corner_values_lock = new SpinLock(); + + double corner_value(ref Vector3i idx) + { + long hash = corner_hash(ref idx); + double value = 0; + + if ( corner_values.TryGetValue(hash, out value) == false) { + Vector3d v = corner_pos(ref idx); + value = Implicit.Value(ref v); + corner_values[hash] = value; + } + return value; + } + void initialize_cell_values(GridCell cell, bool shift) + { + bool taken = false; + corner_values_lock.Enter(ref taken); + + if ( shift ) { + cell.f[1] = corner_value(ref cell.i[1]); + cell.f[2] = corner_value(ref cell.i[2]); + cell.f[5] = corner_value(ref cell.i[5]); + cell.f[6] = corner_value(ref cell.i[6]); + } else { + for (int i = 0; i < 8; ++i) + cell.f[i] = corner_value(ref cell.i[i]); + } + + corner_values_lock.Exit(); + } + + + + // + // store corner values in pre-allocated grid that has + // float.MaxValue as sentinel. + // (note this is float grid, not double...) + // + + DenseGrid3f corner_values_grid; + + double corner_value_grid(ref Vector3i idx) + { + double val = corner_values_grid[idx]; + if (val != float.MaxValue) + return val; + + Vector3d v = corner_pos(ref idx); + val = Implicit.Value(ref v); + corner_values_grid[idx] = (float)val; + return val; + } + void initialize_cell_values_grid(GridCell cell, bool shift) + { + if (shift) { + cell.f[1] = corner_value_grid(ref cell.i[1]); + cell.f[2] = corner_value_grid(ref cell.i[2]); + cell.f[5] = corner_value_grid(ref cell.i[5]); + cell.f[6] = corner_value_grid(ref cell.i[6]); + } else { + for (int i = 0; i < 8; ++i) + cell.f[i] = corner_value_grid(ref cell.i[i]); + } + } + + + + // + // explicitly compute corner values as necessary + // + // + + double corner_value_nohash(ref Vector3i idx) { + Vector3d v = corner_pos(ref idx); + return Implicit.Value(ref v); + } + void initialize_cell_values_nohash(GridCell cell, bool shift) + { + if (shift) { + cell.f[1] = corner_value_nohash(ref cell.i[1]); + cell.f[2] = corner_value_nohash(ref cell.i[2]); + cell.f[5] = corner_value_nohash(ref cell.i[5]); + cell.f[6] = corner_value_nohash(ref cell.i[6]); + } else { + for (int i = 0; i < 8; ++i) + cell.f[i] = corner_value_nohash(ref cell.i[i]); + } + } + + + + + + /// + /// compute 3D corner-positions and field values for cell at index + /// + void initialize_cell(GridCell cell, ref Vector3i idx) + { + cell.i[0] = new Vector3i(idx.x + 0, idx.y + 0, idx.z + 0); + cell.i[1] = new Vector3i(idx.x + 1, idx.y + 0, idx.z + 0); + cell.i[2] = new Vector3i(idx.x + 1, idx.y + 0, idx.z + 1); + cell.i[3] = new Vector3i(idx.x + 0, idx.y + 0, idx.z + 1); + cell.i[4] = new Vector3i(idx.x + 0, idx.y + 1, idx.z + 0); + cell.i[5] = new Vector3i(idx.x + 1, idx.y + 1, idx.z + 0); + cell.i[6] = new Vector3i(idx.x + 1, idx.y + 1, idx.z + 1); + cell.i[7] = new Vector3i(idx.x + 0, idx.y + 1, idx.z + 1); + + //initialize_cell_values(cell, false); + initialize_cell_values_grid(cell, false); + //initialize_cell_values_nohash(cell, false); + } + + + // assume we just want to slide cell at xi-1 to cell at xi, while keeping + // yi and zi constant. Then only x-coords change, and we have already + // computed half the values + void shift_cell_x(GridCell cell, int xi) + { + cell.f[0] = cell.f[1]; + cell.f[3] = cell.f[2]; + cell.f[4] = cell.f[5]; + cell.f[7] = cell.f[6]; + + cell.i[0].x = xi; cell.i[1].x = xi+1; cell.i[2].x = xi+1; cell.i[3].x = xi; + cell.i[4].x = xi; cell.i[5].x = xi+1; cell.i[6].x = xi+1; cell.i[7].x = xi; + + //initialize_cell_values(cell, true); + initialize_cell_values_grid(cell, true); + //initialize_cell_values_nohash(cell, true); + } + + + bool parallel_mesh_access = false; + SpinLock mesh_lock; + + /// + /// processing z-slabs of cells in parallel + /// + void generate_parallel() + { + mesh_lock = new SpinLock(); + parallel_mesh_access = true; + + // [TODO] maybe shouldn't alway use Z axis here? + gParallel.ForEach(Interval1i.Range(CellDimensions.z), (zi) => { + GridCell cell = new GridCell(); + int[] vertlist = new int[12]; + for (int yi = 0; yi < CellDimensions.y; ++yi) { + if (CancelF()) + return; + // compute full cell at x=0, then slide along x row, which saves half of value computes + Vector3i idx = new Vector3i(0, yi, zi); + initialize_cell(cell, ref idx); + polygonize_cell(cell, vertlist); + for (int xi = 1; xi < CellDimensions.x; ++xi) { + shift_cell_x(cell, xi); + polygonize_cell(cell, vertlist); + } + } + }); + + + parallel_mesh_access = false; + } + + + + + /// + /// fully sequential version, no threading + /// + void generate_basic() + { + GridCell cell = new GridCell(); + int[] vertlist = new int[12]; + + for (int zi = 0; zi < CellDimensions.z; ++zi) { + for (int yi = 0; yi < CellDimensions.y; ++yi) { + if (CancelF()) + return; + // compute full cell at x=0, then slide along x row, which saves half of value computes + Vector3i idx = new Vector3i(0, yi, zi); + initialize_cell(cell, ref idx); + polygonize_cell(cell, vertlist); + for (int xi = 1; xi < CellDimensions.x; ++xi) { + shift_cell_x(cell, xi); + polygonize_cell(cell, vertlist); + } + + } + } + } + + + + + /// + /// fully sequential version, no threading + /// + void generate_continuation(IEnumerable seeds) + { + GridCell cell = new GridCell(); + int[] vertlist = new int[12]; + + done_cells = new DenseGrid3i(CellDimensions.x, CellDimensions.y, CellDimensions.z, 0); + + List stack = new List(); + + foreach (Vector3d seed in seeds) { + Vector3i seed_idx = cell_index(seed); + if (done_cells[seed_idx] == 1) + continue; + stack.Add(seed_idx); + done_cells[seed_idx] = 1; + + while ( stack.Count > 0 ) { + Vector3i idx = stack[stack.Count-1]; + stack.RemoveAt(stack.Count-1); + if (CancelF()) + return; + + initialize_cell(cell, ref idx); + if ( polygonize_cell(cell, vertlist) ) { // found crossing + foreach ( Vector3i o in gIndices.GridOffsets6 ) { + Vector3i nbr_idx = idx + o; + if (GridBounds.Contains(nbr_idx) && done_cells[nbr_idx] == 0) { + stack.Add(nbr_idx); + done_cells[nbr_idx] = 1; + } + } + } + } + } + } + + + + + /// + /// parallel seed evaluation + /// + void generate_continuation_parallel(IEnumerable seeds) + { + mesh_lock = new SpinLock(); + parallel_mesh_access = true; + + gParallel.ForEach(seeds, (seed) => { + Vector3i seed_idx = cell_index(seed); + if (set_cell_if_not_done(ref seed_idx) == false) + return; + + GridCell cell = new GridCell(); + int[] vertlist = new int[12]; + + List stack = new List(); + stack.Add(seed_idx); + + while (stack.Count > 0) { + Vector3i idx = stack[stack.Count - 1]; + stack.RemoveAt(stack.Count - 1); + if (CancelF()) + return; + + initialize_cell(cell, ref idx); + if (polygonize_cell(cell, vertlist)) { // found crossing + foreach (Vector3i o in gIndices.GridOffsets6) { + Vector3i nbr_idx = idx + o; + if (GridBounds.Contains(nbr_idx)) { + if (set_cell_if_not_done(ref nbr_idx) == true) { + stack.Add(nbr_idx); + } + } + } + } + } + }); + + parallel_mesh_access = false; + } + + + + DenseGrid3i done_cells; + SpinLock done_cells_lock = new SpinLock(); + + bool set_cell_if_not_done(ref Vector3i idx) + { + bool was_set = false; + bool taken = false; + done_cells_lock.Enter(ref taken); + if (done_cells[idx] == 0) { + done_cells[idx] = 1; + was_set = true; + } + done_cells_lock.Exit(); + return was_set; + } + + + + + + + + + + + /// + /// find edge crossings and generate triangles for this cell + /// + bool polygonize_cell(GridCell cell, int[] vertIndexList) + { + // construct bits of index into edge table, where bit for each + // corner is 1 if that value is < isovalue. + // This tell us which edges have sign-crossings, and the int value + // of the bitmap is an index into the edge and triangle tables + int cubeindex = 0, shift = 1; + for (int i = 0; i < 8; ++i) { + if (cell.f[i] < IsoValue) + cubeindex |= shift; + shift <<= 1; + } + + // no crossings! + if (edgeTable[cubeindex] == 0) + return false; + + // check each bit of value in edge table. If it is 1, we + // have a crossing on that edge. Look up the indices of this + // edge and find the intersection point along it + shift = 1; + Vector3d pa = Vector3d.Zero, pb = Vector3d.Zero; + for (int i = 0; i <= 11; i++) { + if ((edgeTable[cubeindex] & shift) != 0) { + int a = edge_indices[i, 0], b = edge_indices[i, 1]; + vertIndexList[i] = edge_vertex_id(ref cell.i[a], ref cell.i[b], cell.f[a], cell.f[b]); + } + shift <<= 1; + } + + // now iterate through the set of triangles in triTable for this cube, + // and emit triangles using the vertices we found. + int tri_count = 0; + for (int i = 0; triTable[cubeindex, i] != -1; i += 3) { + int ta = triTable[cubeindex, i]; + int tb = triTable[cubeindex, i + 1]; + int tc = triTable[cubeindex, i + 2]; + int a = vertIndexList[ta], b = vertIndexList[tb], c = vertIndexList[tc]; + + // if a corner is within tolerance of isovalue, then some triangles + // will be degenerate, and we can skip them w/o resulting in cracks (right?) + // !! this should never happen anymore...artifact of old hashtable impl + if (a == b || a == c || b == c) + continue; + + /*int tid = */ + append_triangle(a, b, c); + tri_count++; + } + + return (tri_count > 0); + } + + + + + /// + /// add vertex to mesh, with locking if we are computing in parallel + /// + int append_vertex(Vector3d v) + { + bool lock_taken = false; + if (parallel_mesh_access) { + mesh_lock.Enter(ref lock_taken); + } + + int vid = Mesh.AppendVertex(v); + + if (lock_taken) + mesh_lock.Exit(); + + return vid; + } + + + + /// + /// add triangle to mesh, with locking if we are computing in parallel + /// + int append_triangle(int a, int b, int c) + { + bool lock_taken = false; + if (parallel_mesh_access) { + mesh_lock.Enter(ref lock_taken); + } + + int tid = Mesh.AppendTriangle(a, b, c); + + if (lock_taken) + mesh_lock.Exit(); + + return tid; + } + + + + /// + /// root-find the intersection along edge from f(p1)=valp1 to f(p2)=valp2 + /// + void find_iso(ref Vector3d p1, ref Vector3d p2, double valp1, double valp2, ref Vector3d pIso) + { + // Ok, this is a bit hacky but seems to work? If both isovalues + // are the same, we just return the midpoint. If one is nearly zero, we can + // but assume that's where the surface is. *However* if we return that point exactly, + // we can get nonmanifold vertices, because multiple fans may connect there. + // Since DMesh3 disallows that, it results in holes. So we pull + // slightly towards the other point along this edge. This means we will get + // repeated nearly-coincident vertices, but the mesh will be manifold. + const double dt = 0.999999; + if (Math.Abs(valp1 - valp2) < 0.00001) { + pIso = (p1 + p2) * 0.5; + return; + } + if (Math.Abs(IsoValue - valp1) < 0.00001) { + pIso = dt * p1 + (1.0 - dt) * p2; + return; + } + if (Math.Abs(IsoValue - valp2) < 0.00001) { + pIso = (dt) * p2 + (1.0 - dt) * p1; + return; + } + + // [RMS] if we don't maintain min/max order here, then numerical error means + // that hashing on point x/y/z doesn't work + Vector3d a = p1, b = p2; + double fa = valp1, fb = valp2; + if (valp2 < valp1) { + a = p2; b = p1; + fb = valp1; fa = valp2; + } + + // converge on root + if (RootMode == RootfindingModes.Bisection) { + for (int k = 0; k < RootModeSteps; ++k) { + pIso.x = (a.x + b.x) * 0.5; pIso.y = (a.y + b.y) * 0.5; pIso.z = (a.z + b.z) * 0.5; + double mid_f = Implicit.Value(ref pIso); + if (mid_f < IsoValue) { + a = pIso; fa = mid_f; + } else { + b = pIso; fb = mid_f; + } + } + pIso = Vector3d.Lerp(a, b, 0.5); + + } else { + double mu = 0; + if (RootMode == RootfindingModes.LerpSteps) { + for (int k = 0; k < RootModeSteps; ++k) { + mu = (IsoValue - fa) / (fb - fa); + pIso.x = a.x + mu * (b.x - a.x); + pIso.y = a.y + mu * (b.y - a.y); + pIso.z = a.z + mu * (b.z - a.z); + double mid_f = Implicit.Value(ref pIso); + if (mid_f < IsoValue) { + a = pIso; fa = mid_f; + } else { + b = pIso; fb = mid_f; + } + } + } + + // final lerp + mu = (IsoValue - fa) / (fb - fa); + pIso.x = a.x + mu * (b.x - a.x); + pIso.y = a.y + mu * (b.y - a.y); + pIso.z = a.z + mu * (b.z - a.z); + } + } + + + + + /* + * Below here are standard marching-cubes tables. + */ + + + static readonly int[,] edge_indices = new int[,] { + {0,1}, {1,2}, {2,3}, {3,0}, {4,5}, {5,6}, {6,7}, {7,4}, {0,4}, {1,5}, {2,6}, {3,7} + }; + + static readonly int[] edgeTable = new int[256] { + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 }; + + + static readonly int[,] triTable = new int[256, 16] + {{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, + {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, + {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, + {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, + {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, + {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, + {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, + {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, + {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, + {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, + {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, + {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, + {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, + {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, + {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, + {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, + {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, + {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, + {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, + {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, + {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, + {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, + {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, + {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, + {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, + {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, + {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, + {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, + {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, + {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, + {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, + {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, + {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, + {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, + {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, + {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, + {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, + {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, + {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, + {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, + {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, + {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, + {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, + {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, + {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, + {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, + {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, + {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, + {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, + {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, + {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, + {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, + {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, + {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, + {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, + {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, + {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, + {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, + {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, + {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, + {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, + {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, + {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, + {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, + {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, + {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, + {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, + {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, + {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, + {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, + {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, + {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, + {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, + {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, + {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, + {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, + {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, + {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, + {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, + {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, + {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, + {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, + {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, + {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, + {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, + {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, + {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, + {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, + {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, + {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, + {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, + {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, + {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, + {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, + {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, + {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, + {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, + {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, + {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, + {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, + {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, + {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, + {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, + {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, + {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, + {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, + {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, + {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, + {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, + {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, + {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, + {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, + {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, + {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, + {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, + {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, + {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, + {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, + {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, + {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, + {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, + {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, + {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, + {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, + {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, + {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, + {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, + {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, + {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, + {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, + {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, + {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, + {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, + {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, + {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, + {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, + {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, + {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, + {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, + {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, + {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, + {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, + {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, + {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, + {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, + {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, + {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, + {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, + {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, + {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, + {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, + {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, + {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, + {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, + {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, + {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, + {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, + {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, + {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, + {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, + {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, + {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, + {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, + {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}; + + } +} diff --git a/mesh_ops/AutoHoleFill.cs b/mesh_ops/AutoHoleFill.cs new file mode 100644 index 00000000..7dfb8e5a --- /dev/null +++ b/mesh_ops/AutoHoleFill.cs @@ -0,0 +1,364 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// Work in progress. Idea is that this class will analyze the hole and choose correct filling + /// strategy. Mainly just calling other fillers. + /// + /// Also contains prototype of filler that decomposes hole into spans based on normals and + /// then uses PlanarSpansFiller. See comments, is not really functional. + /// + /// + public class AutoHoleFill + { + public DMesh3 Mesh; + + public double TargetEdgeLength = 2.5; + + public EdgeLoop FillLoop; + + /* + * Outputs + */ + + /// Final fill triangles. May include triangles outside initial fill loop, if ConstrainToHoleInterior=false + public int[] FillTriangles; + + + public AutoHoleFill(DMesh3 mesh, EdgeLoop fillLoop) + { + this.Mesh = mesh; + this.FillLoop = fillLoop; + } + + + enum UseFillType + { + PlanarFill, + MinimalFill, + PlanarSpansFill, + SmoothFill + } + + + + public bool Apply() + { + UseFillType type = classify_hole(); + + bool bResult = false; + + bool DISABLE_PLANAR_FILL = false; + + if (type == UseFillType.PlanarFill && DISABLE_PLANAR_FILL == false) + bResult = fill_planar(); + else if (type == UseFillType.MinimalFill) + bResult = fill_minimal(); + else if (type == UseFillType.PlanarSpansFill) + bResult = fill_planar_spans(); + else + bResult = fill_smooth(); + + if (bResult == false && type != UseFillType.SmoothFill) + bResult = fill_smooth(); + + return bResult; + } + + + + + UseFillType classify_hole() + { + return UseFillType.MinimalFill; +#if false + + int NV = FillLoop.VertexCount; + int NE = FillLoop.EdgeCount; + + Vector3d size = FillLoop.ToCurve().GetBoundingBox().Diagonal; + + NormalHistogram hist = new NormalHistogram(4096, true); + + for (int k = 0; k < NE; ++k) { + int eid = FillLoop.Edges[k]; + Index2i et = Mesh.GetEdgeT(eid); + Vector3d n = Mesh.GetTriNormal(et.a); + hist.Count(n, 1.0, true); + } + + if (hist.UsedBins.Count == 1) + return UseFillType.PlanarFill; + + //int nontrivial_bins = 0; + //foreach ( int bin in hist.UsedBins ) { + // if (hist.Counts[bin] > 8) + // nontrivial_bins++; + //} + //if (nontrivial_bins > 0) + // return UseFillType.PlanarSpansFill; + + return UseFillType.SmoothFill; +#endif + } + + + + + bool fill_smooth() + { + SmoothedHoleFill fill = new SmoothedHoleFill(Mesh, FillLoop); + fill.TargetEdgeLength = TargetEdgeLength; + fill.SmoothAlpha = 1.0f; + fill.ConstrainToHoleInterior = true; + //fill.SmoothSolveIterations = 3; // do this if we have a complicated hole - should be able to tell by normal histogram... + return fill.Apply(); + } + + + + bool fill_planar() + { + Vector3d n = Vector3d.Zero, c = Vector3d.Zero; + int NE = FillLoop.EdgeCount; + for (int k = 0; k < NE; ++k) { + int eid = FillLoop.Edges[k]; + n += Mesh.GetTriNormal(Mesh.GetEdgeT(eid).a); + c += Mesh.GetEdgePoint(eid, 0.5); + } + n.Normalize(); c /= (double)NE; + + PlanarHoleFiller filler = new PlanarHoleFiller(Mesh); + filler.FillTargetEdgeLen = TargetEdgeLength; + filler.AddFillLoop(FillLoop); + filler.SetPlane(c, n); + + bool bOK = filler.Fill(); + return bOK; + } + + + + bool fill_minimal() + { + MinimalHoleFill minfill = new MinimalHoleFill(Mesh, FillLoop); + bool bOK = minfill.Apply(); + return bOK; + } + + + + + + /// + /// Here are reasons this isn't working: + /// 1) find_coplanar_span_sets does not actually limit to coplanar (see comments) + /// 2) + /// + /// + bool fill_planar_spans() + { + Dictionary> span_sets = find_coplanar_span_sets(Mesh, FillLoop); + + foreach ( var set in span_sets ) { + Vector3d normal = set.Key; + List spans = set.Value; + Vector3d pos = spans[0].GetVertex(0); + + if (spans.Count > 1) { + List> subset_set = sort_planar_spans(spans, normal); + foreach ( var subset in subset_set) { + if (subset.Count == 1 ) { + PlanarSpansFiller filler = new PlanarSpansFiller(Mesh, subset); + filler.FillTargetEdgeLen = TargetEdgeLength; + filler.SetPlane(pos, normal); + filler.Fill(); + } + } + + } else { + PlanarSpansFiller filler = new PlanarSpansFiller(Mesh, spans); + filler.FillTargetEdgeLen = TargetEdgeLength; + filler.SetPlane(pos, normal); + filler.Fill(); + } + } + + return true; + } + + + /// + /// This function is supposed to take a set of spans in a plane and sort them + /// into regions that can be filled with a polygon. Currently kind of clusters + /// based on intersecting bboxes. Does not work. + /// + /// I think fundamentally it needs to look back at the input mesh, to see what + /// is connected/not-connected. Or possibly use polygon winding number? Need + /// to somehow define what the holes are... + /// + List> sort_planar_spans(List allspans, Vector3d normal) + { + List> result = new List>(); + Frame3f polyFrame = new Frame3f(Vector3d.Zero, normal); + + int N = allspans.Count; + + List plines = new List(); + foreach (EdgeSpan span in allspans) { + plines.Add(to_polyline(span, polyFrame)); + } + + bool[] bad_poly = new bool[N]; + for (int k = 0; k < N; ++k) + bad_poly[k] = false; // self_intersects(plines[k]); + + bool[] used = new bool[N]; + for (int k = 0; k < N; ++k) { + if (used[k]) + continue; + bool is_bad = bad_poly[k]; + AxisAlignedBox2d bounds = plines[k].Bounds; + used[k] = true; + + List set = new List() { k }; + + for ( int j = k+1; j < N; ++j ) { + if (used[j]) + continue; + AxisAlignedBox2d boundsj = plines[j].Bounds; + if ( bounds.Intersects(boundsj) ) { + used[j] = true; + is_bad = is_bad || bad_poly[j]; + bounds.Contain(boundsj); + set.Add(j); + } + } + + if ( is_bad == false ) { + List span_set = new List(); + foreach (int idx in set) + span_set.Add(allspans[idx]); + result.Add(span_set); + } + + } + + return result; + } + PolyLine2d to_polyline(EdgeSpan span, Frame3f polyFrame) + { + int NV = span.VertexCount; + PolyLine2d poly = new PolyLine2d(); + for (int k = 0; k < NV; ++k) + poly.AppendVertex(polyFrame.ToPlaneUV((Vector3f)span.GetVertex(k), 2)); + return poly; + } + Polygon2d to_polygon(EdgeSpan span, Frame3f polyFrame) + { + int NV = span.VertexCount; + Polygon2d poly = new Polygon2d(); + for (int k = 0; k < NV; ++k) + poly.AppendVertex(polyFrame.ToPlaneUV((Vector3f)span.GetVertex(k), 2)); + return poly; + } + bool self_intersects(PolyLine2d poly) + { + Segment2d seg = new Segment2d(poly.Start, poly.End); + int NS = poly.VertexCount - 2; + for ( int i = 1; i < NS; ++i ) { + if (poly.Segment(i).Intersects(ref seg)) + return true; + } + return false; + } + + + + + // NO DOES NOT WORK. DOES NOT FIND EDGE SPANS THAT ARE IN PLANE BUT HAVE DIFFERENT NORMAL! + // NEED TO COLLECT UP SPANS USING NORMAL HISTOGRAM NORMALS! + // ALSO NEED TO ACTUALLY CHECK FOR COPLANARITY, NOT JUST SAME NORMAL!! + + Dictionary> find_coplanar_span_sets(DMesh3 mesh, EdgeLoop loop) + { + double dot_thresh = 0.999; + + Dictionary> span_sets = new Dictionary>(); + + int NV = loop.Vertices.Length; + int NE = loop.Edges.Length; + + Vector3d[] edge_normals = new Vector3d[NE]; + for (int k = 0; k < NE; ++k) + edge_normals[k] = mesh.GetTriNormal(mesh.GetEdgeT(loop.Edges[k]).a); + + // find coplanar verts + // [RMS] this is wrong, if normals vary smoothly enough we will mark non-coplanar spans as coplanar + bool[] vert_coplanar = new bool[NV]; + int nc = 0; + for ( int k = 0; k < NV; ++k ) { + int prev = (k==0) ? NV-1 : k-1; + if (edge_normals[k].Dot(ref edge_normals[prev]) > dot_thresh) { + vert_coplanar[k] = true; + nc++; + } + } + if (nc < 2) + return null; + + int iStart = 0; + while (vert_coplanar[iStart]) + iStart++; + + int iPrev = iStart; + int iCur = iStart+1; + while (iCur != iStart) { + if (vert_coplanar[iCur] == false) { + iPrev = iCur; + iCur = (iCur + 1) % NV; + continue; + } + + List edges = new List() { loop.Edges[iPrev] }; + int span_start_idx = iCur; + while (vert_coplanar[iCur]) { + edges.Add(loop.Edges[iCur]); + iCur = (iCur + 1) % NV; + } + + if ( edges.Count > 1 ) { + Vector3d span_n = edge_normals[span_start_idx]; + EdgeSpan span = EdgeSpan.FromEdges(mesh, edges); + span.CheckValidity(); + foreach ( var pair in span_sets ) { + if ( pair.Key.Dot(ref span_n) > dot_thresh ) { + span_n = pair.Key; + break; + } + } + List found; + if (span_sets.TryGetValue(span_n, out found) == false) + span_sets[span_n] = new List() { span }; + else + found.Add(span); + } + + } + + + + return span_sets; + + } + + + } +} diff --git a/mesh_ops/MergeCoincidentEdges.cs b/mesh_ops/MergeCoincidentEdges.cs new file mode 100644 index 00000000..1d1237cd --- /dev/null +++ b/mesh_ops/MergeCoincidentEdges.cs @@ -0,0 +1,159 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using g3; + +namespace gs +{ + /// + /// Merge coincident edges. + /// + public class MergeCoincidentEdges + { + public DMesh3 Mesh; + + public double MergeDistance = MathUtil.ZeroTolerancef; + + public bool OnlyUniquePairs = false; + + public MergeCoincidentEdges(DMesh3 mesh) + { + Mesh = mesh; + } + + double merge_r2; + + public virtual bool Apply() { + merge_r2 = MergeDistance * MergeDistance; + + // construct hash table for edge midpoints + MeshBoundaryEdgeMidpoints pointset = new MeshBoundaryEdgeMidpoints(this.Mesh); + PointSetHashtable hash = new PointSetHashtable(pointset); + int hashN = 64; + if (Mesh.TriangleCount > 100000) hashN = 128; + if (Mesh.TriangleCount > 1000000) hashN = 256; + hash.Build(hashN); + + Vector3d a = Vector3d.Zero, b = Vector3d.Zero; + Vector3d c = Vector3d.Zero, d = Vector3d.Zero; + + // find edge equivalence sets. First we find all other edges with same + // midpoint, and then we check if endpoints are the same in second loop + int[] buffer = new int[1024]; + List[] EquivSets = new List[Mesh.MaxEdgeID]; + HashSet remaining = new HashSet(); + foreach ( int eid in Mesh.BoundaryEdgeIndices() ) { + Vector3d midpt = Mesh.GetEdgePoint(eid, 0.5); + int N; + while (hash.FindInBall(midpt, MergeDistance, buffer, out N) == false) + buffer = new int[buffer.Length]; + if (N == 1 && buffer[0] != eid) + throw new Exception("MergeCoincidentEdges.Apply: how could this happen?!"); + if (N <= 1) + continue; // unique edge + + Mesh.GetEdgeV(eid, ref a, ref b); + + // if same endpoints, add to equivalence set + List equiv = new List(N - 1); + for (int i = 0; i < N; ++i) { + if (buffer[i] != eid) { + Mesh.GetEdgeV(buffer[i], ref c, ref d); + if ( is_same_edge(ref a, ref b, ref c, ref d)) + equiv.Add(buffer[i]); + } + } + if (equiv.Count > 0) { + EquivSets[eid] = equiv; + remaining.Add(eid); + } + } + + // [TODO] could replace remaining hashset w/ PQ, and use conservative count? + + // add potential duplicate edges to priority queue, sorted by + // number of possible matches. + // [TODO] Does this need to be a PQ? Not updating PQ below anyway... + DynamicPriorityQueue Q = new DynamicPriorityQueue(); + foreach ( int i in remaining ) { + if (OnlyUniquePairs) { + if (EquivSets[i].Count != 1) + continue; + foreach (int j in EquivSets[i]) { + if (EquivSets[j].Count != 1 || EquivSets[j][0] != i) + continue; + } + } + + Q.Enqueue(new DuplicateEdge() { eid = i }, EquivSets[i].Count); + } + + while ( Q.Count > 0 ) { + DuplicateEdge e = Q.Dequeue(); + if (Mesh.IsEdge(e.eid) == false || EquivSets[e.eid] == null || remaining.Contains(e.eid) == false ) + continue; // dealt with this edge already + if (Mesh.IsBoundaryEdge(e.eid) == false) + continue; + + List equiv = EquivSets[e.eid]; + + // find viable match + // [TODO] how to make good decisions here? prefer planarity? + bool merged = false; + int failed = 0; + for (int i = 0; i < equiv.Count && merged == false; ++i ) { + int other_eid = equiv[i]; + if ( Mesh.IsEdge(other_eid) == false || Mesh.IsBoundaryEdge(other_eid) == false ) + continue; + + DMesh3.MergeEdgesInfo info; + MeshResult result = Mesh.MergeEdges(e.eid, other_eid, out info); + if ( result != MeshResult.Ok ) { + equiv.RemoveAt(i); + i--; + + EquivSets[other_eid].Remove(e.eid); + //Q.UpdatePriority(...); // how need ref to queue node to do this...?? + // maybe equiv set is queue node?? + + failed++; + } else { + // ok we merged, other edge is no longer free + merged = true; + EquivSets[other_eid] = null; + remaining.Remove(other_eid); + } + } + + if ( merged ) { + EquivSets[e.eid] = null; + remaining.Remove(e.eid); + } else { + // should we do something else here? doesn't make sense to put + // back into Q, as it should be at the top, right? + EquivSets[e.eid] = null; + remaining.Remove(e.eid); + } + + } + + return true; + } + + + + bool is_same_edge(ref Vector3d a, ref Vector3d b, ref Vector3d c, ref Vector3d d) { + return (a.DistanceSquared(c) < merge_r2 && b.DistanceSquared(d) < merge_r2) || + (a.DistanceSquared(d) < merge_r2 && b.DistanceSquared(c) < merge_r2); + } + + + + class DuplicateEdge : DynamicPriorityQueueNode { + public int eid; + } + + + } +} diff --git a/mesh_ops/MeshAssembly.cs b/mesh_ops/MeshAssembly.cs new file mode 100644 index 00000000..b290b3cd --- /dev/null +++ b/mesh_ops/MeshAssembly.cs @@ -0,0 +1,131 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using g3; + +namespace gs +{ + + /// + /// Given an input mesh, try to decompose it's connected components into + /// parts with some semantics - solids, open meshes, etc. + /// + public class MeshAssembly + { + public DMesh3 SourceMesh; + + // if true, each shell is a separate solid + public bool HasNoVoids = false; + + /* + * Outputs + */ + + public List ClosedSolids; + public List OpenMeshes; + + + public MeshAssembly(DMesh3 sourceMesh) + { + SourceMesh = sourceMesh; + + ClosedSolids = new List(); + OpenMeshes = new List(); + } + + + public void Decompose() + { + process(); + } + + + + void process() + { + DMesh3 useSourceMesh = SourceMesh; + + // try to do simple mesh repairs + if ( useSourceMesh.CachedIsClosed == false ) { + + useSourceMesh = new DMesh3(SourceMesh); + + // [TODO] should remove duplicate triangles here? + RemoveDuplicateTriangles dupes = new RemoveDuplicateTriangles(useSourceMesh); + dupes.Apply(); + + // close cracks + MergeCoincidentEdges merge = new MergeCoincidentEdges(useSourceMesh); + //merge.OnlyUniquePairs = true; + merge.Apply(); + } + + //Util.WriteDebugMesh(useSourceMesh, "c:\\scratch\\__FIRST_MERGE.obj"); + + + DMesh3[] components = MeshConnectedComponents.Separate(useSourceMesh); + + List solidComps = new List(); + + foreach ( DMesh3 mesh in components ) { + + // [TODO] check if this is a mesh w/ cracks, in which case we + // can do other processing? + + bool closed = mesh.CachedIsClosed; + if ( closed == false ) { + OpenMeshes.Add(mesh); + continue; + } + + solidComps.Add(mesh); + } + + + if (solidComps.Count == 0) + return; + if ( solidComps.Count == 1 ) { + ClosedSolids = new List() { solidComps[0] }; + } + + + if (HasNoVoids) { + // each solid is a separate solid + ClosedSolids = process_solids_novoid(solidComps); + } else { + ClosedSolids = process_solids(solidComps); + } + + } + + + + List process_solids(List solid_components) + { + // [TODO] maybe we can have special tags that extract out certain meshes? + + DMesh3 combinedSolid = new DMesh3(SourceMesh.Components | MeshComponents.FaceGroups); + MeshEditor editor = new MeshEditor(combinedSolid); + foreach (DMesh3 solid in solid_components) { + editor.AppendMesh(solid, combinedSolid.AllocateTriangleGroup()); + } + + return new List() { combinedSolid }; + } + + + + List process_solids_novoid(List solid_components) + { + return solid_components; + } + + + + + } +} diff --git a/mesh_ops/MeshAutoRepair.cs b/mesh_ops/MeshAutoRepair.cs new file mode 100644 index 00000000..bc529466 --- /dev/null +++ b/mesh_ops/MeshAutoRepair.cs @@ -0,0 +1,388 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// Mesh Auto Repair top-level driver. + /// + /// TODO: + /// - remove degenerate *faces* (which may still have all edges > length) + /// - this is tricky, in many CAD meshes these faces can't just be collapsed. But can often remove via flipping...? + /// + /// + public class MeshAutoRepair + { + public double RepairTolerance = MathUtil.ZeroTolerancef; + + // assume edges shorter than this are degenerate and should be collapsed + public double MinEdgeLengthTol = 0.0001; + + // number of times we will delete border triangles and try again + public int ErosionIterations = 5; + + + // [TODO] interior components? + public enum RemoveModes { + None = 0, Interior = 1, Occluded = 2 + }; + public RemoveModes RemoveMode = MeshAutoRepair.RemoveModes.None; + + + + + + /// + /// Set this to be able to cancel running remesher + /// + public ProgressCancel Progress = null; + + /// + /// if this returns true, abort computation. + /// + protected virtual bool Cancelled() + { + return (Progress == null) ? false : Progress.Cancelled(); + } + + + + + + public DMesh3 Mesh; + + public MeshAutoRepair(DMesh3 mesh3) + { + Mesh = mesh3; + } + + + public bool Apply() + { + bool do_checks = false; + + if ( do_checks ) Mesh.CheckValidity(); + + + /* + * Remove parts of the mesh we don't want before we bother with anything else + * TODO: maybe we need to repair orientation first? if we want to use MWN... + */ + do_remove_inside(); + if (Cancelled()) return false; + + int repeat_count = 0; + repeat_all: + + /* + * make sure orientation of connected components is consistent + * TODO: what about mobius strip problems? + */ + repair_orientation(false); + if (Cancelled()) return false; + + /* + * Do safe close-cracks to handle easy cases + */ + + repair_cracks(true, RepairTolerance); + if (Mesh.IsClosed()) goto all_done; + if (Cancelled()) return false; + + /* + * Collapse tiny edges and then try easy cases again, and + * then allow for handling of ambiguous cases + */ + + collapse_all_degenerate_edges(RepairTolerance*0.5, true); + if (Cancelled()) return false; + repair_cracks(true, 2*RepairTolerance); + if (Cancelled()) return false; + repair_cracks(false, 2*RepairTolerance); + if (Cancelled()) return false; + if (Mesh.IsClosed()) goto all_done; + + /* + * Possibly we have joined regions with different orientation (is it?), fix that + * TODO: mobius strips again + */ + repair_orientation(false); + if (Cancelled()) return false; + + if (do_checks) Mesh.CheckValidity(); + + // get rid of any remaining single-triangles before we start filling holes + remove_loners(); + + /* + * Ok, fill simple holes. + */ + int nRemainingBowties = 0; + int nHoles; bool bSawSpans; + fill_trivial_holes(out nHoles, out bSawSpans); + if (Cancelled()) return false; + if (Mesh.IsClosed()) goto all_done; + + /* + * Now fill harder holes. If we saw spans, that means boundary loops could + * not be resolved in some cases, do we disconnect bowties and try again. + */ + fill_any_holes(out nHoles, out bSawSpans); + if (Cancelled()) return false; + if (bSawSpans) { + disconnect_bowties(out nRemainingBowties); + fill_any_holes(out nHoles, out bSawSpans); + } + if (Cancelled()) return false; + if (Mesh.IsClosed()) goto all_done; + + /* + * We may have a closed mesh now but it might still have bowties (eg + * tetrahedra sharing vtx case). So disconnect those. + */ + disconnect_bowties(out nRemainingBowties); + if (Cancelled()) return false; + + /* + * If the mesh is not closed, we will do one more round to try again. + */ + if (repeat_count == 0 && Mesh.IsClosed() == false) { + repeat_count++; + goto repeat_all; + } + + /* + * Ok, we didn't get anywhere on our first repeat. If we are still not + * closed, we will try deleting boundary triangles and repeating. + * Repeat this N times. + */ + if ( repeat_count <= ErosionIterations && Mesh.IsClosed() == false) { + repeat_count++; + MeshFaceSelection bdry_faces = new MeshFaceSelection(Mesh); + foreach (int eid in MeshIterators.BoundaryEdges(Mesh)) + bdry_faces.SelectEdgeTris(eid); + MeshEditor.RemoveTriangles(Mesh, bdry_faces, true); + goto repeat_all; + } + + all_done: + + /* + * Remove tiny edges + */ + if (MinEdgeLengthTol > 0) { + collapse_all_degenerate_edges(MinEdgeLengthTol, false); + } + if (Cancelled()) return false; + + /* + * finally do global orientation + */ + repair_orientation(true); + if (Cancelled()) return false; + + if (do_checks) Mesh.CheckValidity(); + + /* + * Might as well compact output mesh... + */ + Mesh = new DMesh3(Mesh, true); + MeshNormals.QuickCompute(Mesh); + + return true; + } + + + + + void fill_trivial_holes(out int nRemaining, out bool saw_spans) + { + MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); + nRemaining = 0; + saw_spans = loops.SawOpenSpans; + + foreach (var loop in loops) { + if (Cancelled()) break; + bool filled = false; + if (loop.VertexCount == 3) { + SimpleHoleFiller filler = new SimpleHoleFiller(Mesh, loop); + filled = filler.Fill(); + } else if ( loop.VertexCount == 4 ) { + MinimalHoleFill filler = new MinimalHoleFill(Mesh, loop); + filled = filler.Apply(); + if (filled == false) { + SimpleHoleFiller fallback = new SimpleHoleFiller(Mesh, loop); + filled = fallback.Fill(); + } + } + + if (filled == false) + ++nRemaining; + } + } + + + + void fill_any_holes(out int nRemaining, out bool saw_spans) + { + MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); + nRemaining = 0; + saw_spans = loops.SawOpenSpans; + + foreach (var loop in loops) { + if (Cancelled()) break; + MinimalHoleFill filler = new MinimalHoleFill(Mesh, loop); + bool filled = filler.Apply(); + if (filled == false) { + if (Cancelled()) break; + SimpleHoleFiller fallback = new SimpleHoleFiller(Mesh, loop); + filled = fallback.Fill(); + } + } + } + + + + + bool repair_cracks(bool bUniqueOnly, double mergeDist) + { + try { + MergeCoincidentEdges merge = new MergeCoincidentEdges(Mesh); + merge.OnlyUniquePairs = bUniqueOnly; + merge.MergeDistance = mergeDist; + return merge.Apply(); + } catch (Exception /*e*/) { + // ?? + return false; + } + } + + + + bool remove_duplicate_faces(double vtxTolerance, out int nRemoved) + { + nRemoved = 0; + try { + RemoveDuplicateTriangles dupe = new RemoveDuplicateTriangles(Mesh); + dupe.VertexTolerance = vtxTolerance; + bool bOK = dupe.Apply(); + nRemoved = dupe.Removed; + return bOK; + + } catch (Exception/*e*/) { + return false; + } + } + + + + bool collapse_degenerate_edges( + double minLength, bool bBoundaryOnly, + out int collapseCount) + { + collapseCount = 0; + // don't iterate sequentially because there may be pathological cases + foreach (int eid in MathUtil.ModuloIteration(Mesh.MaxEdgeID)) { + if (Cancelled()) break; + if (Mesh.IsEdge(eid) == false) + continue; + bool is_boundary_edge = Mesh.IsBoundaryEdge(eid); + if (bBoundaryOnly && is_boundary_edge == false) + continue; + Index2i ev = Mesh.GetEdgeV(eid); + Vector3d a = Mesh.GetVertex(ev.a), b = Mesh.GetVertex(ev.b); + if (a.Distance(b) < minLength) { + int keep = Mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b; + int discard = (keep == ev.a) ? ev.b : ev.a; + DMesh3.EdgeCollapseInfo collapseInfo; + MeshResult result = Mesh.CollapseEdge(keep, discard, out collapseInfo); + if (result == MeshResult.Ok) { + ++collapseCount; + if (Mesh.IsBoundaryVertex(keep) == false || is_boundary_edge) + Mesh.SetVertex(keep, (a + b) * 0.5); + } + } + } + return true; + } + bool collapse_all_degenerate_edges(double minLength, bool bBoundaryOnly) + { + bool repeat = true; + while (repeat) { + if (Cancelled()) break; + int collapse_count; + collapse_degenerate_edges(minLength, bBoundaryOnly, out collapse_count); + if (collapse_count == 0) + repeat = false; + } + return true; + } + + + + + bool disconnect_bowties(out int nRemaining) + { + MeshEditor editor = new MeshEditor(Mesh); + nRemaining = editor.DisconnectAllBowties(); + return true; + } + + + void repair_orientation(bool bGlobal) + { + MeshRepairOrientation orient = new MeshRepairOrientation(Mesh); + orient.OrientComponents(); + if (Cancelled()) return; + if (bGlobal) + orient.SolveGlobalOrientation(); + } + + + + + bool remove_interior(out int nRemoved) + { + RemoveOccludedTriangles remove = new RemoveOccludedTriangles(Mesh); + remove.PerVertex = true; + remove.InsideMode = RemoveOccludedTriangles.CalculationMode.FastWindingNumber; + remove.Apply(); + nRemoved = remove.RemovedT.Count(); + return true; + } + bool remove_occluded(out int nRemoved) + { + RemoveOccludedTriangles remove = new RemoveOccludedTriangles(Mesh); + remove.PerVertex = true; + remove.InsideMode = RemoveOccludedTriangles.CalculationMode.SimpleOcclusionTest; + remove.Apply(); + nRemoved = remove.RemovedT.Count(); + return true; + } + bool do_remove_inside() + { + int nRemoved = 0; + if (RemoveMode == RemoveModes.Interior) { + return remove_interior(out nRemoved); + } else if (RemoveMode == RemoveModes.Occluded) { + return remove_occluded(out nRemoved); + } + return true; + } + + + + bool remove_loners() + { + bool bOK = MeshEditor.RemoveIsolatedTriangles(Mesh); + return true; + } + + + } +} diff --git a/mesh_ops/MeshBoolean.cs b/mesh_ops/MeshBoolean.cs new file mode 100644 index 00000000..6519bb19 --- /dev/null +++ b/mesh_ops/MeshBoolean.cs @@ -0,0 +1,152 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace g3 +{ + public class MeshBoolean + { + public DMesh3 Target; + public DMesh3 Tool; + + // points within this tolerance are merged + public double VertexSnapTol = 0.00001; + + public DMesh3 Result; + + MeshMeshCut cutTargetOp; + MeshMeshCut cutToolOp; + + DMesh3 cutTargetMesh; + DMesh3 cutToolMesh; + + public bool Compute() + { + // Alternate strategy: + // - don't do RemoveContained + // - match embedded vertices, split where possible + // - find min-cut path through shared edges + // - remove contiguous patches that are inside both/etc (use MWN) + // ** no good for coplanar regions... + + + cutTargetOp = new MeshMeshCut() { + Target = new DMesh3(Target), + CutMesh = Tool, + VertexSnapTol = VertexSnapTol + }; + cutTargetOp.Compute(); + cutTargetOp.RemoveContained(); + cutTargetMesh = cutTargetOp.Target; + + cutToolOp = new MeshMeshCut() { + Target = new DMesh3(Tool), + CutMesh = Target, + VertexSnapTol = VertexSnapTol + }; + cutToolOp.Compute(); + cutToolOp.RemoveContained(); + cutToolMesh = cutToolOp.Target; + + resolve_vtx_pairs(); + + Result = cutToolMesh; + MeshEditor.Append(Result, cutTargetMesh); + + return true; + } + + + + + + + + void resolve_vtx_pairs() + { + //HashSet targetVerts = new HashSet(cutTargetOp.CutVertices); + //HashSet toolVerts = new HashSet(cutToolOp.CutVertices); + + // tracking on-cut vertices is not working yet... + Util.gDevAssert(Target.IsClosed() && Tool.IsClosed()); + + HashSet targetVerts = new HashSet(MeshIterators.BoundaryVertices(cutTargetMesh)); + HashSet toolVerts = new HashSet(MeshIterators.BoundaryVertices(cutToolMesh)); + + split_missing(cutTargetOp, cutToolOp, cutTargetMesh, cutToolMesh, targetVerts, toolVerts); + split_missing(cutToolOp, cutTargetOp, cutToolMesh, cutTargetMesh, toolVerts, targetVerts); + } + + + void split_missing(MeshMeshCut fromOp, MeshMeshCut toOp, + DMesh3 fromMesh, DMesh3 toMesh, + HashSet fromVerts, HashSet toVerts) + { + List missing = new List(); + foreach (int vid in fromVerts) { + Vector3d v = fromMesh.GetVertex(vid); + int near_vid = find_nearest_vertex(toMesh, v, toVerts); + if (near_vid == DMesh3.InvalidID ) + missing.Add(vid); + } + + foreach (int vid in missing) { + Vector3d v = fromMesh.GetVertex(vid); + int near_eid = find_nearest_edge(toMesh, v, toVerts); + if ( near_eid == DMesh3.InvalidID) { + System.Console.WriteLine("could not find edge to split?"); + continue; + } + + DMesh3.EdgeSplitInfo splitInfo; + MeshResult result = toMesh.SplitEdge(near_eid, out splitInfo); + if ( result != MeshResult.Ok ) { + System.Console.WriteLine("edge split failed"); + continue; + } + + toMesh.SetVertex(splitInfo.vNew, v); + toVerts.Add(splitInfo.vNew); + } + } + + + + int find_nearest_vertex(DMesh3 mesh, Vector3d v, HashSet vertices) + { + int near_vid = DMesh3.InvalidID; + double nearSqr = VertexSnapTol * VertexSnapTol; + foreach ( int vid in vertices ) { + double dSqr = mesh.GetVertex(vid).DistanceSquared(ref v); + if ( dSqr < nearSqr ) { + near_vid = vid; + nearSqr = dSqr; + } + } + return near_vid; + } + + int find_nearest_edge(DMesh3 mesh, Vector3d v, HashSet vertices) + { + int near_eid = DMesh3.InvalidID; + double nearSqr = VertexSnapTol * VertexSnapTol; + foreach ( int eid in mesh.BoundaryEdgeIndices() ) { + Index2i ev = mesh.GetEdgeV(eid); + if (vertices.Contains(ev.a) == false || vertices.Contains(ev.b) == false) + continue; + Segment3d seg = new Segment3d(mesh.GetVertex(ev.a), mesh.GetVertex(ev.b)); + double dSqr = seg.DistanceSquared(v); + if (dSqr < nearSqr) { + near_eid = eid; + nearSqr = dSqr; + } + } + return near_eid; + } + + } +} diff --git a/mesh_ops/MeshInsertProjectedPolygon.cs b/mesh_ops/MeshInsertProjectedPolygon.cs new file mode 100644 index 00000000..5b834ae7 --- /dev/null +++ b/mesh_ops/MeshInsertProjectedPolygon.cs @@ -0,0 +1,260 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// Inserts a polygon into a mesh using a planar projection. You provide a + /// projection frame and either the polygon in the frame's XY-coordinate system, + /// or a DCurve3 space curve that will be projected. + /// + /// Currently you must also provide a seed triangle, that intersects the curve. + /// We flood-fill from the vertices of that triangle to find the interior vertices, + /// and hence the set of faces that are modified. + /// + /// The insertion operation splits the existing mesh edges, so the inserted polygon + /// will have more segments than the input polygon, in general. If you set + /// SimplifyInsertion = true, then we collapse these extra edges, so you (should) + /// get back an edge loop with the same number of vertices. However, on a non-planar + /// mesh this means the edges will no longer lie on the input surface. + /// + /// If RemovePolygonInterior = true, the faces inside the polygon are deleted + /// + /// returns: + /// ModifiedRegion: this is the RegionOperator created to subset the mesh for editing. + /// You can use this to access the modified mesh + /// + /// InsertedPolygonVerts: the output vertex ID for Polygon[i]. This *does not* + /// include the intermediate vertices, it's a 1-1 correspondence. + /// + /// InsertedLoop: inserted edge loop on output mesh + /// + /// InteriorTriangles: the triangles inside the polygon, null if RemovePolygonInterior=true + /// + /// + /// If you would like to change the behavior after the insertion is computed, you can + /// subclass and override BackPropagate(). + /// + /// + /// [TODO] currently we construct a planar BVTree (but 3D) to map the new vertices to + /// 3D via barycentric interpolation. However we could do this inline. MeshInsertUVPolyCurve + /// needs to fully support working on separate coordinate set (it tries via Get/Set PointF, but + /// it is not 100% working), and it needs to let client know about poke and split events, w/ + /// bary-coords, so that we can compute the new 3D positions. + /// + /// + public class MeshInsertProjectedPolygon + { + public DMesh3 Mesh; + public int SeedTriangle = -1; // you must provide this so that we can efficiently + // find region of mesh to insert into + public Frame3f ProjectFrame; // assumption is that Z is plane normal + + // if true, we call Simply() on the inserted UV-curve, which means the + // resulting insertion should have as many vertices as Polygon, and + // the InsertedPolygonVerts list should be verts of a valid edge-loop + public bool SimplifyInsertion = true; + + // if true, we delete triangles on polygon interior + public bool RemovePolygonInterior = true; + + + // internally a RegionOperator is constructed and the insertion is done + // on a submesh. This is that submesh, provided for your convenience + public RegionOperator ModifiedRegion; + + // vertex IDs of inserted polygon vertices in output mesh. + public int[] InsertedPolygonVerts; + + // inserted edge loop + public EdgeLoop InsertedLoop; + + // set of triangles inside polygon. null if RemovePolgonInterior = true + public int[] InteriorTriangles; + + // inserted polygon, in case you did not save a reference + public Polygon2d Polygon; + + + + /// + /// insert polygon in given frame + /// + public MeshInsertProjectedPolygon(DMesh3 mesh, Polygon2d poly, Frame3f frame, int seedTri) + { + Mesh = mesh; + Polygon = new Polygon2d(poly); + ProjectFrame = frame; + SeedTriangle = seedTri; + } + + /// + /// create Polygon by projecting polygon3 into frame + /// + public MeshInsertProjectedPolygon(DMesh3 mesh, DCurve3 polygon3, Frame3f frame, int seedTri ) + { + if (polygon3.Closed == false) + throw new Exception("MeshInsertPolyCurve(): only closed polygon3 supported for now"); + + Mesh = mesh; + ProjectFrame = frame; + SeedTriangle = seedTri; + + Polygon = new Polygon2d(); + foreach (Vector3d v3 in polygon3.Vertices) { + Vector2f uv = frame.ToPlaneUV((Vector3f)v3, 2); + Polygon.AppendVertex(uv); + } + } + + + public virtual ValidationStatus Validate() + { + if (Mesh.IsTriangle(SeedTriangle) == false) + return ValidationStatus.NotATriangle; + + return ValidationStatus.Ok; + } + + + public bool Insert() + { + Func is_contained_v = (vid) => { + Vector3d v = Mesh.GetVertex(vid); + Vector2f vf2 = ProjectFrame.ToPlaneUV((Vector3f)v, 2); + return Polygon.Contains(vf2); + }; + + MeshVertexSelection vertexROI = new MeshVertexSelection(Mesh); + Index3i seedT = Mesh.GetTriangle(SeedTriangle); + + // if a seed vert of seed triangle is containd in polygon, we will + // flood-fill out from there, this gives a better ROI. + // If not, we will try flood-fill from the seed triangles. + List seed_verts = new List(); + for ( int j = 0; j < 3; ++j ) { + if ( is_contained_v(seedT[j]) ) + seed_verts.Add(seedT[j]); + } + if (seed_verts.Count == 0) { + seed_verts.Add(seedT.a); + seed_verts.Add(seedT.b); + seed_verts.Add(seedT.c); + } + + // flood-fill out from seed vertices until we have found all vertices + // contained in polygon + vertexROI.FloodFill(seed_verts.ToArray(), is_contained_v); + + // convert vertex ROI to face ROI + MeshFaceSelection faceROI = new MeshFaceSelection(Mesh, vertexROI, 1); + faceROI.ExpandToOneRingNeighbours(); + faceROI.FillEars(true); // this might be a good idea... + + // construct submesh + RegionOperator regionOp = new RegionOperator(Mesh, faceROI); + DSubmesh3 roiSubmesh = regionOp.Region; + DMesh3 roiMesh = roiSubmesh.SubMesh; + + // save 3D positions of unmodified mesh + Vector3d[] initialPositions = new Vector3d[roiMesh.MaxVertexID]; + + // map roi mesh to plane + MeshTransforms.PerVertexTransform(roiMesh, roiMesh.VertexIndices(), (v, vid) => { + Vector2f uv = ProjectFrame.ToPlaneUV((Vector3f)v, 2); + initialPositions[vid] = v; + return new Vector3d(uv.x, uv.y, 0); + }); + + // save a copy of 2D mesh and construct bvtree. we will use + // this later to project back to 3d + // [TODO] can we use a better spatial DS here, that takes advantage of 2D? + DMesh3 projectMesh = new DMesh3(roiMesh); + DMeshAABBTree3 projecter = new DMeshAABBTree3(projectMesh, true); + + MeshInsertUVPolyCurve insertUV = new MeshInsertUVPolyCurve(roiMesh, Polygon); + //insertUV.Validate() + bool bOK = insertUV.Apply(); + if (!bOK) + throw new Exception("insertUV.Apply() failed"); + + if ( SimplifyInsertion ) + insertUV.Simplify(); + + int[] insertedPolyVerts = insertUV.CurveVertices; + + // grab inserted loop, assuming it worked + EdgeLoop insertedLoop = null; + if ( insertUV.Loops.Count == 1 ) { + insertedLoop = insertUV.Loops[0]; + } + + // find interior triangles + List interiorT = new List(); + foreach (int tid in roiMesh.TriangleIndices()) { + Vector3d centroid = roiMesh.GetTriCentroid(tid); + if (Polygon.Contains(centroid.xy)) + interiorT.Add(tid); + } + if (RemovePolygonInterior) { + MeshEditor editor = new MeshEditor(roiMesh); + editor.RemoveTriangles(interiorT, true); + InteriorTriangles = null; + } else { + InteriorTriangles = interiorT.ToArray(); + } + + + // map back to 3d + Vector3d a = Vector3d.Zero, b = Vector3d.Zero, c = Vector3d.Zero; + foreach ( int vid in roiMesh.VertexIndices() ) { + + // [TODO] somehow re-use exact positions from regionOp maps? + + // construct new 3D pos w/ barycentric interpolation + Vector3d v = roiMesh.GetVertex(vid); + int tid = projecter.FindNearestTriangle(v); + Index3i tri = projectMesh.GetTriangle(tid); + projectMesh.GetTriVertices(tid, ref a, ref b, ref c); + Vector3d bary = MathUtil.BarycentricCoords(ref v, ref a, ref b, ref c); + Vector3d pos = bary.x * initialPositions[tri.a] + bary.y * initialPositions[tri.b] + bary.z * initialPositions[tri.c]; + + roiMesh.SetVertex(vid, pos); + } + + bOK = BackPropagate(regionOp, insertedPolyVerts, insertedLoop); + + return bOK; + } + + + + protected virtual bool BackPropagate(RegionOperator regionOp, int[] insertedPolyVerts, EdgeLoop insertedLoop) + { + bool bOK = regionOp.BackPropropagate(); + if (bOK) { + ModifiedRegion = regionOp; + + IndexUtil.Apply(insertedPolyVerts, regionOp.ReinsertSubToBaseMapV); + InsertedPolygonVerts = insertedPolyVerts; + + if (insertedLoop != null) { + InsertedLoop = MeshIndexUtil.MapLoopViaVertexMap(regionOp.ReinsertSubToBaseMapV, + regionOp.Region.SubMesh, regionOp.Region.BaseMesh, insertedLoop); + if (RemovePolygonInterior) + InsertedLoop.CorrectOrientation(); + } + } + return bOK; + } + + + + + } +} diff --git a/mesh_ops/MeshMeshCut.cs b/mesh_ops/MeshMeshCut.cs new file mode 100644 index 00000000..f00a0035 --- /dev/null +++ b/mesh_ops/MeshMeshCut.cs @@ -0,0 +1,664 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace g3 +{ + /// + /// + /// + /// TODO: + /// - track descendant triangles of each input face + /// - for missing segments, can resolve in 2D in plane of face + /// + /// + /// + public class MeshMeshCut + { + public DMesh3 Target; + public DMesh3 CutMesh; + + PointHashGrid3d PointHash; + + // points within this tolerance are merged + public double VertexSnapTol = 0.00001; + + // List of vertices in output Target that are on the + // cut path, after calling RemoveContained. + // TODO: still missing some vertices?? + public List CutVertices; + + + public void Compute() + { + double cellSize = Target.CachedBounds.MaxDim / 64; + PointHash = new PointHashGrid3d(cellSize, -1); + + // insert target vertices into hash + foreach ( int vid in Target.VertexIndices()) { + Vector3d v = Target.GetVertex(vid); + int existing = find_existing_vertex(v); + if (existing != -1) + System.Console.WriteLine("VERTEX {0} IS DUPLICATE OF {1}!", vid, existing); + PointHash.InsertPointUnsafe(vid, v); + } + + initialize(); + find_segments(); + insert_face_vertices(); + insert_edge_vertices(); + connect_edges(); + + // SegmentInsertVertices was constructed by planar polygon + // insertions in MeshInsertUVPolyCurve calls, but we also + // need to the segment vertices + foreach (SegmentVtx sv in SegVertices) + SegmentInsertVertices.Add(sv.vtx_id); + } + + + public void RemoveContained() + { + DMeshAABBTree3 spatial = new DMeshAABBTree3(CutMesh, true); + spatial.WindingNumber(Vector3d.Zero); + SafeListBuilder removeT = new SafeListBuilder(); + gParallel.ForEach(Target.TriangleIndices(), (tid) => { + Vector3d v = Target.GetTriCentroid(tid); + if (spatial.WindingNumber(v) > 0.9) + removeT.SafeAdd(tid); + }); + MeshEditor.RemoveTriangles(Target, removeT.Result); + + // [RMS] construct set of on-cut vertices? This is not + // necessarily all boundary vertices... + CutVertices = new List(); + foreach (int vid in SegmentInsertVertices) { + if (Target.IsVertex(vid)) + CutVertices.Add(vid); + } + } + + public void AppendSegments(double r) + { + foreach ( var seg in Segments ) { + Segment3d s = new Segment3d(seg.v0.v, seg.v1.v); + if ( Target.FindEdge(seg.v0.vtx_id, seg.v1.vtx_id) == DMesh3.InvalidID ) + MeshEditor.AppendLine(Target, s, (float)r); + } + } + + public void ColorFaces() + { + int counter = 1; + Dictionary gidmap = new Dictionary(); + foreach (var key in SubFaces.Keys) + gidmap[key] = counter++; + Target.EnableTriangleGroups(0); + foreach ( int tid in Target.TriangleIndices() ) { + if (ParentFaces.ContainsKey(tid)) + Target.SetTriangleGroup(tid, gidmap[ParentFaces[tid]]); + else if (SubFaces.ContainsKey(tid)) + Target.SetTriangleGroup(tid, gidmap[tid]); + } + } + + + class SegmentVtx + { + public Vector3d v; + public int type = -1; + public int initial_type = -1; + public int vtx_id = DMesh3.InvalidID; + public int elem_id = DMesh3.InvalidID; + } + List SegVertices; + Dictionary VIDToSegVtxMap; + + + // segment vertices in each triangle that we still have to insert + Dictionary> FaceVertices; + + // segment vertices in each edge that we still have to insert + Dictionary> EdgeVertices; + + + class IntersectSegment + { + public int base_tid; + public SegmentVtx v0; + public SegmentVtx v1; + public SegmentVtx this[int key] { + get { return (key == 0) ? v0 : v1; } + set { if (key == 0) v0 = value; else v1 = value; } + } + } + IntersectSegment[] Segments; + + Vector3d[] BaseFaceCentroids; + Vector3d[] BaseFaceNormals; + Dictionary> SubFaces; + Dictionary ParentFaces; + + HashSet SegmentInsertVertices; + + void initialize() + { + BaseFaceCentroids = new Vector3d[Target.MaxTriangleID]; + BaseFaceNormals = new Vector3d[Target.MaxTriangleID]; + double area = 0; + foreach (int tid in Target.TriangleIndices()) + Target.GetTriInfo(tid, out BaseFaceNormals[tid], out area, out BaseFaceCentroids[tid]); + + // allocate internals + SegVertices = new List(); + EdgeVertices = new Dictionary>(); + FaceVertices = new Dictionary>(); + SubFaces = new Dictionary>(); + ParentFaces = new Dictionary(); + SegmentInsertVertices = new HashSet(); + VIDToSegVtxMap = new Dictionary(); + } + + + + /// + /// 1) Find intersection segments + /// 2) sort onto existing input mesh vtx/edge/face + /// + void find_segments() + { + Dictionary SegVtxMap = new Dictionary(); + + // find intersection segments + // TODO: intersection polygons + // TODO: do we need to care about intersection vertices? + DMeshAABBTree3 targetSpatial = new DMeshAABBTree3(Target, true); + DMeshAABBTree3 cutSpatial = new DMeshAABBTree3(CutMesh, true); + var intersections = targetSpatial.FindAllIntersections(cutSpatial); + + // for each segment, for each vtx, determine if it is + // at an existing vertex, on-edge, or in-face + Segments = new IntersectSegment[intersections.Segments.Count]; + for ( int i = 0; i < Segments.Length; ++i ) { + var isect = intersections.Segments[i]; + Vector3dTuple2 points = new Vector3dTuple2(isect.point0, isect.point1); + IntersectSegment iseg = new IntersectSegment() { + base_tid = isect.t0 + }; + Segments[i] = iseg; + for (int j = 0; j < 2; ++j) { + Vector3d v = points[j]; + + // if this exact vtx coord has been seen, use same vtx + SegmentVtx sv; + if (SegVtxMap.TryGetValue(v, out sv)) { + iseg[j] = sv; + continue; + } + sv = new SegmentVtx() { v = v }; + SegVertices.Add(sv); + SegVtxMap[v] = sv; + iseg[j] = sv; + + // this vtx is tol-equal to input mesh vtx + int existing_v = find_existing_vertex(isect.point0); + if (existing_v >= 0) { + sv.initial_type = sv.type = 0; + sv.elem_id = existing_v; + sv.vtx_id = existing_v; + VIDToSegVtxMap[sv.vtx_id] = sv; + continue; + } + + Triangle3d tri = new Triangle3d(); + Target.GetTriVertices(isect.t0, ref tri.V0, ref tri.V1, ref tri.V2); + Index3i tv = Target.GetTriangle(isect.t0); + + // this vtx is tol-on input mesh edge + int on_edge_i = on_edge(ref tri, ref v); + if ( on_edge_i >= 0 ) { + sv.initial_type = sv.type = 1; + sv.elem_id = Target.FindEdge(tv[on_edge_i], tv[(on_edge_i+1)%3]); + Util.gDevAssert(sv.elem_id != DMesh3.InvalidID); + add_edge_vtx(sv.elem_id, sv); + continue; + } + + // otherwise contained in input mesh face + sv.initial_type = sv.type = 2; + sv.elem_id = isect.t0; + add_face_vtx(sv.elem_id, sv); + } + + } + + } + + + + + /// + /// For each on-face vtx, we poke the face, and re-sort + /// the remaining vertices on that face onto new faces/edges + /// + void insert_face_vertices() + { + while ( FaceVertices.Count > 0 ) { + var pair = FaceVertices.First(); + int tid = pair.Key; + List triVerts = pair.Value; + SegmentVtx v = triVerts[triVerts.Count-1]; + triVerts.RemoveAt(triVerts.Count-1); + + DMesh3.PokeTriangleInfo pokeInfo; + MeshResult result = Target.PokeTriangle(tid, out pokeInfo); + if (result != MeshResult.Ok) + throw new Exception("shit"); + int new_v = pokeInfo.new_vid; + + Target.SetVertex(new_v, v.v); + v.vtx_id = new_v; + VIDToSegVtxMap[v.vtx_id] = v; + PointHash.InsertPoint(v.vtx_id, v.v); + + // remove this triangles vtx list because it is no longer valid + FaceVertices.Remove(tid); + + // update remaining verts + Index3i pokeEdges = pokeInfo.new_edges; + Index3i pokeTris = new Index3i(tid, pokeInfo.new_t1, pokeInfo.new_t2); + foreach ( SegmentVtx sv in triVerts ) { + update_from_poke(sv, pokeEdges, pokeTris); + if (sv.type == 1) + add_edge_vtx(sv.elem_id, sv); + else if (sv.type == 2) + add_face_vtx(sv.elem_id, sv); + } + + // track poke subfaces + add_poke_subfaces(tid, ref pokeInfo); + } + } + + + + /// + /// figure out which vtx/edge/face the input vtx is on + /// + void update_from_poke(SegmentVtx sv, Index3i pokeEdges, Index3i pokeTris) + { + // check if within tolerance of existing vtx, because we did not + // sort that out before... + int existing_v = find_existing_vertex(sv.v); + if (existing_v >= 0) { + sv.type = 0; + sv.elem_id = existing_v; + sv.vtx_id = existing_v; + VIDToSegVtxMap[sv.vtx_id] = sv; + return; + } + + for ( int j = 0; j < 3; ++j ) { + if ( is_on_edge(pokeEdges[j], sv.v) ) { + sv.type = 1; + sv.elem_id = pokeEdges[j]; + return; + } + } + + // [TODO] should use PrimalQuery2d for this! + for ( int j = 0; j < 3; ++j ) { + if ( is_in_triangle(pokeTris[j], sv.v) ) { + sv.type = 2; + sv.elem_id = pokeTris[j]; + return; + } + } + + System.Console.WriteLine("unsorted vertex!"); + sv.elem_id = pokeTris.a; + } + + + + + /// + /// for each on-edge vtx, we split the edge and then + /// re-sort any of the vertices on that edge onto new edges + /// + void insert_edge_vertices() + { + while (EdgeVertices.Count > 0) { + var pair = EdgeVertices.First(); + int eid = pair.Key; + List edgeVerts = pair.Value; + SegmentVtx v = edgeVerts[edgeVerts.Count - 1]; + edgeVerts.RemoveAt(edgeVerts.Count - 1); + + Index2i splitTris = Target.GetEdgeT(eid); + + DMesh3.EdgeSplitInfo splitInfo; + MeshResult result = Target.SplitEdge(eid, out splitInfo); + if (result != MeshResult.Ok) + throw new Exception("insert_edge_vertices: split failed!"); + int new_v = splitInfo.vNew; + Index2i splitEdges = new Index2i(eid, splitInfo.eNewBN); + + Target.SetVertex(new_v, v.v); + v.vtx_id = new_v; + VIDToSegVtxMap[v.vtx_id] = v; + PointHash.InsertPoint(v.vtx_id, v.v); + + // remove this triangles vtx list because it is no longer valid + EdgeVertices.Remove(eid); + + // update remaining verts + foreach (SegmentVtx sv in edgeVerts) { + update_from_split(sv, splitEdges); + if (sv.type == 1) + add_edge_vtx(sv.elem_id, sv); + } + + // track subfaces + add_split_subfaces(splitTris, ref splitInfo); + + } + } + + + + /// + /// figure out which vtx/edge the input vtx is on + /// + void update_from_split(SegmentVtx sv, Index2i splitEdges) + { + // check if within tolerance of existing vtx, because we did not + // sort that out before... + int existing_v = find_existing_vertex(sv.v); + if (existing_v >= 0) { + sv.type = 0; + sv.elem_id = existing_v; + sv.vtx_id = existing_v; + VIDToSegVtxMap[sv.vtx_id] = sv; + return; + } + + for (int j = 0; j < 2; ++j) { + if (is_on_edge(splitEdges[j], sv.v)) { + sv.type = 1; + sv.elem_id = splitEdges[j]; + return; + } + } + + throw new Exception("update_from_split: unsortable vertex?"); + } + + + + + + + + /// + /// Make sure that all intersection segments are represented by + /// a connected chain of edges. + /// + void connect_edges() + { + int NS = Segments.Length; + for ( int si = 0; si < NS; ++si ) { + IntersectSegment seg = Segments[si]; + if (seg.v0 == seg.v1) + continue; // degenerate! + if (seg.v0.vtx_id == seg.v1.vtx_id) + continue; // also degenerate and how does this happen? + + int a = seg.v0.vtx_id, b = seg.v1.vtx_id; + + if (a == DMesh3.InvalidID || b == DMesh3.InvalidID) + throw new Exception("segment vertex is not defined?"); + int eid = Target.FindEdge(a, b); + if (eid != DMesh3.InvalidID) + continue; // already connected + + // TODO: in many cases there is an edge we added during a + // poke or split that we could flip to get edge AB. + // this is much faster and we should do it where possible! + // HOWEVER we need to know which edges we can and cannot flip + // is_inserted_free_edge() should do this but not implemented yet + // possibly also requires that we do all these flips before any + // calls to insert_segment() ! + + try { + insert_segment(seg); + } catch (Exception) { + // ignore? + } + } + } + + + void insert_segment(IntersectSegment seg) + { + List subfaces = get_all_baseface_tris(seg.base_tid); + + RegionOperator op = new RegionOperator(Target, subfaces); + + Vector3d n = BaseFaceNormals[seg.base_tid]; + Vector3d c = BaseFaceCentroids[seg.base_tid]; + Vector3d e0, e1; + Vector3d.MakePerpVectors(ref n, out e0, out e1); + + DMesh3 mesh = op.Region.SubMesh; + MeshTransforms.PerVertexTransform(mesh, (v) => { + v -= c; + return new Vector3d(v.Dot(e0), v.Dot(e1), 0); + }); + + Vector3d end0 = seg.v0.v, end1 = seg.v1.v; + end0 -= c; end1 -= c; + Vector2d p0 = new Vector2d(end0.Dot(e0), end0.Dot(e1)); + Vector2d p1 = new Vector2d(end1.Dot(e0), end1.Dot(e1)); + PolyLine2d path = new PolyLine2d(); + path.AppendVertex(p0); path.AppendVertex(p1); + + MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(mesh, path); + insert.Apply(); + + MeshVertexSelection cutVerts = new MeshVertexSelection(mesh); + cutVerts.SelectEdgeVertices(insert.OnCutEdges); + + MeshTransforms.PerVertexTransform(mesh, (v) => { + return c + v.x * e0 + v.y * e1; + }); + + op.BackPropropagate(); + + // add new cut vertices to cut list + foreach (int vid in cutVerts) + SegmentInsertVertices.Add(op.ReinsertSubToBaseMapV[vid]); + + add_regionop_subfaces(seg.base_tid, op); + } + + + + + + void add_edge_vtx(int eid, SegmentVtx vtx) + { + List l; + if (EdgeVertices.TryGetValue(eid, out l)) { + l.Add(vtx); + } else { + l = new List() { vtx }; + EdgeVertices[eid] = l; + } + } + + void add_face_vtx(int tid, SegmentVtx vtx) + { + List l; + if (FaceVertices.TryGetValue(tid, out l)) { + l.Add(vtx); + } else { + l = new List() { vtx }; + FaceVertices[tid] = l; + } + } + + + + void add_poke_subfaces(int tid, ref DMesh3.PokeTriangleInfo pokeInfo) + { + int parent = get_parent(tid); + HashSet subfaces = get_subfaces(parent); + if (tid != parent) + add_subface(subfaces, parent, tid); + add_subface(subfaces, parent, pokeInfo.new_t1); + add_subface(subfaces, parent, pokeInfo.new_t2); + } + void add_split_subfaces(Index2i origTris, ref DMesh3.EdgeSplitInfo splitInfo) + { + int parent_1 = get_parent(origTris.a); + HashSet subfaces_1 = get_subfaces(parent_1); + if (origTris.a != parent_1) + add_subface(subfaces_1, parent_1, origTris.a); + add_subface(subfaces_1, parent_1, splitInfo.eNewT2); + + if ( origTris.b != DMesh3.InvalidID ) { + int parent_2 = get_parent(origTris.b); + HashSet subfaces_2 = get_subfaces(parent_2); + if (origTris.b != parent_2) + add_subface(subfaces_2, parent_2, origTris.b); + add_subface(subfaces_2, parent_2, splitInfo.eNewT3); + } + } + void add_regionop_subfaces(int parent, RegionOperator op) + { + HashSet subfaces = get_subfaces(parent); + foreach (int tid in op.CurrentBaseTriangles) { + if (tid != parent) + add_subface(subfaces, parent, tid); + } + } + + + int get_parent(int tid) + { + int parent; + if (ParentFaces.TryGetValue(tid, out parent) == false) + parent = tid; + return parent; + } + HashSet get_subfaces(int parent) + { + HashSet subfaces; + if (SubFaces.TryGetValue(parent, out subfaces) == false) { + subfaces = new HashSet(); + SubFaces[parent] = subfaces; + } + return subfaces; + } + void add_subface(HashSet subfaces, int parent, int tid) + { + subfaces.Add(tid); + ParentFaces[tid] = parent; + } + List get_all_baseface_tris(int base_tid) + { + List faces = new List(get_subfaces(base_tid)); + faces.Add(base_tid); + return faces; + } + + bool is_inserted_free_edge(int eid) + { + Index2i et = Target.GetEdgeT(eid); + if (get_parent(et.a) != get_parent(et.b)) + return false; + // TODO need to check if we need to save edge AB to connect vertices! + throw new Exception("not done yet!"); + return true; + } + + + + + protected int on_edge(ref Triangle3d tri, ref Vector3d v) + { + Segment3d s01 = new Segment3d(tri.V0, tri.V1); + if (s01.DistanceSquared(v) < VertexSnapTol * VertexSnapTol) + return 0; + Segment3d s12 = new Segment3d(tri.V1, tri.V2); + if (s12.DistanceSquared(v) < VertexSnapTol * VertexSnapTol) + return 1; + Segment3d s20 = new Segment3d(tri.V2, tri.V0); + if (s20.DistanceSquared(v) < VertexSnapTol * VertexSnapTol) + return 2; + return -1; + } + protected int on_edge_eid(int tid, Vector3d v) + { + Index3i tv = Target.GetTriangle(tid); + Triangle3d tri = new Triangle3d(); + Target.GetTriVertices(tid, ref tri.V0, ref tri.V1, ref tri.V2); + int eidx = on_edge(ref tri, ref v); + if (eidx < 0) + return DMesh3.InvalidID; + int eid = Target.FindEdge(tv[eidx], tv[(eidx+1)%3]); + Util.gDevAssert(eid != DMesh3.InvalidID); + return eid; + } + protected bool is_on_edge(int eid, Vector3d v) + { + Index2i ev = Target.GetEdgeV(eid); + Segment3d seg = new Segment3d(Target.GetVertex(ev.a), Target.GetVertex(ev.b)); + return seg.DistanceSquared(v) < VertexSnapTol * VertexSnapTol; + } + + protected bool is_in_triangle(int tid, Vector3d v) + { + Triangle3d tri = new Triangle3d(); + Target.GetTriVertices(tid, ref tri.V0, ref tri.V1, ref tri.V2); + Vector3d bary = tri.BarycentricCoords(v); + return (bary.x >= 0 && bary.y >= 0 && bary.z >= 0 + && bary.x < 1 && bary.y <= 1 && bary.z <= 1); + + } + + + + /// + /// find existing vertex at point, if it exists + /// + protected int find_existing_vertex(Vector3d pt) + { + return find_nearest_vertex(pt, VertexSnapTol); + } + /// + /// find closest vertex, within searchRadius + /// + protected int find_nearest_vertex(Vector3d pt, double searchRadius, int ignore_vid = -1) + { + KeyValuePair found = (ignore_vid == -1) ? + PointHash.FindNearestInRadius(pt, searchRadius, + (b) => { return pt.DistanceSquared(Target.GetVertex(b)); }) + : + PointHash.FindNearestInRadius(pt, searchRadius, + (b) => { return pt.DistanceSquared(Target.GetVertex(b)); }, + (vid) => { return vid == ignore_vid; }); + if (found.Key == PointHash.InvalidValue) + return -1; + return found.Key; + } + + + + } +} diff --git a/mesh_ops/MeshRepairOrientation.cs b/mesh_ops/MeshRepairOrientation.cs new file mode 100644 index 00000000..6b4a7ad5 --- /dev/null +++ b/mesh_ops/MeshRepairOrientation.cs @@ -0,0 +1,177 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using g3; + +namespace gs +{ + public class MeshRepairOrientation + { + public DMesh3 Mesh; + + DMeshAABBTree3 spatial; + protected DMeshAABBTree3 Spatial { + get { + if (spatial == null) + spatial = new DMeshAABBTree3(Mesh, true); + return spatial; + } + } + + public MeshRepairOrientation(DMesh3 mesh3, DMeshAABBTree3 spatial = null) + { + Mesh = mesh3; + this.spatial = spatial; + } + + + class Component + { + public List triangles; + public double outFacing; + public double inFacing; + } + List Components = new List(); + + + + + // TODO: + // - (in merge coincident) don't merge tris with same/opposite normals (option) + // - after orienting components, try to find adjacent open components and + // transfer orientation between them + // - orient via nesting + + + public void OrientComponents() + { + Components = new List(); + + HashSet remaining = new HashSet(Mesh.TriangleIndices()); + List stack = new List(); + while (remaining.Count > 0) { + Component c = new Component(); + c.triangles = new List(); + + stack.Clear(); + int start = remaining.First(); + remaining.Remove(start); + c.triangles.Add(start); + stack.Add(start); + while (stack.Count > 0) { + int cur = stack[stack.Count - 1]; + stack.RemoveAt(stack.Count - 1); + Index3i tcur = Mesh.GetTriangle(cur); + + Index3i nbrs = Mesh.GetTriNeighbourTris(cur); + for (int j = 0; j < 3; ++j) { + int nbr = nbrs[j]; + if (remaining.Contains(nbr) == false) + continue; + + int a = tcur[j]; + int b = tcur[(j+1)%3]; + + Index3i tnbr = Mesh.GetTriangle(nbr); + if (IndexUtil.find_tri_ordered_edge(b, a, ref tnbr) == DMesh3.InvalidID) { + Mesh.ReverseTriOrientation(nbr); + } + stack.Add(nbr); + remaining.Remove(nbr); + c.triangles.Add(nbr); + } + + } + + Components.Add(c); + } + } + + + + + + public void ComputeStatistics() + { + var s = this.Spatial; // make sure this exists + // Cannot do in parallel because we set a filter on spatial DS. + // Also we are doing rays in parallel anyway... + foreach ( var c in Components ) { + compute_statistics(c); + } + } + void compute_statistics(Component c) + { + int NC = c.triangles.Count; + c.inFacing = c.outFacing = 0; + double dist = 2 * Mesh.CachedBounds.DiagonalLength; + + // only want to raycast triangles in this + HashSet tris = new HashSet(c.triangles); + spatial.TriangleFilterF = tris.Contains; + + // We want to try to figure out what is 'outside' relative to the world. + // Assumption is that faces we can hit from far away should be oriented outwards. + // So, for each triangle we construct far-away points in positive and negative normal + // direction, then raycast back towards the triangle. If we hit the triangle from + // one side and not the other, that is evidence we should keep/reverse that triangle. + // If it is not hit, or hit from both, that does not provide any evidence. + // We collect up this keep/reverse evidence and use the larger to decide on the global orientation. + + SpinLock count_lock = new SpinLock(); + + gParallel.BlockStartEnd(0, NC - 1, (a, b) => { + for (int i = a; i <= b; ++i) { + int ti = c.triangles[i]; + Vector3d normal, centroid; double area; + Mesh.GetTriInfo(ti, out normal, out area, out centroid); + if (area < MathUtil.ZeroTolerancef) + continue; + + // construct far away points + Vector3d pos_pt = centroid + dist * normal; + Vector3d neg_pt = centroid - dist * normal; + + // raycast towards triangle from far-away point + int hit_pos = spatial.FindNearestHitTriangle(new Ray3d(pos_pt, -normal)); + int hit_neg = spatial.FindNearestHitTriangle(new Ray3d(neg_pt, normal)); + if (hit_pos != ti && hit_neg != ti) + continue; // no evidence + if (hit_pos == ti && hit_neg == ti) + continue; // no evidence (?) + + bool taken = false; + count_lock.Enter(ref taken); + + if (hit_neg == ti) + c.inFacing += area; + else if (hit_pos == ti) + c.outFacing += area; + + count_lock.Exit(); + } + }); + + spatial.TriangleFilterF = null; + } + + + + public void SolveGlobalOrientation() + { + ComputeStatistics(); + MeshEditor editor = new MeshEditor(Mesh); + foreach (Component c in Components) { + if (c.inFacing > c.outFacing) { + editor.ReverseTriangles(c.triangles); + } + } + } + + + + } +} diff --git a/mesh_ops/MeshSpatialSort.cs b/mesh_ops/MeshSpatialSort.cs new file mode 100644 index 00000000..b2f74a12 --- /dev/null +++ b/mesh_ops/MeshSpatialSort.cs @@ -0,0 +1,292 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Threading; +using g3; + +namespace gs +{ + /// + /// This class sorts a set of mesh components. + /// + public class MeshSpatialSort + { + // ComponentMesh is a wrapper around input meshes + public List Components; + + // a MeshSolid is an "Outer" mesh and a set of "Cavity" meshes + // (the cavity list includes contained open meshes, though) + public List Solids; + + public bool AllowOpenContainers = false; + public double FastWindingIso = 0.5f; + + + public MeshSpatialSort() + { + Components = new List(); + } + + + public void AddMesh(DMesh3 mesh, object identifier, DMeshAABBTree3 spatial = null) + { + ComponentMesh comp = new ComponentMesh(mesh, identifier, spatial); + if (spatial == null) { + if (comp.IsClosed || AllowOpenContainers) + comp.Spatial = new DMeshAABBTree3(mesh, true); + } + + Components.Add(comp); + } + + + + + public class ComponentMesh + { + public object Identifier; + public DMesh3 Mesh; + public bool IsClosed; + public DMeshAABBTree3 Spatial; + public AxisAlignedBox3d Bounds; + + // meshes that contain this one + public List InsideOf = new List(); + + // meshes that are inside of this one + public List InsideSet = new List(); + + public ComponentMesh(DMesh3 mesh, object identifier, DMeshAABBTree3 spatial) + { + this.Mesh = mesh; + this.Identifier = identifier; + this.IsClosed = mesh.IsClosed(); + this.Spatial = spatial; + Bounds = mesh.CachedBounds; + } + + public bool Contains(ComponentMesh mesh2, double fIso = 0.5f) + { + if (this.Spatial == null) + return false; + // make sure FWN is available + this.Spatial.FastWindingNumber(Vector3d.Zero); + + // block-parallel iteration provides a reasonable speedup + int NV = mesh2.Mesh.VertexCount; + bool contained = true; + gParallel.BlockStartEnd(0, NV - 1, (a, b) => { + if (contained == false) + return; + for (int vi = a; vi <= b && contained; vi++) { + Vector3d v = mesh2.Mesh.GetVertex(vi); + if ( Math.Abs(Spatial.FastWindingNumber(v)) < fIso) { + contained = false; + break; + } + } + }, 100); + + return contained; + } + } + + + + public class MeshSolid + { + public ComponentMesh Outer; + public List Cavities = new List(); + } + + + + + + + + public void Sort() + { + int N = Components.Count; + + ComponentMesh[] comps = Components.ToArray(); + + // sort by bbox containment to speed up testing (does it??) + Array.Sort(comps, (i,j) => { + return i.Bounds.Contains(j.Bounds) ? -1 : 1; + }); + + // containment sets + bool[] bIsContained = new bool[N]; + Dictionary> ContainSets = new Dictionary>(); + Dictionary> ContainedParents = new Dictionary>(); + SpinLock dataLock = new SpinLock(); + + // [TODO] this is 90% of compute time... + // - if I know X contains Y, and Y contains Z, then I don't have to check that X contains Z + // - can we exploit this somehow? + // - if j contains i, then it cannot be that i contains j. But we are + // not checking for this! (although maybe bbox check still early-outs it?) + + // construct containment sets + gParallel.ForEach(Interval1i.Range(N), (i) => { + ComponentMesh compi = comps[i]; + + if (compi.IsClosed == false && AllowOpenContainers == false) + return; + + for (int j = 0; j < N; ++j) { + if (i == j) + continue; + ComponentMesh compj = comps[j]; + + // cannot be contained if bounds are not contained + if (compi.Bounds.Contains(compj.Bounds) == false) + continue; + + // any other early-outs?? + if (compi.Contains(compj)) { + + bool entered = false; + dataLock.Enter(ref entered); + + compj.InsideOf.Add(compi); + compi.InsideSet.Add(compj); + + if (ContainSets.ContainsKey(i) == false) + ContainSets.Add(i, new List()); + ContainSets[i].Add(j); + bIsContained[j] = true; + if (ContainedParents.ContainsKey(j) == false) + ContainedParents.Add(j, new List()); + ContainedParents[j].Add(i); + + dataLock.Exit(); + } + + } + }); + + + List solids = new List(); + HashSet used = new HashSet(); + + Dictionary CompToOuterIndex = new Dictionary(); + + List ParentsToProcess = new List(); + + + // The following is a lot of code but it is very similar, just not clear how + // to refactor out the common functionality + // 1) we find all the top-level uncontained polys and add them to the final polys list + // 2a) for any poly contained in those parent-polys, that is not also contained in anything else, + // add as hole to that poly + // 2b) remove all those used parents & holes from consideration + // 2c) now find all the "new" top-level polys + // 3) repeat 2a-c until done all polys + // 4) any remaining polys must be interior solids w/ no holes + // **or** weird leftovers like intersecting polys... + + // add all top-level uncontained polys + for (int i = 0; i < N; ++i) { + ComponentMesh compi = comps[i]; + if (bIsContained[i]) + continue; + + MeshSolid g = new MeshSolid() { Outer = compi }; + + int idx = solids.Count; + CompToOuterIndex[compi] = idx; + used.Add(compi); + + if (ContainSets.ContainsKey(i)) + ParentsToProcess.Add(i); + + solids.Add(g); + } + + + // keep iterating until we processed all parents + while (ParentsToProcess.Count > 0) { + List ContainersToRemove = new List(); + + // now for all top-level components that contain children, add those children + // as long as they do not have multiple contain-parents + foreach (int i in ParentsToProcess) { + ComponentMesh parentComp = comps[i]; + int outer_idx = CompToOuterIndex[parentComp]; + + List children = ContainSets[i]; + foreach (int childj in children) { + ComponentMesh childComp = comps[childj]; + Util.gDevAssert(used.Contains(childComp) == false); + + // skip multiply-contained children + List parents = ContainedParents[childj]; + if (parents.Count > 1) + continue; + + solids[outer_idx].Cavities.Add(childComp); + + used.Add(childComp); + if (ContainSets.ContainsKey(childj)) + ContainersToRemove.Add(childj); + } + ContainersToRemove.Add(i); + } + + // remove all containers that are no longer valid + foreach (int ci in ContainersToRemove) { + ContainSets.Remove(ci); + + // have to remove from each ContainedParents list + List keys = new List(ContainedParents.Keys); + foreach (int j in keys) { + if (ContainedParents[j].Contains(ci)) + ContainedParents[j].Remove(ci); + } + } + + ParentsToProcess.Clear(); + + // ok now find next-level uncontained parents... + for (int i = 0; i < N; ++i) { + ComponentMesh compi = comps[i]; + if (used.Contains(compi)) + continue; + if (ContainSets.ContainsKey(i) == false) + continue; + List parents = ContainedParents[i]; + if (parents.Count > 0) + continue; + + MeshSolid g = new MeshSolid() { Outer = compi }; + + int idx = solids.Count; + CompToOuterIndex[compi] = idx; + used.Add(compi); + + if (ContainSets.ContainsKey(i)) + ParentsToProcess.Add(i); + + solids.Add(g); + } + } + + + // any remaining components must be top-level + for (int i = 0; i < N; ++i) { + ComponentMesh compi = comps[i]; + if (used.Contains(compi)) + continue; + MeshSolid g = new MeshSolid() { Outer = compi }; + solids.Add(g); + } + + Solids = solids; + } + + + } +} diff --git a/mesh_ops/MeshStitchLoops.cs b/mesh_ops/MeshStitchLoops.cs new file mode 100644 index 00000000..3cde53f8 --- /dev/null +++ b/mesh_ops/MeshStitchLoops.cs @@ -0,0 +1,190 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// Stitch together two edge loops without any constraint that they have the same vertex count + /// (otherwise can use MeshEditor.StitchLoop / StitchUnorderedEdges). + /// + /// [TODO] + /// - something smarter than stitch_span_simple(). For example, equalize length we have + /// travelled along the span. Could also use normals to try to keep span "smooth" + /// - currently Loop0 and Loop1 need to be reversed/not depending on whether we are + /// stitching "through" mesh or not. If not set properly, then fill self-intersects. + /// Could we (optionally) resolve this automatically, eg by checking total of the two alternatives? + /// + public class MeshStitchLoops + { + public DMesh3 Mesh; + public EdgeLoop Loop0; + public EdgeLoop Loop1; + + // if you are not sure that loops have correct order relative to + // existing boundary edges, set this to false and we will figure out ourselves + public bool TrustLoopOrientations = true; + + public SetGroupBehavior Group = SetGroupBehavior.AutoGenerate; + + + // span represents an interval of loop indices on either side that + // need to be stitched together + struct span + { + public Interval1i span0; + public Interval1i span1; + } + List spans = new List(); + + + public MeshStitchLoops(DMesh3 mesh, EdgeLoop l0, EdgeLoop l1) + { + Mesh = mesh; + Loop0 = new EdgeLoop(l0); + Loop1 = new EdgeLoop(l1); + + span s = new span() { + span0 = new Interval1i(0, 0), + span1 = new Interval1i(0, 0) + }; + spans.Add(s); + } + + + /// + /// specify subset of vertices that have known correspondences. + /// + public void AddKnownCorrespondences(int[] verts0, int[] verts1) + { + int N = verts0.Length; + if (N != verts1.Length) + throw new Exception("MeshStitchLoops.AddKnownCorrespondence: lengths not the same!"); + + // construct list of pair correspondences as loop indices + List pairs = new List(); + for ( int k = 0; k < N; ++k ) { + int i0 = Loop0.FindVertexIndex(verts0[k]); + int i1 = Loop1.FindVertexIndex(verts1[k]); + pairs.Add(new Index2i(i0, i1)); + } + + // sort by increasing index in loop0 (arbitrary) + pairs.Sort((pair1, pair2) => { return pair1.a.CompareTo(pair2.a); }); + + // now construct spans + List new_spans = new List(); + for ( int k = 0; k < pairs.Count; ++k ) { + Index2i p1 = pairs[k]; + Index2i p2 = pairs[(k + 1) % pairs.Count]; + span s = new span() { + span0 = new Interval1i(p1.a, p2.a), + span1 = new Interval1i(p1.b, p2.b) + }; + new_spans.Add(s); + } + spans = new_spans; + } + + + + + public bool Stitch() + { + if (spans.Count == 1) + throw new Exception("MeshStitchLoops.Stitch: blind stitching not supported yet..."); + + int gid = Group.GetGroupID(Mesh); + + bool all_ok = true; + + int NS = spans.Count; + for ( int si = 0; si < NS; si++ ) { + span s = spans[si]; + + if (stitch_span_simple(s, gid) == false) + all_ok = false; + } + + return all_ok; + } + + + + /// + /// this just does back-and-forth zippering, of as many quads as possible, and + /// then a triangle-fan to finish whichever side is longer + /// + bool stitch_span_simple(span s, int gid) + { + bool all_ok = true; + + int N0 = Loop0.Vertices.Length; + int N1 = Loop1.Vertices.Length; + + // stitch as many quads as we can + int cur0 = s.span0.a, end0 = s.span0.b; + int cur1 = s.span1.a, end1 = s.span1.b; + while (cur0 != end0 && cur1 != end1) { + int next0 = (cur0 + 1) % N0; + int next1 = (cur1 + 1) % N1; + + int a = Loop0.Vertices[cur0], b = Loop0.Vertices[next0]; + int c = Loop1.Vertices[cur1], d = Loop1.Vertices[next1]; + if (add_triangle(b, a, c, gid) == false) + all_ok = false; + if (add_triangle(c, d, b, gid) == false) + all_ok = false; + + cur0 = next0; + cur1 = next1; + } + + // now finish remaining verts on one side + int last_c = Loop1.Vertices[cur1]; + while (cur0 != end0) { + int next0 = (cur0 + 1) % N0; + int a = Loop0.Vertices[cur0], b = Loop0.Vertices[next0]; + if (add_triangle(b, a, last_c, gid) == false) + all_ok = false; + cur0 = next0; + } + + // or the other (only one of these two loops will happen) + int last_b = Loop0.Vertices[cur0]; + while (cur1 != end1) { + int next1 = (cur1 + 1) % N1; + int c = Loop1.Vertices[cur1], d = Loop1.Vertices[next1]; + if (add_triangle(c, d, last_b, gid) == false) + all_ok = false; + cur1 = next1; + } + + return all_ok; + } + + + + + bool add_triangle(int a, int b, int c, int gid) + { + int new_tid = DMesh3.InvalidID; + if (TrustLoopOrientations == false) { + int eid = Mesh.FindEdge(a, b); + Index2i ab = Mesh.GetOrientedBoundaryEdgeV(eid); + new_tid = Mesh.AppendTriangle(ab.b, ab.a, c, gid); + } else { + new_tid = Mesh.AppendTriangle(a, b, c, gid); + } + return (new_tid >= 0); + } + + + + + } +} diff --git a/mesh_ops/MeshTopology.cs b/mesh_ops/MeshTopology.cs new file mode 100644 index 00000000..fc5512c5 --- /dev/null +++ b/mesh_ops/MeshTopology.cs @@ -0,0 +1,229 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using g3; + +namespace gs +{ + /// + /// Extract topological information about the mesh based on identifying + /// semantic edges/vertices/etc + /// + /// WIP + /// + /// + public class MeshTopology + { + public DMesh3 Mesh; + + double crease_angle = 30.0f; + public double CreaseAngle { + get { return crease_angle; } + set { crease_angle = value; invalidate_topology(); } + } + + public MeshTopology(DMesh3 mesh) + { + Mesh = mesh; + } + + + public HashSet BoundaryEdges; + public HashSet CreaseEdges; + public HashSet AllEdges; + + public HashSet AllVertices; + public HashSet JunctionVertices; + + public EdgeLoop[] Loops; + public EdgeSpan[] Spans; + + int topo_timestamp = -1; + public bool IgnoreTimestamp = false; + + + /// + /// Compute the topology elements + /// + public void Compute() + { + validate_topology(); + } + + + /// + /// add topological edges/vertices as constraints for remeshing + /// + public void AddRemeshConstraints(MeshConstraints constraints) + { + validate_topology(); + + int set_index = 10; + + foreach (EdgeSpan span in Spans) { + DCurveProjectionTarget target = new DCurveProjectionTarget(span.ToCurve()); + MeshConstraintUtil.ConstrainVtxSpanTo(constraints, Mesh, span.Vertices, target, set_index++); + } + + foreach (EdgeLoop loop in Loops) { + DCurveProjectionTarget target = new DCurveProjectionTarget(loop.ToCurve()); + MeshConstraintUtil.ConstrainVtxLoopTo(constraints, Mesh, loop.Vertices, target, set_index++); + } + + VertexConstraint corners = VertexConstraint.Pinned; + corners.FixedSetID = -1; + foreach (int vid in JunctionVertices) { + if (constraints.HasVertexConstraint(vid)) { + VertexConstraint v = constraints.GetVertexConstraint(vid); + v.Target = null; + v.Fixed = true; + v.FixedSetID = -1; + constraints.SetOrUpdateVertexConstraint(vid, v); + } else { + constraints.SetOrUpdateVertexConstraint(vid, corners); + } + } + } + + + + void invalidate_topology() + { + topo_timestamp = -1; + } + + + void validate_topology() + { + if (IgnoreTimestamp && AllEdges != null) + return; + + if ( Mesh.ShapeTimestamp != topo_timestamp ) { + find_crease_edges(CreaseAngle); + extract_topology(); + topo_timestamp = Mesh.ShapeTimestamp; + } + } + + + + void find_crease_edges(double angle_tol) + { + CreaseEdges = new HashSet(); + BoundaryEdges = new HashSet(); + + double dot_tol = Math.Cos(angle_tol * MathUtil.Deg2Rad); + + foreach ( int eid in Mesh.EdgeIndices() ) { + Index2i et = Mesh.GetEdgeT(eid); + if ( et.b == DMesh3.InvalidID ) { + BoundaryEdges.Add(eid); + continue; + } + + Vector3d n0 = Mesh.GetTriNormal(et.a); + Vector3d n1 = Mesh.GetTriNormal(et.b); + if ( Math.Abs(n0.Dot(n1)) < dot_tol ) { + CreaseEdges.Add(eid); + } + } + + AllEdges = new HashSet(CreaseEdges); ; + foreach ( int eid in BoundaryEdges ) + AllEdges.Add(eid); + + AllVertices = new HashSet(); + IndexUtil.EdgesToVertices(Mesh, AllEdges, AllVertices); + } + + + + + + void extract_topology() + { + DGraph3 graph = new DGraph3(); + + // add vertices to graph, and store mappings + int[] mapV = new int[Mesh.MaxVertexID]; + int[] mapVFrom = new int[AllVertices.Count]; + foreach (int vid in AllVertices) { + int new_vid = graph.AppendVertex(Mesh.GetVertex(vid)); + mapV[vid] = new_vid; + mapVFrom[new_vid] = vid; + } + + // add edges to graph. graph-to-mesh eid mapping is stored via graph edge-group-id + int[] mapE = new int[Mesh.MaxEdgeID]; + foreach (int eid in AllEdges) { + Index2i ev = Mesh.GetEdgeV(eid); + int new_a = mapV[ev.a]; + int new_b = mapV[ev.b]; + int new_eid = graph.AppendEdge(new_a, new_b, eid); + mapE[eid] = new_eid; + } + + // extract the graph topology + DGraph3Util.Curves curves = DGraph3Util.ExtractCurves(graph, true); + + // reconstruct mesh spans / curves / junctions from graph topology + + int NP = curves.PathEdges.Count; + Spans = new EdgeSpan[NP]; + for (int pi = 0; pi < NP; ++pi) { + List pathE = curves.PathEdges[pi]; + for (int k = 0; k < pathE.Count; ++k) { + pathE[k] = graph.GetEdgeGroup(pathE[k]); + } + Spans[pi] = EdgeSpan.FromEdges(Mesh, pathE); + } + + int NL = curves.LoopEdges.Count; + Loops = new EdgeLoop[NL]; + for (int li = 0; li < NL; ++li) { + List loopE = curves.LoopEdges[li]; + for (int k = 0; k < loopE.Count; ++k) { + loopE[k] = graph.GetEdgeGroup(loopE[k]); + } + Loops[li] = EdgeLoop.FromEdges(Mesh, loopE); + } + + JunctionVertices = new HashSet(); + foreach (int gvid in curves.JunctionV) + JunctionVertices.Add(mapVFrom[gvid]); + } + + + + + + public DMesh3 MakeElementsMesh(Polygon2d spanProfile, Polygon2d loopProfile) + { + DMesh3 result = new DMesh3(); + validate_topology(); + + foreach (EdgeSpan span in Spans) { + DCurve3 curve = span.ToCurve(Mesh); + TubeGenerator tubegen = new TubeGenerator(curve, spanProfile); + MeshEditor.Append(result, tubegen.Generate().MakeDMesh()); + } + + foreach (EdgeLoop loop in Loops) { + DCurve3 curve = loop.ToCurve(Mesh); + TubeGenerator tubegen = new TubeGenerator(curve, loopProfile); + MeshEditor.Append(result, tubegen.Generate().MakeDMesh()); + } + + return result; + } + + + + + + } +} diff --git a/mesh_ops/MeshTrimLoop.cs b/mesh_ops/MeshTrimLoop.cs new file mode 100644 index 00000000..70cbddaf --- /dev/null +++ b/mesh_ops/MeshTrimLoop.cs @@ -0,0 +1,167 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace g3 +{ + + /// + /// Delete triangles inside on/near-surface trimming curve, and then adapt the new + /// boundary loop to conform to the loop. + /// + /// [DANGER] To use this class, we require a spatial data structure we can project onto. + /// Currently we assume that this is a DMesh3AABBTree *because* if you don't provide a + /// seed triangle, we use FindNearestTriangle() to find this index on the input mesh. + /// So, it must be a tree for the exact same mesh (!). + /// However we then delete a bunch of triangles and use this spatial DS only for reprojection. + /// Possibly these should be two separate things? Or force caller to provide seed triangle + /// for trim loop, instead of solving this problem for them? + /// (But basically there is no way around having a full mesh copy...) + /// + /// + /// TODO: + /// - output boundary EdgeLoop that has been aligned w/ trim curve + /// - handle cases where input mesh has open borders + /// + public class MeshTrimLoop + { + public DMesh3 Mesh; + public DMeshAABBTree3 Spatial; + public DCurve3 TrimLine; + + public int RemeshBorderRings = 2; + public double SmoothingAlpha = 1.0; // valid range is [0,1] + public double TargetEdgeLength = 0; // if 0, will use average border edge length + public int RemeshRounds = 20; + + int seed_tri = -1; + Vector3d seed_pt = Vector3d.MaxValue; + + /// + /// Cut mesh with plane. Assumption is that plane normal is Z value. + /// + public MeshTrimLoop(DMesh3 mesh, DCurve3 trimline, int tSeedTID, DMeshAABBTree3 spatial = null) + { + if (spatial != null && spatial.Mesh == mesh) + throw new ArgumentException("MeshTrimLoop: input spatial DS must have its own copy of mesh"); + Mesh = mesh; + TrimLine = new DCurve3(trimline); + if (spatial != null) { + Spatial = spatial; + } + seed_tri = tSeedTID; + } + + public MeshTrimLoop(DMesh3 mesh, DCurve3 trimline, Vector3d vSeedPt, DMeshAABBTree3 spatial = null) + { + if (spatial != null && spatial.Mesh == mesh) + throw new ArgumentException("MeshTrimLoop: input spatial DS must have its own copy of mesh"); + Mesh = mesh; + TrimLine = new DCurve3(trimline); + if (spatial != null) { + Spatial = spatial; + } + seed_pt = vSeedPt; + } + + public virtual ValidationStatus Validate() + { + // [TODO] + return ValidationStatus.Ok; + } + + + public virtual bool Trim() + { + if ( Spatial == null ) { + Spatial = new DMeshAABBTree3(new DMesh3(Mesh, false, MeshComponents.None)); + Spatial.Build(); + } + + if ( seed_tri == -1 ) { + seed_tri = Spatial.FindNearestTriangle(seed_pt); + } + + MeshFacesFromLoop loop = new MeshFacesFromLoop(Mesh, TrimLine, Spatial, seed_tri); + + MeshFaceSelection selection = loop.ToSelection(); + selection.LocalOptimize(true, true); + MeshEditor editor = new MeshEditor(Mesh); + editor.RemoveTriangles(selection, true); + + MeshConnectedComponents components = new MeshConnectedComponents(Mesh); + components.FindConnectedT(); + if ( components.Count > 1 ) { + int keep = components.LargestByCount; + for ( int i = 0; i < components.Count; ++i ) { + if ( i != keep ) + editor.RemoveTriangles(components[i].Indices, true); + } + } + editor.RemoveAllBowtieVertices(true); + + MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh); + bool loopsOK = false; + try { + loopsOK = loops.Compute(); + } catch (Exception) { + return false; + } + if (!loopsOK) + return false; + + + // [TODO] to support trimming mesh w/ existing holes, we need to figure out which + // loop we created in RemoveTriangles above! + if (loops.Count > 1) + return false; + + + int[] loopVerts = loops[0].Vertices; + + MeshFaceSelection borderTris = new MeshFaceSelection(Mesh); + borderTris.SelectVertexOneRings(loopVerts); + borderTris.ExpandToOneRingNeighbours(RemeshBorderRings); + + RegionRemesher remesh = new RegionRemesher(Mesh, borderTris.ToArray()); + remesh.Region.MapVerticesToSubmesh(loopVerts); + + double target_len = TargetEdgeLength; + if (target_len <= 0) { + double mine, maxe, avge; + MeshQueries.EdgeLengthStatsFromEdges(Mesh, loops[0].Edges, out mine, out maxe, out avge); + target_len = avge; + } + + MeshProjectionTarget meshTarget = new MeshProjectionTarget(Spatial.Mesh, Spatial); + remesh.SetProjectionTarget(meshTarget); + remesh.SetTargetEdgeLength(target_len); + remesh.SmoothSpeedT = SmoothingAlpha; + + DCurveProjectionTarget curveTarget = new DCurveProjectionTarget(TrimLine); + SequentialProjectionTarget multiTarget = new SequentialProjectionTarget(curveTarget, meshTarget); + + int set_id = 3; + MeshConstraintUtil.ConstrainVtxLoopTo(remesh, loopVerts, multiTarget, set_id); + + for (int i = 0; i < RemeshRounds; ++i) { + remesh.BasicRemeshPass(); + } + + remesh.BackPropropagate(); + + // [TODO] output loop somehow...use MeshConstraints.FindConstrainedEdgesBySetID(set_id)... + + return true; + + } // Trim() + + + + + + + } +} diff --git a/mesh_ops/MinimalHoleFill.cs b/mesh_ops/MinimalHoleFill.cs new file mode 100644 index 00000000..703901f1 --- /dev/null +++ b/mesh_ops/MinimalHoleFill.cs @@ -0,0 +1,489 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// Construct a "minimal" fill surface for the hole. This surface + /// is often quasi-developable, reconstructs sharp edges, etc. + /// There are various options. + /// + public class MinimalHoleFill + { + public DMesh3 Mesh; + public EdgeLoop FillLoop; + + public bool IgnoreBoundaryTriangles = false; + public bool OptimizeDevelopability = true; + public bool OptimizeTriangles = true; + public double DevelopabilityTolerance = 0.0001; + + /* + * Outputs + */ + + /// Final fill vertices (should be empty?) + public int[] FillVertices; + + /// Final fill triangles + public int[] FillTriangles; + + + public MinimalHoleFill(DMesh3 mesh, EdgeLoop fillLoop) + { + this.Mesh = mesh; + this.FillLoop = fillLoop; + } + + + RegionOperator regionop; + DMesh3 fillmesh; + HashSet boundaryv; + Dictionary exterior_angle_sums; + + double[] curvatures; + + public bool Apply() + { + // do a simple fill + SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop); + int fill_gid = Mesh.AllocateTriangleGroup(); + bool bOK = simplefill.Fill(fill_gid); + if (bOK == false) + return false; + + if (FillLoop.Vertices.Length <= 3) { + FillTriangles = simplefill.NewTriangles; + FillVertices = new int[0]; + return true; + } + + // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later + HashSet intial_fill_tris = new HashSet(simplefill.NewTriangles); + regionop = new RegionOperator(Mesh, simplefill.NewTriangles, + (submesh) => { submesh.ComputeTriMaps = true; }); + fillmesh = regionop.Region.SubMesh; + + // for each boundary vertex, compute the exterior angle sum + // we will use this to compute gaussian curvature later + boundaryv = new HashSet(MeshIterators.BoundaryEdgeVertices(fillmesh)); + exterior_angle_sums = new Dictionary(); + if (IgnoreBoundaryTriangles == false) { + foreach (int sub_vid in boundaryv) { + double angle_sum = 0; + int base_vid = regionop.Region.MapVertexToBaseMesh(sub_vid); + foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid)) { + if (intial_fill_tris.Contains(tid) == false) { + Index3i et = regionop.BaseMesh.GetTriangle(tid); + int idx = IndexUtil.find_tri_index(base_vid, ref et); + angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx); + } + } + exterior_angle_sums[sub_vid] = angle_sum; + } + } + + + // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass + double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge; + MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge); + MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge); + double remesh_target_len = loop_avge; + if (fill_maxe / remesh_target_len > 10) + remesh_target_len = fill_maxe / 10; + //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4); + + // remesh up to target edge length, ideally gives us some triangles to work with + RemesherPro remesh1 = new RemesherPro(fillmesh); + remesh1.SmoothSpeedT = 1.0; + MeshConstraintUtil.FixAllBoundaryEdges(remesh1); + //remesh1.SetTargetEdgeLength(remesh_target_len / 2); // would this speed things up? on large regions? + //remesh1.FastestRemesh(); + remesh1.SetTargetEdgeLength(remesh_target_len); + remesh1.FastestRemesh(); + + /* + * first round: collapse to minimal mesh, while flipping to try to + * get to ballpark minimal mesh. We stop these passes as soon as + * we have done two rounds where we couldn't do another collapse + * + * This is the most unstable part of the algorithm because there + * are strong ordering effects. maybe we could sort the edges somehow?? + */ + + int zero_collapse_passes = 0; + int collapse_passes = 0; + while (collapse_passes++ < 20 && zero_collapse_passes < 2) { + + // collapse pass + int NE = fillmesh.MaxEdgeID; + int collapses = 0; + for (int ei = 0; ei < NE; ++ei) { + if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) + continue; + Index2i ev = fillmesh.GetEdgeV(ei); + bool a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b); + if (a_bdry && b_bdry) + continue; + int keepv = (a_bdry) ? ev.a : ev.b; + int otherv = (keepv == ev.a) ? ev.b : ev.a; + Vector3d newv = fillmesh.GetVertex(keepv); + if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv)) + continue; + DMesh3.EdgeCollapseInfo info; + MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info); + if (result == MeshResult.Ok) + collapses++; + } + if (collapses == 0) zero_collapse_passes++; else zero_collapse_passes = 0; + + // flip pass. we flip in these cases: + // 1) if angle between current triangles is too small (slightly more than 90 degrees, currently) + // 2) if angle between flipped triangles is smaller than between current triangles + // 3) if flipped edge length is shorter *and* such a flip won't flip the normal + NE = fillmesh.MaxEdgeID; + Vector3d n1, n2, on1, on2; + for (int ei = 0; ei < NE; ++ei) { + if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei)) + continue; + bool do_flip = false; + + Index2i ev = fillmesh.GetEdgeV(ei); + MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); + double dot_cur = n1.Dot(n2); + double dot_flip = on1.Dot(on2); + if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur+MathUtil.Epsilonf) + do_flip = true; + + if (do_flip == false) { + Index2i otherv = fillmesh.GetEdgeOpposingV(ei); + double len_e = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b)); + double len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b)); + if (len_flip < len_e) { + if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false) + do_flip = true; + } + } + + if (do_flip) { + DMesh3.EdgeFlipInfo info; + MeshResult result = fillmesh.FlipEdge(ei, out info); + } + } + } + + // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?) + // Try to force removal of such vertices, even if it makes ugly mesh + remove_remaining_interior_verts(); + + + // enable/disable passes. + bool DO_FLATTER_PASS = true; + bool DO_CURVATURE_PASS = OptimizeDevelopability && true; + bool DO_AREA_PASS = OptimizeDevelopability && OptimizeTriangles && true; + + + /* + * In this pass we repeat the flipping iterations from the previous pass. + * + * Note that because of the always-flip-if-dot-is-small case (commented), + * this pass will frequently not converge, as some number of edges will + * be able to flip back and forth (because neither has large enough dot). + * This is not ideal, but also, if we remove this behavior, then we + * generally get worse fills. This case basically introduces a sort of + * randomization factor that lets us escape local minima... + * + */ + + HashSet remaining_edges = new HashSet(fillmesh.EdgeIndices()); + HashSet updated_edges = new HashSet(); + + int flatter_passes = 0; + int zero_flips_passes = 0; + while ( flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS) { + zero_flips_passes++; + foreach (int ei in remaining_edges) { + if (fillmesh.IsBoundaryEdge(ei)) + continue; + + bool do_flip = false; + + Index2i ev = fillmesh.GetEdgeV(ei); + Vector3d n1, n2, on1, on2; + MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2); + double dot_cur = n1.Dot(n2); + double dot_flip = on1.Dot(on2); + if (flatter_passes < 20 && dot_cur < 0.1) // this check causes oscillatory behavior + do_flip = true; + if (dot_flip > dot_cur + MathUtil.Epsilonf) + do_flip = true; + + if (do_flip) { + DMesh3.EdgeFlipInfo info; + MeshResult result = fillmesh.FlipEdge(ei, out info); + if (result == MeshResult.Ok) { + zero_flips_passes = 0; + add_all_edges(ei, updated_edges); + } + } + } + + var tmp = remaining_edges; + remaining_edges = updated_edges; + updated_edges = tmp; updated_edges.Clear(); + } + + + int curvature_passes = 0; + if (DO_CURVATURE_PASS) { + + curvatures = new double[fillmesh.MaxVertexID]; + foreach (int vid in fillmesh.VertexIndices()) + update_curvature(vid); + + remaining_edges = new HashSet(fillmesh.EdgeIndices()); + updated_edges = new HashSet(); + + /* + * In this pass we try to minimize gaussian curvature at all the vertices. + * This will recover sharp edges, etc, and do lots of good stuff. + * However, this pass will not make much progress if we are not already + * relatively close to a minimal mesh, so it really relies on the previous + * passes getting us in the ballpark. + */ + while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS) { + foreach (int ei in remaining_edges) { + if (fillmesh.IsBoundaryEdge(ei)) + continue; + + Index2i ev = fillmesh.GetEdgeV(ei); + Index2i ov = fillmesh.GetEdgeOpposingV(ei); + + int find_other = fillmesh.FindEdge(ov.a, ov.b); + if (find_other != DMesh3.InvalidID) + continue; + + double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); + if (total_curv_cur < MathUtil.ZeroTolerancef) + continue; + + DMesh3.EdgeFlipInfo info; + MeshResult result = fillmesh.FlipEdge(ei, out info); + if (result != MeshResult.Ok) + continue; + + double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); + + bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef; + if (keep_flip == false) { + result = fillmesh.FlipEdge(ei, out info); + } else { + update_curvature(ev.a); update_curvature(ev.b); + update_curvature(ov.a); update_curvature(ov.b); + add_all_edges(ei, updated_edges); + } + } + var tmp = remaining_edges; + remaining_edges = updated_edges; + updated_edges = tmp; updated_edges.Clear(); + } + } + //System.Console.WriteLine("collapse {0} flatter {1} curvature {2}", collapse_passes, flatter_passes, curvature_passes); + + /* + * In this final pass, we try to improve triangle quality. We flip if + * the flipped triangles have better total aspect ratio, and the + * curvature doesn't change **too** much. The .DevelopabilityTolerance + * parameter determines what is "too much" curvature change. + */ + if (DO_AREA_PASS) { + remaining_edges = new HashSet(fillmesh.EdgeIndices()); + updated_edges = new HashSet(); + int area_passes = 0; + while (remaining_edges.Count() > 0 && area_passes < 20) { + area_passes++; + foreach (int ei in remaining_edges) { + if (fillmesh.IsBoundaryEdge(ei)) + continue; + + Index2i ev = fillmesh.GetEdgeV(ei); + Index2i ov = fillmesh.GetEdgeOpposingV(ei); + + int find_other = fillmesh.FindEdge(ov.a, ov.b); + if (find_other != DMesh3.InvalidID) + continue; + + double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b); + + double a = aspect_metric(ei); + if (a > 1) + continue; + + DMesh3.EdgeFlipInfo info; + MeshResult result = fillmesh.FlipEdge(ei, out info); + if (result != MeshResult.Ok) + continue; + + double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b); + + bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance; + if (keep_flip == false) { + result = fillmesh.FlipEdge(ei, out info); + } else { + update_curvature(ev.a); update_curvature(ev.b); + update_curvature(ov.a); update_curvature(ov.b); + add_all_edges(ei, updated_edges); + } + } + var tmp = remaining_edges; + remaining_edges = updated_edges; + updated_edges = tmp; updated_edges.Clear(); + } + } + + + regionop.BackPropropagate(); + FillTriangles = regionop.CurrentBaseTriangles; + FillVertices = regionop.CurrentBaseInteriorVertices().ToArray(); + + return true; + + } + + + + + + void remove_remaining_interior_verts() + { + HashSet interiorv = new HashSet(MeshIterators.InteriorVertices(fillmesh)); + int prev_count = 0; + while (interiorv.Count > 0 && interiorv.Count != prev_count) { + prev_count = interiorv.Count; + int[] curv = interiorv.ToArray(); + foreach (int vid in curv) { + foreach (int e in fillmesh.VtxEdgesItr(vid)) { + Index2i ev = fillmesh.GetEdgeV(e); + int otherv = (ev.a == vid) ? ev.b : ev.a; + DMesh3.EdgeCollapseInfo info; + MeshResult result = fillmesh.CollapseEdge(otherv, vid, out info); + if (result == MeshResult.Ok) + break; + } + if (fillmesh.IsVertex(vid) == false) + interiorv.Remove(vid); + } + } + if (interiorv.Count > 0) + Util.gBreakToDebugger(); + } + + + + + + void add_all_edges(int ei, HashSet edge_set) + { + Index2i et = fillmesh.GetEdgeT(ei); + Index3i te = fillmesh.GetTriEdges(et.a); + edge_set.Add(te.a); edge_set.Add(te.b); edge_set.Add(te.c); + te = fillmesh.GetTriEdges(et.b); + edge_set.Add(te.a); edge_set.Add(te.b); edge_set.Add(te.c); + } + + + + double area_metric(int eid) + { + Index3i ta, tb, ota, otb; + MeshUtil.GetEdgeFlipTris(fillmesh, eid, out ta, out tb, out ota, out otb); + double area_a = get_tri_area(fillmesh, ref ta); + double area_b = get_tri_area(fillmesh, ref tb); + double area_c = get_tri_area(fillmesh, ref ota); + double area_d = get_tri_area(fillmesh, ref otb); + double avg_ab = (area_a + area_b) * 0.5; + double avg_cd = (area_c + area_d) * 0.5; + double metric_ab = Math.Abs(area_a - avg_ab) + Math.Abs(area_b - avg_ab); + double metric_cd = Math.Abs(area_c - avg_cd) + Math.Abs(area_d - avg_cd); + return metric_cd / metric_ab; + } + + + double aspect_metric(int eid) + { + Index3i ta, tb, ota, otb; + MeshUtil.GetEdgeFlipTris(fillmesh, eid, out ta, out tb, out ota, out otb); + double aspect_a = get_tri_aspect(fillmesh, ref ta); + double aspect_b = get_tri_aspect(fillmesh, ref tb); + double aspect_c = get_tri_aspect(fillmesh, ref ota); + double aspect_d = get_tri_aspect(fillmesh, ref otb); + double metric_ab = Math.Abs(aspect_a - 1.0) + Math.Abs(aspect_b - 1.0); + double metric_cd = Math.Abs(aspect_c - 1.0) + Math.Abs(aspect_d - 1.0); + return metric_cd / metric_ab; + } + + + void update_curvature(int vid) + { + double angle_sum = 0; + exterior_angle_sums.TryGetValue(vid, out angle_sum); + foreach (int tid in fillmesh.VtxTrianglesItr(vid)) { + Index3i et = fillmesh.GetTriangle(tid); + int idx = IndexUtil.find_tri_index(vid, ref et); + angle_sum += fillmesh.GetTriInternalAngleR(tid, idx); + } + curvatures[vid] = angle_sum - MathUtil.TwoPI; + } + double curvature_metric_cached(int a, int b, int c, int d) + { + double defect_a = curvatures[a]; + double defect_b = curvatures[b]; + double defect_c = curvatures[c]; + double defect_d = curvatures[d]; + return Math.Abs(defect_a) + Math.Abs(defect_b) + Math.Abs(defect_c) + Math.Abs(defect_d); + } + + + double curvature_metric_eval(int a, int b, int c, int d) + { + double defect_a = compute_gauss_curvature(a); + double defect_b = compute_gauss_curvature(b); + double defect_c = compute_gauss_curvature(c); + double defect_d = compute_gauss_curvature(d); + return Math.Abs(defect_a) + Math.Abs(defect_b) + Math.Abs(defect_c) + Math.Abs(defect_d); + } + + double compute_gauss_curvature(int vid) + { + double angle_sum = 0; + exterior_angle_sums.TryGetValue(vid, out angle_sum); + foreach (int tid in fillmesh.VtxTrianglesItr(vid)) { + Index3i et = fillmesh.GetTriangle(tid); + int idx = IndexUtil.find_tri_index(vid, ref et); + angle_sum += fillmesh.GetTriInternalAngleR(tid, idx); + } + return angle_sum - MathUtil.TwoPI; + } + + + + Vector3d get_tri_normal(DMesh3 mesh, Index3i tri) + { + return MathUtil.Normal(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c)); + } + double get_tri_area(DMesh3 mesh, ref Index3i tri) + { + return MathUtil.Area(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c)); + } + double get_tri_aspect(DMesh3 mesh, ref Index3i tri) + { + return MathUtil.AspectRatio(mesh.GetVertex(tri.a), mesh.GetVertex(tri.b), mesh.GetVertex(tri.c)); + } + + } +} diff --git a/mesh_ops/PlanarSpansFiller.cs b/mesh_ops/PlanarSpansFiller.cs new file mode 100644 index 00000000..245e7cf5 --- /dev/null +++ b/mesh_ops/PlanarSpansFiller.cs @@ -0,0 +1,210 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace g3 +{ + /// + /// This class fills an ordered sequence of planar spans. The 2D polygon is formed + /// by chaining the spans. + /// + /// Current issues: + /// - connectors have a single segment, so when simplified, they become a single edge. + /// should subsample them instead. + /// - currently mapping from inserted edges back to span edges is not calculated, so + /// we have no way to merge them (ie MergeFillBoundary not implemented) + /// - fill triangles not returned? + /// + /// + /// + public class PlanarSpansFiller + { + public DMesh3 Mesh; + + public Vector3d PlaneOrigin; + public Vector3d PlaneNormal; + + /// + /// fill mesh will be tessellated to this length, set to + /// double.MaxValue to use zero-length tessellation + /// + public double FillTargetEdgeLen = double.MaxValue; + + /// + /// in some cases fill can succeed but we can't merge w/o creating holes. In + /// such cases it might be better to not merge at all... + /// + public bool MergeFillBoundary = false; + + // these will be computed if you don't set them + Vector3d PlaneX, PlaneY; + + + List FillSpans; + Polygon2d SpansPoly; + AxisAlignedBox2d Bounds; + + public PlanarSpansFiller(DMesh3 mesh, IList spans) + { + Mesh = mesh; + FillSpans = new List(spans); + Bounds = AxisAlignedBox2d.Empty; + } + + public void SetPlane(Vector3d origin, Vector3d normal) + { + PlaneOrigin = origin; + PlaneNormal = normal; + Vector3d.ComputeOrthogonalComplement(1, PlaneNormal, ref PlaneX, ref PlaneY); + } + public void SetPlane(Vector3d origin, Vector3d normal, Vector3d planeX, Vector3d planeY) + { + PlaneOrigin = origin; + PlaneNormal = normal; + PlaneX = planeX; + PlaneY = planeY; + } + + + public bool Fill() + { + compute_polygon(); + + // translate/scale fill loops to unit box. This will improve + // accuracy in the calcs below... + Vector2d shiftOrigin = Bounds.Center; + double scale = 1.0 / Bounds.MaxDim; + SpansPoly.Translate(-shiftOrigin); + SpansPoly.Scale(scale * Vector2d.One, Vector2d.Zero); + + Dictionary ElemToLoopMap = new Dictionary(); + + // generate planar mesh that we will insert polygons into + MeshGenerator meshgen; + float planeW = 1.5f; + int nDivisions = 0; + if ( FillTargetEdgeLen < double.MaxValue && FillTargetEdgeLen > 0) { + int n = (int)((planeW / (float)scale) / FillTargetEdgeLen) + 1; + nDivisions = (n <= 1) ? 0 : n; + } + + if (nDivisions == 0) { + meshgen = new TrivialRectGenerator() { + IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, + }; + } else { + meshgen = new GriddedRectGenerator() { + IndicesMap = new Index2i(1, 2), Width = planeW, Height = planeW, + EdgeVertices = nDivisions + }; + } + DMesh3 FillMesh = meshgen.Generate().MakeDMesh(); + FillMesh.ReverseOrientation(); // why?!? + + int[] polyVertices = null; + + // insert each poly + MeshInsertUVPolyCurve insert = new MeshInsertUVPolyCurve(FillMesh, SpansPoly); + ValidationStatus status = insert.Validate(MathUtil.ZeroTolerancef * scale); + bool failed = true; + if (status == ValidationStatus.Ok) { + if (insert.Apply()) { + insert.Simplify(); + polyVertices = insert.CurveVertices; + failed = false; + } + } + if (failed) + return false; + + // remove any triangles not contained in gpoly + // [TODO] degenerate triangle handling? may be 'on' edge of gpoly... + List removeT = new List(); + foreach (int tid in FillMesh.TriangleIndices()) { + Vector3d v = FillMesh.GetTriCentroid(tid); + if ( SpansPoly.Contains(v.xy) == false) + removeT.Add(tid); + } + foreach (int tid in removeT) + FillMesh.RemoveTriangle(tid, true, false); + + //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\CLIPPED_MESH.obj"); + + // transform fill mesh back to 3d + MeshTransforms.PerVertexTransform(FillMesh, (v) => { + Vector2d v2 = v.xy; + v2 /= scale; + v2 += shiftOrigin; + return to3D(v2); + }); + + + //Util.WriteDebugMesh(FillMesh, "c:\\scratch\\PLANAR_MESH_WITH_LOOPS.obj"); + //Util.WriteDebugMesh(MeshEditor.Combine(FillMesh, Mesh), "c:\\scratch\\FILLED_MESH.obj"); + + // figure out map between new mesh and original edge loops + // [TODO] if # of verts is different, we can still find correspondence, it is just harder + // [TODO] should check that edges (ie sequential verts) are boundary edges on fill mesh + // if not, can try to delete nbr tris to repair + IndexMap mergeMapV = new IndexMap(true); + if (MergeFillBoundary && polyVertices != null) { + throw new NotImplementedException("PlanarSpansFiller: merge fill boundary not implemented!"); + + //int[] fillLoopVerts = polyVertices; + //int NV = fillLoopVerts.Length; + + //PlanarComplex.Element sourceElem = (pi == 0) ? gsolid.Outer : gsolid.Holes[pi - 1]; + //int loopi = ElemToLoopMap[sourceElem]; + //EdgeLoop sourceLoop = Loops[loopi].edgeLoop; + + //for (int k = 0; k < NV; ++k) { + // Vector3d fillV = FillMesh.GetVertex(fillLoopVerts[k]); + // Vector3d sourceV = Mesh.GetVertex(sourceLoop.Vertices[k]); + // if (fillV.Distance(sourceV) < MathUtil.ZeroTolerancef) + // mergeMapV[fillLoopVerts[k]] = sourceLoop.Vertices[k]; + //} + } + + // append this fill to input mesh + MeshEditor editor = new MeshEditor(Mesh); + int[] mapV; + editor.AppendMesh(FillMesh, mergeMapV, out mapV, Mesh.AllocateTriangleGroup()); + + // [TODO] should verify that we actually merged the loops... + + return true; + } + + + + void compute_polygon() + { + SpansPoly = new Polygon2d(); + for ( int i = 0; i < FillSpans.Count; ++i ) { + foreach (int vid in FillSpans[i].Vertices) { + Vector2d v = to2D(Mesh.GetVertex(vid)); + SpansPoly.AppendVertex(v); + } + } + + Bounds = SpansPoly.Bounds; + } + + + Vector2d to2D(Vector3d v) + { + Vector3d dv = v - PlaneOrigin; + dv -= dv.Dot(PlaneNormal) * PlaneNormal; + return new Vector2d(PlaneX.Dot(dv), PlaneY.Dot(dv)); + } + + Vector3d to3D(Vector2d v) + { + return PlaneOrigin + PlaneX * v.x + PlaneY * v.y; + } + + } +} diff --git a/mesh_ops/RemoveDuplicateTriangles.cs b/mesh_ops/RemoveDuplicateTriangles.cs new file mode 100644 index 00000000..d06b3453 --- /dev/null +++ b/mesh_ops/RemoveDuplicateTriangles.cs @@ -0,0 +1,136 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using g3; + +namespace gs +{ + /// + /// Remove duplicate triangles. + /// + public class RemoveDuplicateTriangles + { + public DMesh3 Mesh; + + public double VertexTolerance = MathUtil.ZeroTolerancef; + public bool CheckOrientation = true; + + public int Removed = 0; + + public RemoveDuplicateTriangles(DMesh3 mesh) + { + Mesh = mesh; + } + + + public virtual bool Apply() { + Removed = 0; + + double merge_r2 = VertexTolerance * VertexTolerance; + + // construct hash table for edge midpoints + TriCentroids pointset = new TriCentroids() { Mesh = this.Mesh }; + PointSetHashtable hash = new PointSetHashtable(pointset); + int hashN = (Mesh.TriangleCount > 100000) ? 128 : 64; + hash.Build(hashN); + + Vector3d a = Vector3d.Zero, b = Vector3d.Zero, c = Vector3d.Zero; + Vector3d x = Vector3d.Zero, y = Vector3d.Zero, z = Vector3d.Zero; + + int MaxTriID = Mesh.MaxTriangleID; + + // remove duplicate triangles + int[] buffer = new int[1024]; + for ( int tid = 0; tid < MaxTriID; ++tid ) { + if (!Mesh.IsTriangle(tid)) + continue; + + Vector3d centroid = Mesh.GetTriCentroid(tid); + int N; + while (hash.FindInBall(centroid, VertexTolerance, buffer, out N) == false) + buffer = new int[buffer.Length]; + if (N == 1 && buffer[0] != tid) + throw new Exception("RemoveDuplicateTriangles.Apply: how could this happen?!"); + if (N <= 1) + continue; // unique edge + + Mesh.GetTriVertices(tid, ref a, ref b, ref c); + Vector3d n = MathUtil.Normal(a, b, c); + + for (int i = 0; i < N; ++i) { + if (buffer[i] != tid) { + Mesh.GetTriVertices(buffer[i], ref x, ref y, ref z); + if (is_same_triangle(ref a, ref b, ref c, ref x, ref y, ref z, merge_r2) == false) + continue; + + if (CheckOrientation) { + Vector3d n2 = MathUtil.Normal(x, y, z); + if (n.Dot(n2) < 0.99) + continue; + } + + MeshResult result = Mesh.RemoveTriangle(buffer[i], true, false); + if (result == MeshResult.Ok) + ++Removed; + } + } + } + + return true; + } + + + + bool is_same_triangle(ref Vector3d a, ref Vector3d b, ref Vector3d c, + ref Vector3d x, ref Vector3d y, ref Vector3d z, double tolSqr) { + if ( a.DistanceSquared(x) < tolSqr) { + if (b.DistanceSquared(y) < tolSqr && c.DistanceSquared(z) < tolSqr) + return true; + if (b.DistanceSquared(z) < tolSqr && c.DistanceSquared(y) < tolSqr) + return true; + } else if (a.DistanceSquared(y) < tolSqr) { + if (b.DistanceSquared(x) < tolSqr && c.DistanceSquared(z) < tolSqr) + return true; + if (b.DistanceSquared(z) < tolSqr && c.DistanceSquared(x) < tolSqr) + return true; + } else if (a.DistanceSquared(z) < tolSqr) { + if (b.DistanceSquared(x) < tolSqr && c.DistanceSquared(y) < tolSqr) + return true; + if (b.DistanceSquared(y) < tolSqr && c.DistanceSquared(x) < tolSqr) + return true; + } + return false; + } + + + + // present mesh tri centroids as a PointSet + class TriCentroids : IPointSet { + public DMesh3 Mesh; + + public int VertexCount { get { return Mesh.TriangleCount; } } + public int MaxVertexID { get { return Mesh.MaxTriangleID; } } + + public bool HasVertexNormals { get { return false; } } + public bool HasVertexColors { get { return false; } } + + public Vector3d GetVertex(int i) { return Mesh.GetTriCentroid(i); } + public Vector3f GetVertexNormal(int i) { return Vector3f.AxisY; } + public Vector3f GetVertexColor(int i) { return Vector3f.One; } + + public bool IsVertex(int tID) { return Mesh.IsTriangle(tID); } + + // iterators allow us to work with gaps in index space + public System.Collections.Generic.IEnumerable VertexIndices() { + return Mesh.TriangleIndices(); + } + + public int Timestamp { get { return Mesh.Timestamp; } } + + } + + + + } +} diff --git a/mesh_ops/RemoveOccludedTriangles.cs b/mesh_ops/RemoveOccludedTriangles.cs new file mode 100644 index 00000000..06936376 --- /dev/null +++ b/mesh_ops/RemoveOccludedTriangles.cs @@ -0,0 +1,195 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using g3; + +namespace gs +{ + /// + /// Remove "occluded" triangles, ie triangles on the "inside" of the mesh. + /// This is a fuzzy definition, current implementation is basically computing + /// something akin to ambient occlusion, and if face is fully occluded, then + /// we classify it as inside and remove it. + /// + public class RemoveOccludedTriangles + { + public DMesh3 Mesh; + public DMeshAABBTree3 Spatial; + + // indices of removed triangles. List will be empty if nothing removed + public List RemovedT = null; + + // Mesh.RemoveTriange() can return false, if that happens, this will be true + public bool RemoveFailed = false; + + // if true, then we discard tris if any vertex is occluded. + // Otherwise we discard based on tri centroids + public bool PerVertex = false; + + // we nudge points out by this amount to try to counteract numerical issues + public double NormalOffset = MathUtil.ZeroTolerance; + + // use this as winding isovalue for WindingNumber mode + public double WindingIsoValue = 0.5; + + public enum CalculationMode + { + RayParity = 0, + AnalyticWindingNumber = 1, + FastWindingNumber = 2, + SimpleOcclusionTest = 3 + } + public CalculationMode InsideMode = CalculationMode.RayParity; + + + /// + /// Set this to be able to cancel running remesher + /// + public ProgressCancel Progress = null; + + /// + /// if this returns true, abort computation. + /// + protected virtual bool Cancelled() { + return (Progress == null) ? false : Progress.Cancelled(); + } + + + + public RemoveOccludedTriangles(DMesh3 mesh) + { + Mesh = mesh; + } + + public RemoveOccludedTriangles(DMesh3 mesh, DMeshAABBTree3 spatial) + { + Mesh = mesh; + Spatial = spatial; + } + + + public virtual bool Apply() + { + DMesh3 testAgainstMesh = Mesh; + if (InsideMode == CalculationMode.RayParity) { + MeshBoundaryLoops loops = new MeshBoundaryLoops(testAgainstMesh); + if (loops.Count > 0) { + testAgainstMesh = new DMesh3(Mesh); + foreach (var loop in loops) { + if (Cancelled()) + return false; + SimpleHoleFiller filler = new SimpleHoleFiller(testAgainstMesh, loop); + filler.Fill(); + } + } + } + + DMeshAABBTree3 spatial = (Spatial != null && testAgainstMesh == Mesh) ? + Spatial : new DMeshAABBTree3(testAgainstMesh, true); + if (InsideMode == CalculationMode.AnalyticWindingNumber) + spatial.WindingNumber(Vector3d.Zero); + else if (InsideMode == CalculationMode.FastWindingNumber ) + spatial.FastWindingNumber(Vector3d.Zero); + + if (Cancelled()) + return false; + + // ray directions + List ray_dirs = null; int NR = 0; + if (InsideMode == CalculationMode.SimpleOcclusionTest) { + ray_dirs = new List(); + ray_dirs.Add(Vector3d.AxisX); ray_dirs.Add(-Vector3d.AxisX); + ray_dirs.Add(Vector3d.AxisY); ray_dirs.Add(-Vector3d.AxisY); + ray_dirs.Add(Vector3d.AxisZ); ray_dirs.Add(-Vector3d.AxisZ); + NR = ray_dirs.Count; + } + + Func isOccludedF = (pt) => { + + if (InsideMode == CalculationMode.RayParity) { + return spatial.IsInside(pt); + } else if (InsideMode == CalculationMode.AnalyticWindingNumber) { + return spatial.WindingNumber(pt) > WindingIsoValue; + } else if (InsideMode == CalculationMode.FastWindingNumber) { + return spatial.FastWindingNumber(pt) > WindingIsoValue; + } else { + for (int k = 0; k < NR; ++k) { + int hit_tid = spatial.FindNearestHitTriangle(new Ray3d(pt, ray_dirs[k])); + if (hit_tid == DMesh3.InvalidID) + return false; + } + return true; + } + }; + + bool cancel = false; + + BitArray vertices = null; + if ( PerVertex ) { + vertices = new BitArray(Mesh.MaxVertexID); + + MeshNormals normals = null; + if (Mesh.HasVertexNormals == false) { + normals = new MeshNormals(Mesh); + normals.Compute(); + } + + gParallel.ForEach(Mesh.VertexIndices(), (vid) => { + if (cancel) return; + if (vid % 10 == 0) cancel = Cancelled(); + + Vector3d c = Mesh.GetVertex(vid); + Vector3d n = (normals == null) ? Mesh.GetVertexNormal(vid) : normals[vid]; + c += n * NormalOffset; + vertices[vid] = isOccludedF(c); + }); + } + if (Cancelled()) + return false; + + RemovedT = new List(); + SpinLock removeLock = new SpinLock(); + + gParallel.ForEach(Mesh.TriangleIndices(), (tid) => { + if (cancel) return; + if (tid % 10 == 0) cancel = Cancelled(); + + bool inside = false; + if (PerVertex) { + Index3i tri = Mesh.GetTriangle(tid); + inside = vertices[tri.a] || vertices[tri.b] || vertices[tri.c]; + + } else { + Vector3d c = Mesh.GetTriCentroid(tid); + Vector3d n = Mesh.GetTriNormal(tid); + c += n * NormalOffset; + inside = isOccludedF(c); + } + + if (inside) { + bool taken = false; + removeLock.Enter(ref taken); + RemovedT.Add(tid); + removeLock.Exit(); + } + }); + + if (Cancelled()) + return false; + + if (RemovedT.Count > 0) { + MeshEditor editor = new MeshEditor(Mesh); + bool bOK = editor.RemoveTriangles(RemovedT, true); + RemoveFailed = (bOK == false); + } + + return true; + } + + + + } +} diff --git a/mesh_ops/SmoothedHoleFill.cs b/mesh_ops/SmoothedHoleFill.cs new file mode 100644 index 00000000..65d50d1c --- /dev/null +++ b/mesh_ops/SmoothedHoleFill.cs @@ -0,0 +1,263 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using g3; + +namespace gs +{ + /// + /// This fills a hole in a mesh by doing a trivial fill, optionally offsetting along a fixed vector, + /// then doing a remesh, then a laplacian smooth, then a second remesh. + /// + public class SmoothedHoleFill + { + public DMesh3 Mesh; + + // after initial coarse hole fill, (optionally) offset fill patch in this direction/distance + public Vector3d OffsetDirection = Vector3d.Zero; + public double OffsetDistance = 0.0; + + // remeshing parameters + public double TargetEdgeLength = 2.5; + public double SmoothAlpha = 1.0; + public int InitialRemeshPasses = 20; + public bool RemeshBeforeSmooth = true; + public bool RemeshAfterSmooth = true; + + // optionally allows extended customization of internal remesher + // bool argument is true before smooth, false after + public Action ConfigureRemesherF = null; + + // the laplacian smooth is what gives us the smooth fill + public bool EnableLaplacianSmooth = true; + + // higher iterations == smoother result, but more expensive + // [TODO] currently only has effect if ConstrainToHoleInterior==true (otherwise ROI expands each iteration...) + public int SmoothSolveIterations = 1; + + /// If this is true, we don't modify any triangles outside hole (often results in lower-quality fill) + public bool ConstrainToHoleInterior = false; + + + /* + * ways to specify the hole we will fill + * (you really should use FillLoop unless you have a good reason not to) + */ + + // Option 1: fill this loop specifically + public EdgeLoop FillLoop = null; + + // Option 2: identify right loop using these tris on the border of hole + public List BorderHintTris = null; + + + /* + * Outputs + */ + + /// Final fill triangles. May include triangles outside initial fill loop, if ConstrainToHoleInterior=false + public int[] FillTriangles; + + /// Final fill vertices + public int[] FillVertices; + + + public SmoothedHoleFill(DMesh3 mesh, EdgeLoop fillLoop = null) + { + this.Mesh = mesh; + this.FillLoop = fillLoop; + } + + + public bool Apply() + { + EdgeLoop useLoop = null; + + if (FillLoop == null) { + MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh, true); + if (loops.Count == 0) + return false; + + if (BorderHintTris != null) + useLoop = select_loop_tris_hint(loops); + if (useLoop == null && loops.MaxVerticesLoopIndex >= 0) + useLoop = loops[loops.MaxVerticesLoopIndex]; + } else { + useLoop = FillLoop; + } + if (useLoop == null) + return false; + + // step 1: do stupid hole fill + SimpleHoleFiller filler = new SimpleHoleFiller(Mesh, useLoop); + if (filler.Fill() == false) + return false; + + if (useLoop.Vertices.Length <= 3 ) { + FillTriangles = filler.NewTriangles; + FillVertices = new int[0]; + return true; + } + + MeshFaceSelection tris = new MeshFaceSelection(Mesh); + tris.Select(filler.NewTriangles); + + // extrude initial fill surface (this is used in socketgen for example) + if (OffsetDistance > 0) { + MeshExtrudeFaces extrude = new MeshExtrudeFaces(Mesh, tris); + extrude.ExtrudedPositionF = (v, n, vid) => { + return v + OffsetDistance * OffsetDirection; + }; + if (!extrude.Extrude()) + return false; + tris.Select(extrude.JoinTriangles); + } + + // if we aren't trying to stay inside hole, expand out a bit, + // which allows us to clean up ugly edges + if (ConstrainToHoleInterior == false) { + tris.ExpandToOneRingNeighbours(2); + tris.LocalOptimize(true, true); + } + + // remesh the initial coarse fill region + if (RemeshBeforeSmooth) { + RegionRemesher remesh = new RegionRemesher(Mesh, tris); + remesh.SetTargetEdgeLength(TargetEdgeLength); + remesh.EnableSmoothing = (SmoothAlpha > 0); + remesh.SmoothSpeedT = SmoothAlpha; + if (ConfigureRemesherF != null) + ConfigureRemesherF(remesh, true); + for (int k = 0; k < InitialRemeshPasses; ++k) + remesh.BasicRemeshPass(); + remesh.BackPropropagate(); + + tris = new MeshFaceSelection(Mesh); + tris.Select(remesh.CurrentBaseTriangles); + if (ConstrainToHoleInterior == false) + tris.LocalOptimize(true, true); + } + + if (ConstrainToHoleInterior) { + for (int k = 0; k < SmoothSolveIterations; ++k ) { + smooth_and_remesh_preserve(tris, k == SmoothSolveIterations-1); + tris = new MeshFaceSelection(Mesh); tris.Select(FillTriangles); + } + } else { + smooth_and_remesh(tris); + tris = new MeshFaceSelection(Mesh); tris.Select(FillTriangles); + } + + MeshVertexSelection fill_verts = new MeshVertexSelection(Mesh); + fill_verts.SelectInteriorVertices(tris); + FillVertices = fill_verts.ToArray(); + + return true; + } + + + + void smooth_and_remesh_preserve(MeshFaceSelection tris, bool bFinal) + { + if (EnableLaplacianSmooth) { + LaplacianMeshSmoother.RegionSmooth(Mesh, tris, 2, 2, true); + } + + if (RemeshAfterSmooth) { + MeshProjectionTarget target = (bFinal) ? MeshProjectionTarget.Auto(Mesh, tris, 5) : null; + + RegionRemesher remesh2 = new RegionRemesher(Mesh, tris); + remesh2.SetTargetEdgeLength(TargetEdgeLength); + remesh2.SmoothSpeedT = 1.0; + remesh2.SetProjectionTarget(target); + if (ConfigureRemesherF != null) + ConfigureRemesherF(remesh2, false); + for (int k = 0; k < 10; ++k) + remesh2.BasicRemeshPass(); + remesh2.BackPropropagate(); + + FillTriangles = remesh2.CurrentBaseTriangles; + } else { + FillTriangles = tris.ToArray(); + } + } + + + + void smooth_and_remesh(MeshFaceSelection tris) + { + if (EnableLaplacianSmooth) { + LaplacianMeshSmoother.RegionSmooth(Mesh, tris, 2, 2, false); + } + + if (RemeshAfterSmooth) { + tris.ExpandToOneRingNeighbours(2); + tris.LocalOptimize(true, true); + MeshProjectionTarget target = MeshProjectionTarget.Auto(Mesh, tris, 5); + + RegionRemesher remesh2 = new RegionRemesher(Mesh, tris); + remesh2.SetTargetEdgeLength(TargetEdgeLength); + remesh2.SmoothSpeedT = 1.0; + remesh2.SetProjectionTarget(target); + if (ConfigureRemesherF != null) + ConfigureRemesherF(remesh2, false); + for (int k = 0; k < 10; ++k) + remesh2.BasicRemeshPass(); + remesh2.BackPropropagate(); + + FillTriangles = remesh2.CurrentBaseTriangles; + } else { + FillTriangles = tris.ToArray(); + } + } + + + + + + + + + + EdgeLoop select_loop_tris_hint(MeshBoundaryLoops loops) + { + HashSet hint_edges = new HashSet(); + foreach ( int tid in BorderHintTris ) { + if (Mesh.IsTriangle(tid) == false) + continue; + Index3i et = Mesh.GetTriEdges(tid); + for (int j = 0; j < 3; ++j) { + if (Mesh.IsBoundaryEdge(et[j])) + hint_edges.Add(et[j]); + } + } + + + int N = loops.Count; + int best_loop = -1; + int max_votes = 0; + for ( int li = 0; li < N; ++li ) { + int votes = 0; + EdgeLoop l = loops[li]; + foreach (int eid in l.Edges) { + if (hint_edges.Contains(eid)) + votes++; + } + if ( votes > max_votes ) { + best_loop = li; + max_votes = votes; + } + } + + if (best_loop == -1) + return null; + return loops[best_loop]; + + } + + + } +} diff --git a/spatial/EditMeshSpatial.cs b/spatial/EditMeshSpatial.cs new file mode 100644 index 00000000..862c5b38 --- /dev/null +++ b/spatial/EditMeshSpatial.cs @@ -0,0 +1,105 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using g3; + +namespace gs +{ + /// + /// For use case where we are making local edits to a source mesh. We mask out + /// removed triangles from base mesh SpatialDS, and raycast new triangles separately. + /// + public class EditMeshSpatial : ISpatial + { + public DMesh3 SourceMesh; + public DMeshAABBTree3 SourceSpatial; + public DMesh3 EditMesh; + + HashSet RemovedT = new HashSet(); + HashSet AddedT = new HashSet(); + + public void RemoveTriangle(int tid) + { + if ( AddedT.Contains(tid) ) { + AddedT.Remove(tid); + } else { + RemovedT.Add(tid); + } + } + + public void AddTriangle(int tid) + { + AddedT.Add(tid); + } + + + public bool SupportsNearestTriangle { get { return false; } } + public int FindNearestTriangle(Vector3d p, double fMaxDist = double.MaxValue) { + return DMesh3.InvalidID; + } + + public bool SupportsPointContainment { get { return false; } } + public bool IsInside(Vector3d p) { return false; } + + + public bool SupportsTriangleRayIntersection { get { return true; } } + + public int FindNearestHitTriangle(Ray3d ray, double fMaxDist = double.MaxValue) + { + var save_filter = SourceSpatial.TriangleFilterF; + SourceSpatial.TriangleFilterF = source_filter; + int hit_source_tid = SourceSpatial.FindNearestHitTriangle(ray); + SourceSpatial.TriangleFilterF = save_filter; + + int hit_edit_tid; + IntrRay3Triangle3 edit_hit = find_added_hit(ref ray, out hit_edit_tid); + + if (hit_source_tid == DMesh3.InvalidID && hit_edit_tid == DMesh3.InvalidID) + return DMesh3.InvalidID; + else if (hit_source_tid == DMesh3.InvalidID) + return hit_edit_tid; + else if (hit_edit_tid == DMesh3.InvalidID) + return hit_source_tid; + + IntrRay3Triangle3 source_hit = (hit_source_tid != -1) ? + MeshQueries.TriangleIntersection(SourceMesh, hit_source_tid, ray) : null; + return (edit_hit.RayParameter < source_hit.RayParameter) ? + hit_edit_tid : hit_source_tid; + } + + bool source_filter(int tid) + { + return RemovedT.Contains(tid) == false; + } + + + IntrRay3Triangle3 find_added_hit(ref Ray3d ray, out int hit_tid) + { + hit_tid = DMesh3.InvalidID; + IntrRay3Triangle3 nearest = null; + double dNearT = double.MaxValue; + + Triangle3d tri = new Triangle3d(); + foreach ( int tid in AddedT) { + Index3i tv = EditMesh.GetTriangle(tid); + tri.V0 = EditMesh.GetVertex(tv.a); + tri.V1 = EditMesh.GetVertex(tv.b); + tri.V2 = EditMesh.GetVertex(tv.c); + IntrRay3Triangle3 intr = new IntrRay3Triangle3(ray, tri); + if ( intr.Find() && intr.RayParameter < dNearT ) { + dNearT = intr.RayParameter; + hit_tid = tid; + nearest = intr; + } + } + return nearest; + } + + + + } +} diff --git a/spatial/MeshScalarSamplingGrid.cs b/spatial/MeshScalarSamplingGrid.cs new file mode 100644 index 00000000..927c4ce8 --- /dev/null +++ b/spatial/MeshScalarSamplingGrid.cs @@ -0,0 +1,344 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Threading; +using g3; + +namespace gs +{ + + /// + /// Sample a scalar function on a discrete grid. Can sample full grid, or + /// compute values around a specific iso-contour and then fill in rest of grid + /// with correctly-signed values via fast sweeping (this is the default) + /// + /// TODO: + /// - I think we are over-exploring the grid most of the time. eg along an x-ray that + /// intersects the surface, we only need at most 2 cells, but we are computing at least 3, + /// and possibly 5. + /// - it may be better to use something like bloomenthal polygonizer continuation? where we + /// are keeping track of active edges instead of active cells? + /// + /// + public class MeshScalarSamplingGrid + { + public DMesh3 Mesh; + public Func ScalarF; + + // size of cubes in the grid + public double CellSize; + + // how many cells around border should we keep + public int BufferCells = 1; + + // Should we compute values at all grid cells (expensive!!) or only in narrow band. + // In narrow-band mode, we guess rest of values by propagating along x-rows + public enum ComputeModes + { + FullGrid = 0, + NarrowBand = 1 + } + public ComputeModes ComputeMode = ComputeModes.NarrowBand; + + // in narrow-band mode, if mesh is not closed, we will explore space around this iso-value + public float IsoValue = 0.5f; + + // in NarrowBand mode, we compute mesh SDF grid, if true then it can be accessed + // via SDFGrid property after Compute() + public bool WantMeshSDFGrid = true; + + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + + public bool DebugPrint = false; + + // computed results + Vector3f grid_origin; + DenseGrid3f scalar_grid; + + // sdf grid we compute in narrow-band mode + MeshSignedDistanceGrid mesh_sdf; + + + public MeshScalarSamplingGrid(DMesh3 mesh, double cellSize, Func scalarF) + { + Mesh = mesh; + ScalarF = scalarF; + CellSize = cellSize; + } + + + public void Compute() + { + // figure out origin & dimensions + AxisAlignedBox3d bounds = Mesh.CachedBounds; + + float fBufferWidth = 2 * BufferCells * (float)CellSize; + grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One; + Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One; + int ni = (int)((max.x - grid_origin.x) / (float)CellSize) + 1; + int nj = (int)((max.y - grid_origin.y) / (float)CellSize) + 1; + int nk = (int)((max.z - grid_origin.z) / (float)CellSize) + 1; + + scalar_grid = new DenseGrid3f(); + if ( ComputeMode == ComputeModes.FullGrid ) + make_grid_dense(grid_origin, (float)CellSize, ni, nj, nk, scalar_grid); + else + make_grid(grid_origin, (float)CellSize, ni, nj, nk, scalar_grid); + } + + + + public Vector3i Dimensions { + get { return new Vector3i(scalar_grid.ni, scalar_grid.nj, scalar_grid.nk); } + } + + /// + /// scalar-values grid available after calling Compute() + /// + public DenseGrid3f Grid { + get { return scalar_grid; } + } + + /// + /// Origin of the grid, in same coordinates as mesh + /// + public Vector3f GridOrigin { + get { return grid_origin; } + } + + + /// + /// If ComputeMode==NarrowBand, then we internally compute a signed-distance grid, + /// which will hang onto + /// + public MeshSignedDistanceGrid SDFGrid { + get { return mesh_sdf; } + } + + + public float this[int i, int j, int k] { + get { return scalar_grid[i, j, k]; } + } + + public Vector3f CellCenter(int i, int j, int k) + { + return new Vector3f((float)i * CellSize + grid_origin.x, + (float)j * CellSize + grid_origin.y, + (float)k * CellSize + grid_origin.z); + } + + + + + void make_grid(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f scalars) + { + scalars.resize(ni, nj, nk); + scalars.assign(float.MaxValue); // sentinel + + if (DebugPrint) System.Console.WriteLine("start"); + + // Ok, because the whole idea is that the surface might have holes, we are going to + // compute values along known triangles and then propagate the computed region outwards + // until any iso-sign-change is surrounded. + // To seed propagation, we compute unsigned SDF and then compute values for any voxels + // containing surface (ie w/ distance smaller than cellsize) + + // compute unsigned SDF + MeshSignedDistanceGrid sdf = new MeshSignedDistanceGrid(Mesh, CellSize) { ComputeSigns = false }; + sdf.CancelF = this.CancelF; + sdf.Compute(); + if (CancelF()) + return; + + DenseGrid3f distances = sdf.Grid; + if (WantMeshSDFGrid) + mesh_sdf = sdf; + if (DebugPrint) System.Console.WriteLine("done initial sdf"); + + // compute values at surface voxels + double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; + gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (jk) => { + if (CancelF()) + return; + for (int i = 0; i < ni; ++i) { + Vector3i ijk = new Vector3i(i, jk.y, jk.z); + float dist = distances[ijk]; + // this could be tighter? but I don't think it matters... + if (dist < CellSize) { + Vector3d gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); + scalars[ijk] = (float)ScalarF(gx); + } + } + }); + if (CancelF()) + return; + + if (DebugPrint) System.Console.WriteLine("done narrow-band"); + + // Now propagate outwards from computed voxels. + // Current procedure is to check 26-neighbours around each 'front' voxel, + // and if there are any sign changes, that neighbour is added to front. + // Front is initialized w/ all voxels we computed above + + AxisAlignedBox3i bounds = scalars.Bounds; + bounds.Max -= Vector3i.One; + + // since we will be computing new values as necessary, we cannot use + // grid to track whether a voxel is 'new' or not. + // So, using 3D bitmap intead - is updated at end of each pass. + Bitmap3 bits = new Bitmap3(new Vector3i(ni, nj, nk)); + List cur_front = new List(); + foreach (Vector3i ijk in scalars.Indices()) { + if (scalars[ijk] != float.MaxValue) { + cur_front.Add(ijk); + bits[ijk] = true; + } + } + if (CancelF()) + return; + + // Unique set of 'new' voxels to compute in next iteration. + HashSet queue = new HashSet(); + SpinLock queue_lock = new SpinLock(); + + while (true) { + if (CancelF()) + return; + + // can process front voxels in parallel + bool abort = false; int iter_count = 0; + gParallel.ForEach(cur_front, (ijk) => { + Interlocked.Increment(ref iter_count); + if (iter_count % 100 == 0) + abort = CancelF(); + if (abort) + return; + + float val = scalars[ijk]; + + // check 26-neighbours to see if we have a crossing in any direction + for (int k = 0; k < 26; ++k) { + Vector3i nijk = ijk + gIndices.GridOffsets26[k]; + if (bounds.Contains(nijk) == false) + continue; + float val2 = scalars[nijk]; + if (val2 == float.MaxValue) { + Vector3d gx = new Vector3d((float)nijk.x * dx + origin[0], (float)nijk.y * dx + origin[1], (float)nijk.z * dx + origin[2]); + val2 = (float)ScalarF(gx); + scalars[nijk] = val2; + } + if (bits[nijk] == false) { + // this is a 'new' voxel this round. + // If we have an iso-crossing, add it to the front next round + bool crossing = (val < IsoValue && val2 > IsoValue) || + (val > IsoValue && val2 < IsoValue); + if (crossing) { + bool taken = false; + queue_lock.Enter(ref taken); + queue.Add(nijk); + queue_lock.Exit(); + } + } + } + }); + if (DebugPrint) System.Console.WriteLine("front has {0} voxels", queue.Count); + if (queue.Count == 0) + break; + + // update known-voxels list and create front for next iteration + foreach (Vector3i idx in queue) + bits[idx] = true; + cur_front.Clear(); + cur_front.AddRange(queue); + queue.Clear(); + } + if (DebugPrint) System.Console.WriteLine("done front-prop"); + + if (DebugPrint) { + int filled = 0; + foreach (Vector3i ijk in scalars.Indices()) { + if (scalars[ijk] != float.MaxValue) + filled++; + } + System.Console.WriteLine("filled: {0} / {1} - {2}%", filled, ni * nj * nk, + (double)filled / (double)(ni * nj * nk) * 100.0); + } + + if (CancelF()) + return; + + // fill in the rest of the grid by propagating know values + fill_spans(ni, nj, nk, scalars); + + if (DebugPrint) System.Console.WriteLine("done sweep"); + + + } + + + + + + + + + + + void make_grid_dense(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f scalars) + { + scalars.resize(ni, nj, nk); + + bool abort = false; int count = 0; + gParallel.ForEach(scalars.Indices(), (ijk) => { + Interlocked.Increment(ref count); + if (count % 100 == 0) + abort = CancelF(); + if (abort) + return; + + Vector3d gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); + scalars[ijk] = (float)ScalarF(gx); + }); + + } // end make_level_set_3 + + + + + + void fill_spans(int ni, int nj, int nk, DenseGrid3f scalars) + { + gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (idx) => { + int j = idx.y, k = idx.z; + float last = scalars[0, j, k]; + if (last == float.MaxValue) + last = 0; + for (int i = 0; i < ni; ++i) { + if (scalars[i, j, k] == float.MaxValue) { + scalars[i, j, k] = last; + } else { + last = scalars[i, j, k]; + if (last < IsoValue) // propagate zeros on outside + last = 0; + } + } + }); + } + + + + + + + + + + + } +} diff --git a/spatial/MeshWindingNumberGrid.cs b/spatial/MeshWindingNumberGrid.cs new file mode 100644 index 00000000..ebddcb1a --- /dev/null +++ b/spatial/MeshWindingNumberGrid.cs @@ -0,0 +1,349 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using System.Threading; +using g3; + +namespace gs +{ + + /// + /// Sample mesh winding number (MWN) on a discrete grid. Can sample full grid, or + /// compute MWN values along a specific iso-contour and then fill in rest of grid + /// with correctly-signed values via fast sweeping (this is the default) + /// + /// TODO: + /// - I think we are over-exploring the grid most of the time. eg along an x-ray that + /// intersects the surface, we only need at most 2 cells, but we are computing at least 3, + /// and possibly 5. + /// - it may be better to use something like bloomenthal polygonizer continuation? where we + /// are keeping track of active edges instead of active cells? + /// + /// + public class MeshWindingNumberGrid + { + public DMesh3 Mesh; + public DMeshAABBTree3 MeshSpatial; + + // size of cubes in the grid + public double CellSize; + + // how many cells around border should we keep + public int BufferCells = 1; + + // Should we compute MWN at all grid cells (expensive!!) or only in narrow band. + // In narrow-band mode, we guess rest of MWN values by propagating along x-rows + public enum ComputeModes + { + FullGrid = 0, + NarrowBand = 1 + } + public ComputeModes ComputeMode = ComputeModes.NarrowBand; + + // in narrow-band mode, if mesh is not closed, we will explore space around + // this MWN iso-value + public float WindingIsoValue = 0.5f; + + // in NarrowBand mode, we compute mesh SDF grid, if true then it can be accessed + // via SDFGrid property after Compute() + public bool WantMeshSDFGrid = true; + + /// if this function returns true, we should abort calculation + public Func CancelF = () => { return false; }; + + public bool DebugPrint = false; + + // computed results + Vector3f grid_origin; + DenseGrid3f winding_grid; + + // sdf grid we compute in narrow-band mode + MeshSignedDistanceGrid mesh_sdf; + + + public MeshWindingNumberGrid(DMesh3 mesh, DMeshAABBTree3 spatial, double cellSize) + { + Mesh = mesh; + MeshSpatial = spatial; + CellSize = cellSize; + } + + + public void Compute() + { + // figure out origin & dimensions + AxisAlignedBox3d bounds = Mesh.CachedBounds; + + float fBufferWidth = 2 * BufferCells * (float)CellSize; + grid_origin = (Vector3f)bounds.Min - fBufferWidth * Vector3f.One; + Vector3f max = (Vector3f)bounds.Max + fBufferWidth * Vector3f.One; + int ni = (int)((max.x - grid_origin.x) / (float)CellSize) + 1; + int nj = (int)((max.y - grid_origin.y) / (float)CellSize) + 1; + int nk = (int)((max.z - grid_origin.z) / (float)CellSize) + 1; + + winding_grid = new DenseGrid3f(); + if ( ComputeMode == ComputeModes.FullGrid ) + make_grid_dense(grid_origin, (float)CellSize, ni, nj, nk, winding_grid); + else + make_grid(grid_origin, (float)CellSize, ni, nj, nk, winding_grid); + } + + + + public Vector3i Dimensions { + get { return new Vector3i(winding_grid.ni, winding_grid.nj, winding_grid.nk); } + } + + /// + /// winding-number grid available after calling Compute() + /// + public DenseGrid3f Grid { + get { return winding_grid; } + } + + /// + /// Origin of the winding-number grid, in same coordinates as mesh + /// + public Vector3f GridOrigin { + get { return grid_origin; } + } + + + /// + /// If ComputeMode==NarrowBand, then we internally compute a signed-distance grid, + /// which will hang onto + /// + public MeshSignedDistanceGrid SDFGrid { + get { return mesh_sdf; } + } + + + public float this[int i, int j, int k] { + get { return winding_grid[i, j, k]; } + } + + public Vector3f CellCenter(int i, int j, int k) + { + return new Vector3f((float)i * CellSize + grid_origin.x, + (float)j * CellSize + grid_origin.y, + (float)k * CellSize + grid_origin.z); + } + + + + + void make_grid(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f winding) + { + winding.resize(ni, nj, nk); + winding.assign(float.MaxValue); // sentinel + + // seed MWN cache + MeshSpatial.WindingNumber(Vector3d.Zero); + + if (DebugPrint) System.Console.WriteLine("start"); + + // Ok, because the whole idea is that the surface might have holes, we are going to + // compute MWN along known triangles and then propagate the computed region outwards + // until any MWN iso-sign-change is surrounded. + // To seed propagation, we compute unsigned SDF and then compute MWN for any voxels + // containing surface (ie w/ distance smaller than cellsize) + + // compute unsigned SDF + MeshSignedDistanceGrid sdf = new MeshSignedDistanceGrid(Mesh, CellSize) { ComputeSigns = false }; + sdf.CancelF = this.CancelF; + sdf.Compute(); + if (CancelF()) + return; + + DenseGrid3f distances = sdf.Grid; + if (WantMeshSDFGrid) + mesh_sdf = sdf; + if (DebugPrint) System.Console.WriteLine("done initial sdf"); + + // compute MWN at surface voxels + double ox = (double)origin[0], oy = (double)origin[1], oz = (double)origin[2]; + gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (jk) => { + if (CancelF()) + return; + for (int i = 0; i < ni; ++i) { + Vector3i ijk = new Vector3i(i, jk.y, jk.z); + float dist = distances[ijk]; + // this could be tighter? but I don't think it matters... + if (dist < CellSize) { + Vector3d gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); + winding[ijk] = (float)MeshSpatial.WindingNumber(gx); + } + } + }); + if (CancelF()) + return; + + if (DebugPrint) System.Console.WriteLine("done narrow-band"); + + // Now propagate outwards from computed voxels. + // Current procedure is to check 26-neighbours around each 'front' voxel, + // and if there are any MWN sign changes, that neighbour is added to front. + // Front is initialized w/ all voxels we computed above + + AxisAlignedBox3i bounds = winding.Bounds; + bounds.Max -= Vector3i.One; + + // since we will be computing new MWN values as necessary, we cannot use + // winding grid to track whether a voxel is 'new' or not. + // So, using 3D bitmap intead - is updated at end of each pass. + Bitmap3 bits = new Bitmap3(new Vector3i(ni, nj, nk)); + List cur_front = new List(); + foreach (Vector3i ijk in winding.Indices()) { + if (winding[ijk] != float.MaxValue) { + cur_front.Add(ijk); + bits[ijk] = true; + } + } + if (CancelF()) + return; + + // Unique set of 'new' voxels to compute in next iteration. + HashSet queue = new HashSet(); + SpinLock queue_lock = new SpinLock(); + + while (true) { + if (CancelF()) + return; + + // can process front voxels in parallel + bool abort = false; int iter_count = 0; + gParallel.ForEach(cur_front, (ijk) => { + Interlocked.Increment(ref iter_count); + if (iter_count % 100 == 0) + abort = CancelF(); + if (abort) + return; + + float val = winding[ijk]; + + // check 26-neighbours to see if we have a crossing in any direction + for (int k = 0; k < 26; ++k) { + Vector3i nijk = ijk + gIndices.GridOffsets26[k]; + if (bounds.Contains(nijk) == false) + continue; + float val2 = winding[nijk]; + if (val2 == float.MaxValue) { + Vector3d gx = new Vector3d((float)nijk.x * dx + origin[0], (float)nijk.y * dx + origin[1], (float)nijk.z * dx + origin[2]); + val2 = (float)MeshSpatial.WindingNumber(gx); + winding[nijk] = val2; + } + if (bits[nijk] == false) { + // this is a 'new' voxel this round. + // If we have a MWN-iso-crossing, add it to the front next round + bool crossing = (val < WindingIsoValue && val2 > WindingIsoValue) || + (val > WindingIsoValue && val2 < WindingIsoValue); + if (crossing) { + bool taken = false; + queue_lock.Enter(ref taken); + queue.Add(nijk); + queue_lock.Exit(); + } + } + } + }); + if (DebugPrint) System.Console.WriteLine("front has {0} voxels", queue.Count); + if (queue.Count == 0) + break; + + // update known-voxels list and create front for next iteration + foreach (Vector3i idx in queue) + bits[idx] = true; + cur_front.Clear(); + cur_front.AddRange(queue); + queue.Clear(); + } + if (DebugPrint) System.Console.WriteLine("done front-prop"); + + if (DebugPrint) { + int filled = 0; + foreach (Vector3i ijk in winding.Indices()) { + if (winding[ijk] != float.MaxValue) + filled++; + } + System.Console.WriteLine("filled: {0} / {1} - {2}%", filled, ni * nj * nk, + (double)filled / (double)(ni * nj * nk) * 100.0); + } + + if (CancelF()) + return; + + // fill in the rest of the grid by propagating know MWN values + fill_spans(ni, nj, nk, winding); + + if (DebugPrint) System.Console.WriteLine("done sweep"); + + + } + + + + + + + + + + + void make_grid_dense(Vector3f origin, float dx, + int ni, int nj, int nk, + DenseGrid3f winding) + { + winding.resize(ni, nj, nk); + + MeshSpatial.WindingNumber(Vector3d.Zero); + bool abort = false; int count = 0; + gParallel.ForEach(winding.Indices(), (ijk) => { + Interlocked.Increment(ref count); + if (count % 100 == 0) + abort = CancelF(); + if (abort) + return; + + Vector3d gx = new Vector3d((float)ijk.x * dx + origin[0], (float)ijk.y * dx + origin[1], (float)ijk.z * dx + origin[2]); + winding[ijk] = (float)MeshSpatial.WindingNumber(gx); + }); + + } // end make_level_set_3 + + + + + + void fill_spans(int ni, int nj, int nk, DenseGrid3f winding) + { + gParallel.ForEach(gIndices.Grid3IndicesYZ(nj, nk), (idx) => { + int j = idx.y, k = idx.z; + float last = winding[0, j, k]; + if (last == float.MaxValue) + last = 0; + for (int i = 0; i < ni; ++i) { + if (winding[i, j, k] == float.MaxValue) { + winding[i, j, k] = last; + } else { + last = winding[i, j, k]; + if (last < WindingIsoValue) // propagate zeros on outside + last = 0; + } + } + }); + } + + + + + + + + + + + } +} diff --git a/spatial/PointSetHashtable.cs b/spatial/PointSetHashtable.cs new file mode 100644 index 00000000..0805dc16 --- /dev/null +++ b/spatial/PointSetHashtable.cs @@ -0,0 +1,114 @@ +// Copyright (c) Ryan Schmidt (rms@gradientspace.com) - All Rights Reserved +// Distributed under the Boost Software License, Version 1.0. http://www.boost.org/LICENSE_1_0.txt +using System; +using System.Collections.Generic; +using g3; + +namespace gs +{ + public class PointSetHashtable + { + IPointSet Points; + DSparseGrid3 Grid; + ShiftGridIndexer3 indexF; + + Vector3d Origin; + double CellSize; + + public PointSetHashtable(IPointSet points) + { + this.Points = points; + } + + + public void Build(int maxAxisSubdivs = 64) { + AxisAlignedBox3d bounds = BoundsUtil.Bounds(Points); + double cellsize = bounds.MaxDim / (double)maxAxisSubdivs; + Build(cellsize, bounds.Min); + } + + public void Build(double cellSize, Vector3d origin) { + Origin = origin; + CellSize = cellSize; + indexF = new ShiftGridIndexer3(Origin, CellSize); + + Grid = new DSparseGrid3(new PointList()); + + foreach ( int vid in Points.VertexIndices() ) { + Vector3d v = Points.GetVertex(vid); + Vector3i idx = indexF.ToGrid(v); + PointList cell = Grid.Get(idx); + cell.Add(vid); + } + } + + + + public bool FindInBall(Vector3d pt, double r, int[] buffer, out int buffer_count ) { + buffer_count = 0; + + double halfCell = CellSize * 0.5; + Vector3i idx = indexF.ToGrid(pt); + Vector3d center = indexF.FromGrid(idx) + halfCell * Vector3d.One; + + if (r > CellSize) + throw new ArgumentException("PointSetHashtable.FindInBall: large radius unsupported"); + + double r2 = r * r; + + // check all in this cell + PointList center_cell = Grid.Get(idx, false); + if (center_cell != null) { + foreach (int vid in center_cell) { + if (pt.DistanceSquared(Points.GetVertex(vid)) < r2) { + if (buffer_count == buffer.Length) + return false; + buffer[buffer_count++] = vid; + } + } + } + + // if we are close enough to cell border we need to check nbrs + // [TODO] could iterate over fewer cells here, if r is bounded by CellSize, + // then we should only ever need to look at 3, depending on which octant we are in. + if ( (pt-center).MaxAbs + r > halfCell ) { + for (int ci = 0; ci < 26; ++ci) { + Vector3i ioffset = gIndices.GridOffsets26[ci]; + + // if we are within r from face, we need to look into it + Vector3d ptToFaceCenter = new Vector3d( + center.x + halfCell * ioffset.x - pt.x, + center.y + halfCell * ioffset.y - pt.y, + center.z + halfCell * ioffset.z - pt.z); + if (ptToFaceCenter.MinAbs > r) + continue; + + PointList ncell = Grid.Get(idx + ioffset, false); + if ( ncell != null ) { + foreach (int vid in ncell) { + if (pt.DistanceSquared(Points.GetVertex(vid)) < r2) { + if (buffer_count == buffer.Length) + return false; + buffer[buffer_count++] = vid; + } + } + } + } + } + + return true; + } + + + + + + + public class PointList : List, IGridElement3 { + public IGridElement3 CreateNewGridElement(bool bCopy) { + return new PointList(); + } + } + + } +} From 2309a501ada4d2f8dfba8628388d1debdbb851ad Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 7 Mar 2019 01:22:30 -0500 Subject: [PATCH 223/225] update readme --- README.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 953a4bf3..5d4b30b4 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,10 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - vertices can be pinned to fixed positions - vertices can be constrained to an IProjectionTarget - eg 3D polylines, smooth curves, surfaces, etc - **MeshConstraintUtil** constructs common constraint situations +- **RemesherPro**: extension of Remesher that can remesh much more quickly + - FastestRemesh() uses active-set queue to converge, instead of fixed full-mesh passes + - SharpEdgeReprojectionRemesh() tries to remesh while aligning triangle face normals to the projection target, in an attempt to preserve sharp edges + - FastSplitIteration() quickly splits edges to increase available vertex resolution - **RegionRemesher**: applies *Remesher* to sub-region of a *DMesh3*, via *DSubmesh3* - boundary of sub-region automatically preserved - *BackPropropagate()* function integrates submesh back into input mesh @@ -221,8 +225,10 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - **TubeGenerator**: polygon swept along polyline - **Curve3Axis3RevolveGenerator**: 3D polyline revolved around 3D axis - **Curve3Curve3RevolveGenerator**: 3D polyline revolved around 3D polyline (!) + - **TriangulatedPolygonGenerator**: triangulate 2D polygon-with-holes - **VoxelSurfaceGenerator**: generates minecraft-y voxel mesh surface - **MarchingCubes**: multi-threaded triangulation of implicit functions / scalar fields + - **MarchingCubesPro**: continuation-method approach to marching cubes that explores isosurface from seed points (more efficient but may miss things if seed points are insufficient) ## Mesh Selections @@ -249,14 +255,32 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - **MeshExtrudeMesh**: extrude all faces of mesh and stitch boundaries w/ triangle strips - **MeshICP**: basic iterative-closest-point alignment to target surface - **MeshInsertUVPolyCurve**: insert a 2D polyline (optionally closed) into a 2D mesh +- **MeshInsertPolygon**: insert a 2D polygon-with-holes into a 2D mesh and return set of triangles "inside" polygon +- **MeshInsertProjectedPolygon**: variant of MeshInsertPolygon that inserts 2D polygon onto 3D mesh surface via projection plane - **MeshIterativeSmooth**: standard iterative vertex-laplacian smoothing with uniform, cotan, mean-value weights - **MeshLocalParam**: calculate Discrete Exponential Map uv-coords around a point on mesh - **MeshLoopClosure**: cap open region of mesh with a plane - **MeshLoopSmooth**: smooth an embedded *EdgeLoop* of a mesh - **MeshPlaneCut**: cut a mesh with a plane, return new **EdgeLoop**s and **EdgeSpans**, and optionally fill holes - **RegionOperator**: support class that makes it easy to extract a submesh and safely re-integrate it back into base mesh. IE like RegionRemesher, but you can do arbitrary changes to the submesh (as long as you preserve boundary). -- **SimpleHoleFiller**: topological filling of an open boundary edge loop. No attempt to preserve shape whatsoever! +- **MeshStitchLoops**: Stitch together two edge loops without any constraint that they have the same vertex count +- **MeshTrimLoop**: trim mesh with 3D polyline curve lying on mesh faces (approximately) - **MeshIsoCurve**: compute piecewise-linear iso-curves of a function on a mesh, as a **DGraph3** +- **MeshTopology**: Extract mesh sharp-edge-path topology based on crease angle +- **MeshAssembly**: Decompose mesh into submeshes based on connected solids and open patches +- **MeshSpatialSort**: sorts set of mesh components into "solids" (each solid is outer mesh and contained cavity meshes) +- **MeshMeshCut**: Cut one mesh with another, and optionally remove contained regions +- **MeshBoolean**: Apply **MeshMeshCut** to each of a pair of meshes, and then try to resample cut boundaries so they have same vertices. **This is not a robust mesh boolean!** +- **SimpleHoleFiller**: topological filling of an open boundary edge loop. No attempt to preserve shape whatsoever! +- **SmoothedHoleFill**: fill hole in mesh smoothly, ie with (approximate) boundary tangent continuity +- **MinimalHoleFill**: construct "minimal" fill that is often developable (recovers sharp edges well) +- **PlanarHoleFiller**: fill planar holes in mesh by mapping to 2D, handles nested holes (eg from plane cut through torus) +- **PlanarSpansFiller**: try to fill disconnected set of planar spans, by chaining them (WIP) +- **MeshRepairOrientation**: make triangle winding order consistent across mesh connected components (if possible), and then assign global orientation via spatial sorting/nesting +- **MergeCoincidentEdges**: weld coincident open boundary edges of mesh (more robust than weld vertices!) +- **RemoveDuplicateTriangles**: remove duplicate triangles of mesh +- **RemoteOccludedTriangles**: remove triangles that are "occluded" under various definitions +- **MeshAutoRepair**: apply many of the above algorithms in an attempt to automatically "repair" an input mesh, where "repaired" means the mesh is closed and manifold. ## Spatial Data Structures @@ -277,6 +301,9 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - **Bitmap3**: 3D dense bitmap - **BiGrid3**: two-level DSparseGrid3 - **MeshSignedDistanceGrid**: 3D fast-marching construction of narrow-band level set / voxel-distance-field for mesh +- **MeshScalarSamplingGrid**: Samples scalar function on 3D grid. Can sample full grid or narrow band around specific iso-contour +- **MeshWindingNumberGrid**: MeshScalarSamplingGrid variant specifically for computing narrow-band Mesh Winding Number field on meshes with holes (finds narrow-band in hole regions via flood-fill) +- **CachingMeshSDF**: variant of MeshSignedDistanceGrid that does lazy evaluation of distances (eg for use with continuation-method MarchingCubesPro) - **IProjectionTarget** implementations for DCurve3, DMesh3, Plane3, Circle3d, Cylinder3d, etc, for use w/ reprojection in Remesher and other algorithms - **IIntersectionTarget** implementations for DMesh3, transformed DMesh3, Plane3 @@ -323,6 +350,7 @@ Several tutorials for using g3Sharp have been posted on the Gradientspace blog: - **Cylinder3d** - **DenseGridTrilinearImplicit**: trilinear interpolant of 3D grid +- **CachingDenseGridTrilinearImplicit**: variant of DenseGridTrilinearImplicit that does lazy evaluation of grid values based on an implicit function ## I/O From 79829341d6c225375128c32cd4720dd48f970c6e Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Thu, 7 Mar 2019 01:22:25 -0500 Subject: [PATCH 224/225] update nuget key --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 59d2adc7..9a1be15f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,7 @@ deploy: provider: NuGet # server: # remove to push to NuGet.org api_key: - secure: NwCvdkjy/Jpu+Cev4PeXnCdyEtf5F5O4bDtBoXisEKkez6R+U5Wk12hdHNa1NTFT + secure: DIcqDc7g8mWxhl3/G0MxoGVSnRJzdMvPZdJM7NbYjuy1tFhcF8hWA+nNoGntESX7 on: appveyor_repo_tag: true skip_symbols: false From 8f185f19a96966237ef631d97da567f380e10b6b Mon Sep 17 00:00:00 2001 From: Ryan Schmidt Date: Mon, 19 Dec 2022 19:36:40 -0500 Subject: [PATCH 225/225] Update README.md add forward to geometry4Sharp fork --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5d4b30b4..5a5242a5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# A Short Note about the future of geometry3Sharp + +I have not been able to work on or maintain geometry3Sharp for the past few years, due to some restrictive employment-contract terms. Various forks now exist that have active maintainers, and I would recommend you consider switching to one of those. In particular I would recommend the geometry4Sharp fork being developed by New Wheel Technology (_who also does C# development consulting, if you are looking for that_): + +https://github.com/NewWheelTech/geometry4Sharp + # geometry3Sharp Open-Source (Boost-license) C# library for geometric computing.