diff --git a/.gitignore b/.gitignore
index 01d29097..51bef3c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# Folders
+.vs/
+
# Compiled Object files
*.slo
*.lo
@@ -32,4 +35,5 @@ geometry3Sharp/obj
geometry3Sharp.csproj.user
bin
obj
+.vs
*.meta
diff --git a/README.md b/README.md
index 1a92a056..5a5242a5 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,14 @@
+# 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.
-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 +16,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 +37,14 @@ 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
+- [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
@@ -47,6 +65,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
@@ -164,6 +189,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
@@ -202,8 +231,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
@@ -230,24 +261,44 @@ 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
-- **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
@@ -256,6 +307,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
@@ -302,6 +356,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
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
diff --git a/color/Colorf.cs b/color/Colorf.cs
index 46d2e745..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)
@@ -222,6 +225,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)
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/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/core/BufferUtil.cs b/core/BufferUtil.cs
index b1511875..59bba42b 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,260 @@ 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;
+ }
+
+
+ ///
+ /// 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;
+ }
+
+
+ ///
+ /// 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;
+ }
+
+
+
+
+ ///
+ /// Compress a byte buffer using Deflate/ZLib compression.
+ ///
+ 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;
+
+ 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;
diff --git a/core/DVector.cs b/core/DVector.cs
index 3c488093..fcda514e 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;
@@ -53,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);
}
@@ -149,6 +147,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;
@@ -177,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]
@@ -372,6 +391,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
{
diff --git a/core/DijkstraGraphDistance.cs b/core/DijkstraGraphDistance.cs
index 80a17de6..63bf25b5 100644
--- a/core/DijkstraGraphDistance.cs
+++ b/core/DijkstraGraphDistance.cs
@@ -14,10 +14,12 @@ 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
{
+ public const float InvalidValue = float.MaxValue;
///
/// if you enable this, then you can call GetOrder()
@@ -58,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 };
}
@@ -113,10 +115,50 @@ 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);
}
+
+ ///
+ /// 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
+ ///
public void Reset()
{
if ( SparseNodes != null ) {
@@ -145,7 +187,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 +284,121 @@ 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 = InvalidValue)
+ {
+ 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);
+ }
+ }
+
+
+
+
+
+
+
+ ///
+ /// 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 = InvalidValue)
+ {
+ 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()
///
@@ -251,23 +408,27 @@ 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;
}
}
+ ///
+ /// 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 +438,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];
@@ -303,12 +501,16 @@ 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;
SparseQueue.Update(nbr, nbr_dist);
}
} else {
+ nbr.parent = parent;
SparseQueue.Enqueue(nbr, nbr_dist);
}
}
@@ -317,9 +519,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);
}
@@ -337,6 +539,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;
@@ -344,24 +549,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);
- }
-
}
}
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/core/ProfileUtil.cs b/core/ProfileUtil.cs
index 1db33bc7..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}";
}
@@ -104,9 +113,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)
@@ -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();
}
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/core/RefCountVector.cs b/core/RefCountVector.cs
index a51b5746..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,18 +75,30 @@ 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() {
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;
+ }
}
}
@@ -90,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];
}
@@ -106,6 +124,73 @@ 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) {
+ int j = ref_counts.size;
+ while (j < index) {
+ ref_counts.push_back(invalid);
+ free_indices.push_back(j);
+ ++j;
+ }
+ 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 ) {
+ free_indices[i] = invalid;
+ ref_counts[index] = 1;
+ used_count++;
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+
+ ///
+ /// 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)
{
@@ -113,7 +198,6 @@ public void set_Unsafe(int index, short count)
}
// todo:
- // insert
// remove
// clear
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/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/core/Snapping.cs b/core/Snapping.cs
index 9314f79e..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;
}
@@ -29,5 +30,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, double offset=0)
+ {
+ return SnapToIncrementSigned(fValue - offset, fIncrement, true) + offset;
+ }
+
+ public static double SnapToIncrementHigh(double fValue, double fIncrement, double offset = 0)
+ {
+ return SnapToIncrementSigned(fValue - offset, fIncrement, false) + offset;
+ }
}
}
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; } }
+ }
+
+
///
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);
}
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
}
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/core/gParallel.cs b/core/gParallel.cs
index 23f02c61..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);
@@ -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/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;
+ }
+ }
+
+
+
}
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/Circle2.cs b/curve/Circle2.cs
index 41b5a60b..3cab0789 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,21 @@ 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;
+ }
+
+ ///
+ /// 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);
+ }
}
}
diff --git a/curve/CurveUtils.cs b/curve/CurveUtils.cs
index 042e8622..bc5a9db5 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) {
@@ -38,6 +51,7 @@ public static double ArcLength(IEnumerable vertices) {
foreach (Vector3d v in vertices) {
if (i++ > 0)
sum += (v - prev).Length;
+ prev = v;
}
return sum;
}
@@ -61,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;
}
}
@@ -136,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];
+ }
+ }
+ }
+ }
+
+
+
+
}
@@ -151,7 +211,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/CurveUtils2.cs b/curve/CurveUtils2.cs
index 7c32733a..ed1bf710 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;
@@ -215,5 +226,99 @@ 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);
+ }
+ }
+
+
+
+ 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
+ ///
+ 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/DCurve3.cs b/curve/DCurve3.cs
index ff723b5e..2eb8a2a4 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?
@@ -50,6 +54,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++;
@@ -58,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];
@@ -103,6 +123,11 @@ public void RemoveVertex(int idx)
Timestamp++;
}
+ public void Reverse() {
+ vertices.Reverse();
+ Timestamp++;
+ }
+
public Vector3d this[int key]
{
@@ -114,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 {
@@ -122,57 +147,107 @@ 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;
+ 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]);
+ }
}
-
- public AxisAlignedBox3d GetBoundingBox()
+ public Vector3d PointAt(int iSegment, double fSegT)
{
- // [RMS] problem w/ readonly because vector is a class...
- //AxisAlignedBox3d box = AxisAlignedBox3d.Empty;
- AxisAlignedBox3d box = new AxisAlignedBox3d(false);
+ Segment3d seg = new Segment3d(vertices[iSegment], vertices[(iSegment + 1) % vertices.Count]);
+ return seg.PointAt(fSegT);
+ }
+
+
+ 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]);
+ }
}
+ 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
+ ///
+ 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;
@@ -189,6 +264,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;
@@ -220,5 +298,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/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/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/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
diff --git a/curve/DGraph3Util.cs b/curve/DGraph3Util.cs
index a056a3e7..1bf54209 100644
--- a/curve/DGraph3Util.cs
+++ b/curve/DGraph3Util.cs
@@ -16,17 +16,29 @@ public struct Curves
{
public List Loops;
public List Paths;
+
+ public HashSet BoundaryV;
+ public HashSet JunctionV;
+
+ public List> LoopEdges;
+ public List> PathEdges;
}
///
/// Decompose graph into simple polylines and polygons.
///
- public static Curves ExtractCurves(DGraph3 graph)
+ 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();
@@ -46,9 +58,13 @@ 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 };
+ 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);
@@ -57,12 +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)
+ 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 ) {
@@ -72,8 +100,13 @@ 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 };
+ 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);
@@ -82,24 +115,46 @@ 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!
if (vid == start_vid) {
path.RemoveVertex(path.VertexCount - 1);
path.Closed = true;
+ 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...
@@ -111,23 +166,39 @@ 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 };
+ 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))
break;
}
poly.RemoveVertex(poly.VertexCount - 1);
+ 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;
}
@@ -213,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/curve/GeneralPolygon2d.cs b/curve/GeneralPolygon2d.cs
index abca2fd4..cc15dffa 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; }
@@ -183,6 +188,22 @@ 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)
+ return false;
+ foreach (var h in holes)
+ {
+ if (h.Intersects(seg))
+ return false;
+ }
+ return true;
+ }
+
public bool Intersects(Polygon2d poly)
{
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/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;
+ }
+
+
+
+ }
+}
diff --git a/curve/PolyLine2d.cs b/curve/PolyLine2d.cs
index 5364974c..213ff496 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);
@@ -334,6 +340,42 @@ public PolyLine2d Transform(ITransform2 xform)
}
+
+ static public PolyLine2d MakeBoxSpiral(Vector2d center, double len, double spacing)
+ {
+ 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/curve/PolySimplification2.cs b/curve/PolySimplification2.cs
index 9836ed3d..acce4bdb 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];
@@ -117,13 +138,19 @@ 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;
- cur_i = i1;
skip_count = 0;
+ if (i1 == 0) {
+ cur_i = NStop;
+ } else {
+ cur_i = i1;
+ }
continue;
}
@@ -152,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);
@@ -177,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;
+ }
+
+
}
}
diff --git a/curve/Polygon2d.cs b/curve/Polygon2d.cs
index 81ad24fc..b74287ca 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);
@@ -89,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)
{
@@ -96,6 +107,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();
@@ -187,6 +212,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];
@@ -196,6 +223,9 @@ public double SignedArea {
return fArea * 0.5;
}
}
+ public double Area {
+ get { return Math.Abs(SignedArea); }
+ }
@@ -303,8 +333,24 @@ 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
+ 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;
@@ -317,8 +363,27 @@ 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
+ 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;
@@ -684,6 +749,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);
diff --git a/distance/DistPoint3Triangle3.cs b/distance/DistPoint3Triangle3.cs
index f54725d5..3ba058a7 100644
--- a/distance/DistPoint3Triangle3.cs
+++ b/distance/DistPoint3Triangle3.cs
@@ -52,14 +52,21 @@ 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;
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;
@@ -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/distance/DistRay3Segment3.cs b/distance/DistRay3Segment3.cs
index b5a0697c..a6dc4b6a 100644
--- a/distance/DistRay3Segment3.cs
+++ b/distance/DistRay3Segment3.cs
@@ -5,10 +5,11 @@
namespace g3
{
- // ported from WildMagic 5
- // https://www.geometrictools.com/Downloads/Downloads.html
-
- class DistRay3Segment3
+ ///
+ /// Distance between ray and segment
+ /// ported from WildMagic5
+ ///
+ public class DistRay3Segment3
{
Ray3d ray;
public 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;
}
@@ -57,7 +62,7 @@ public double Get() {
public double GetSquared()
{
- if (DistanceSquared > 0)
+ if (DistanceSquared >= 0)
return DistanceSquared;
Vector3d diff = ray.Origin - segment.Center;
@@ -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;
+ }
+
+
+
}
}
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
diff --git a/geometry3Sharp.csproj b/geometry3Sharp.csproj
index 76901f63..a43ff4a0 100644
--- a/geometry3Sharp.csproj
+++ b/geometry3Sharp.csproj
@@ -22,6 +22,7 @@
prompt
4
true
+ AnyCPU
pdbonly
@@ -31,6 +32,7 @@
prompt
4
true
+ AnyCPU
@@ -53,6 +55,7 @@
+
@@ -77,7 +80,9 @@
+
+
@@ -88,6 +93,7 @@
+
@@ -100,9 +106,13 @@
+
+
+
+
@@ -119,6 +129,7 @@
+
@@ -144,7 +155,11 @@
+
+
+
+
@@ -163,6 +178,7 @@
+
@@ -178,9 +194,11 @@
+
+
@@ -285,8 +303,13 @@
+
+
+
+
+
@@ -294,6 +317,8 @@
+
+
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/implicit/GridImplicits3d.cs b/implicit/GridImplicits3d.cs
index 70d72c75..6e2d1fe8 100644
--- a/implicit/GridImplicits3d.cs
+++ b/implicit/GridImplicits3d.cs
@@ -10,15 +10,16 @@ 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;
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)
{
@@ -26,6 +27,22 @@ 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()
+ {
+ 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)
diff --git a/implicit/Implicit3d.cs b/implicit/Implicit3d.cs
index dcbb28b1..ea6d70cc 100644
--- a/implicit/Implicit3d.cs
+++ b/implicit/Implicit3d.cs
@@ -1,24 +1,679 @@
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();
+ }
+
+
+ ///
+ /// 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.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;
+ }
+ }
+
+
+
+ ///
+ /// 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;
+
+ 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()
+ {
+ // [TODO] intersect boxes
+ 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 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;
+
+ 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;
+
+ 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();
+ }
+ }
+
+
+
+
+ ///
+ /// 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;
+
+ 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;
+
+
+ /// 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;
+ 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;
+ }
+
+ public AxisAlignedBox3d Bounds()
+ {
+ var box = A.Bounds();
+ box.Contain(B.Bounds());
+ box.Expand(ExpandBounds * box.MaxDim );
+ return box;
+ }
+ }
+
+
+
+
+
+
+
+
+ /*
+ * 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;
+ }
+ }
+
+
- public class ImplicitSphere3d : ImplicitFunction3d
+
+
+
+
+ ///
+ /// sum-blend
+ ///
+ public class SkeletalBlend3d : BoundedImplicitFunction3d
{
+ public BoundedImplicitFunction3d A;
+ public BoundedImplicitFunction3d B;
+
public double Value(ref Vector3d pt)
{
- return pt.Length - 5.0f;
+ return A.Value(ref pt) + B.Value(ref pt);
+ }
+
+ public AxisAlignedBox3d Bounds()
+ {
+ AxisAlignedBox3d box = A.Bounds();
+ box.Contain(B.Bounds());
+ box.Expand(0.25 * box.MaxDim);
+ return box;
}
}
+
+ ///
+ /// 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 == 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);
+ }
+ }
+
+ public AxisAlignedBox3d Bounds()
+ {
+ AxisAlignedBox3d box = A.Bounds();
+ box.Contain(B.Bounds());
+ box.Expand(0.25 * box.MaxDim);
+ return box;
+ }
+ }
+
+
+
+
+ ///
+ /// 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 FieldShift = 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 + FieldShift;
+ }
+
+ 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;
+ }
+ }
+
+
+
+
+
}
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);
+ });
+ }
+
+ }
+}
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/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..1c2ddb5d 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,102 @@ 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;
+ double RHS;
+
+ Vector3d diff = ray.Origin - box.Center;
+ Vector3d extent = box.Extents + expandExtents;
+
+ 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.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.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.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.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.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;
+ }
+
+ return true;
+ }
+
+
+ ///
+ /// 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/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;
- }
- }
+ }
}
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/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/io/OBJWriter.cs b/io/OBJWriter.cs
index a22c0b4e..59708df7 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
@@ -57,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;
@@ -117,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);
}
@@ -233,61 +243,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/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);
}
diff --git a/io/SVGWriter.cs b/io/SVGWriter.cs
index 8fe0f15d..ed6a0115 100644
--- a/io/SVGWriter.cs
+++ b/io/SVGWriter.cs
@@ -244,6 +244,36 @@ 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 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,
+ 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);
+ }
+
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/io/StandardMeshWriter.cs b/io/StandardMeshWriter.cs
index 70db0f75..088fcc38 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,28 @@ 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.Close();
+ stream.Dispose();
+ };
+
+
+
+
public void Dispose()
{
}
@@ -45,7 +78,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 +121,84 @@ 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);
+ w.Flush();
+ 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);
+ w.Flush();
+ 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);
+ 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 {
+ 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);
+ w.Flush();
+ return result;
+ } finally {
+ CloseStreamF(stream);
+ }
}
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;
+ }
}
+
+
}
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/AxisAlignedBox3d.cs b/math/AxisAlignedBox3d.cs
index 8644f1cd..021bdd60 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);
@@ -94,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;
}
@@ -132,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
@@ -144,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; }
+ }
+
+ //! 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)
+
+ public void Scale(double sx, double sy, double sz)
{
Vector3d c = Center;
Vector3d e = Extents; e.x *= sx; e.y *= sy; e.z *= sz;
@@ -167,6 +208,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);
@@ -176,6 +226,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),
@@ -192,6 +251,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)
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)
{
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/BoundsUtil.cs b/math/BoundsUtil.cs
index d471f91c..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())
@@ -28,7 +36,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)
@@ -43,7 +57,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 )
@@ -59,6 +105,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
///
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/FastWindingMath.cs b/math/FastWindingMath.cs
new file mode 100644
index 00000000..1ff678f6
--- /dev/null
+++ b/math/FastWindingMath.cs
@@ -0,0 +1,306 @@
+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
+ {
+ ///
+ /// 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, 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, 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, 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/math/Frame3f.cs b/math/Frame3f.cs
index c8fea33b..8d9e1732 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));
@@ -226,116 +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));
}
-
- 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);
+ /// Map box *into* local coordinates of Frame
+ 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;
}
- 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);
+ /// Map box *from* local frame coordinates into "world" coordinates
+ 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;
}
- 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);
+ /// Map box *into* local coordinates of Frame
+ 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;
}
- 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);
+ /// Map box *from* local frame coordinates into "world" coordinates
+ 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;
}
@@ -358,12 +430,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) );
}
diff --git a/math/IndexUtil.cs b/math/IndexUtil.cs
index bff9dcb8..8891eb2d 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 )
@@ -80,6 +87,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)
{
@@ -107,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)
{
@@ -231,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
@@ -294,6 +350,50 @@ public static void Apply(int[] indices, IList map)
indices[i] = map[indices[i]];
}
+
+
+ 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);
+ vertices.Add(tv.a); vertices.Add(tv.b); vertices.Add(tv.c);
+ }
+ }
+
+
+ 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);
+ edges.Add(te.a); edges.Add(te.b); edges.Add(te.c);
+ }
+ }
+
+
+
+ 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);
+ }
+ }
+
}
@@ -302,6 +402,22 @@ public static void Apply(int[] indices, IList map)
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/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;
diff --git a/math/MathUtil.cs b/math/MathUtil.cs
index 85afb68e..d49d34c0 100644
--- a/math/MathUtil.cs
+++ b/math/MathUtil.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
namespace g3
@@ -10,6 +11,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;
@@ -370,12 +372,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) {
@@ -483,6 +485,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
@@ -629,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);
+ }
+ }
+
+
}
}
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/math/Matrix3d.cs b/math/Matrix3d.cs
index 95f38acb..f0fd6395 100644
--- a/math/Matrix3d.cs
+++ b/math/Matrix3d.cs
@@ -68,13 +68,35 @@ 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);
}
+ ///
+ /// 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);
@@ -150,6 +172,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;
@@ -177,6 +213,14 @@ public void ToBuffer(double[] buf) {
}
+
+ 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/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;
diff --git a/math/Quaterniond.cs b/math/Quaterniond.cs
index d608338a..555d49cb 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".
@@ -312,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/math/Quaternionf.cs b/math/Quaternionf.cs
index a36e06e3..7e6c7872 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;
@@ -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 {
@@ -311,6 +353,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/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
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/TransformSequence.cs b/math/TransformSequence.cs
index 2c92a73d..a8197e69 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;
@@ -54,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)
@@ -103,6 +119,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 +170,14 @@ public Vector3d TransformP(Vector3d p)
p += Operations[i].RotateOrigin;
break;
+ case XFormType.ToFrame:
+ p = Operations[i].Frame.ToFrameP(ref p);
+ break;
+
+ case XFormType.FromFrame:
+ p = Operations[i].Frame.FromFrameP(ref p);
+ break;
+
default:
throw new NotImplementedException("TransformSequence.TransformP: unhandled type!");
}
@@ -148,6 +189,98 @@ public Vector3d TransformP(Vector3d p)
+ ///
+ /// Apply transforms to vector. Includes scaling.
+ ///
+ 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
+ ///
+ public Vector3f TransformP(Vector3f p) {
+ return (Vector3f)TransformP((Vector3d)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;
+ }
+
public void Store(BinaryWriter writer)
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/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/Vector3d.cs b/math/Vector3d.cs
index 0c2ca2da..044ef93b 100644
--- a/math/Vector3d.cs
+++ b/math/Vector3d.cs
@@ -127,25 +127,27 @@ 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)
- {
+ public Vector3d UnitCross(ref Vector3d v2) {
Vector3d n = new Vector3d(
y * v2.z - z * v2.y,
z * v2.x - x * v2.z,
@@ -153,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)
{
@@ -356,9 +362,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 +402,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 +432,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 +464,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/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/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/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/DMesh3.cs b/mesh/DMesh3.cs
index 19b90a31..794b7dda 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,
@@ -27,6 +28,12 @@ public enum MeshResult
Failed_SameOrientation = 28,
Failed_WouldCreateBowtie = 30,
+ Failed_VertexAlreadyExists = 31,
+ Failed_CannotAllocateVertex = 32,
+
+ Failed_WouldCreateNonmanifoldEdge = 50,
+ Failed_TriangleAlreadyExists = 51,
+ Failed_CannotAllocateTriangle = 52
};
@@ -321,9 +328,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; }
}
@@ -470,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]);
}
@@ -817,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) {
@@ -909,12 +945,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;
@@ -948,8 +991,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;
@@ -1002,6 +1050,67 @@ 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, bool bUnsafe = false)
+ {
+ if (vertices_refcount.isValid(vid))
+ return MeshResult.Failed_VertexAlreadyExists;
+
+ bool bOK = (bUnsafe) ? vertices_refcount.allocate_at_unsafe(vid) :
+ 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 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) {
return AppendTriangle(new Index3i(v0, v1, v2), gid);
}
@@ -1063,6 +1172,76 @@ void add_tri_edge(int tid, int v0, int v1, int j, int eid)
+ ///
+ /// 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, bool bUnsafe = false)
+ {
+ 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 = (bUnsafe) ? triangles_refcount.allocate_at_unsafe(tid) :
+ 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 virtual void BeginUnsafeTrianglesInsert() {
+ // do nothing...
+ }
+ public virtual void EndUnsafeTrianglesInsert() {
+ triangles_refcount.rebuild_free_list();
+ }
+
+
+
+
+
public void EnableVertexNormals(Vector3f initial_normal)
{
if (HasVertexNormals)
@@ -1156,6 +1335,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 +1346,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 +1366,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 +1379,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 +1416,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 +1441,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 +1452,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 +1489,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 +1509,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 +1576,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 +1595,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 +1612,31 @@ 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++;
+ }
+ if (n > 0) {
+ 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 +1735,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 +1786,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 +1805,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 +1836,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 +1867,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 +1900,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)
{
@@ -1684,17 +1936,64 @@ 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;
+ 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;
+ 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 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");
}
- // compute vertex bounding box
+ ///
+ /// Computes bounding box of all vertices.
+ ///
public AxisAlignedBox3d GetBounds()
{
double x = 0, y = 0, z = 0;
@@ -1715,7 +2014,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 +2032,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 +2053,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;
}
@@ -1763,16 +2064,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; }
+ }
+
///
@@ -1882,9 +2193,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/DMesh3Changes.cs b/mesh/DMesh3Changes.cs
new file mode 100644
index 00000000..3f673d57
--- /dev/null
+++ b/mesh/DMesh3Changes.cs
@@ -0,0 +1,495 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+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();
+ }
+ }
+
+ }
+
+
+
+
+
+
+
+
+ ///
+ /// 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);
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+ ///
+ /// 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
+ {
+ protected DVector RemovedV;
+ protected DVector Positions;
+ protected DVector Normals;
+ protected DVector Colors;
+ protected DVector UVs;
+
+ protected DVector RemovedT;
+ protected DVector Triangles;
+
+ public Action,IEnumerable> OnApplyF;
+ public Action, IEnumerable> OnRevertF;
+
+ public RemoveTrianglesMeshChange()
+ {
+ }
+
+
+ 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;
+
+ 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 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;
+ 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());
+ }
+
+ if ( OnApplyF != null )
+ OnApplyF(RemovedV, RemovedT);
+ }
+
+
+ public void Revert(DMesh3 mesh)
+ {
+ int NV = RemovedV.size;
+ 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;
+ 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();
+ }
+
+ if (OnRevertF != null)
+ OnRevertF(RemovedV, RemovedT);
+ }
+
+
+
+
+ bool save_vertex(DMesh3 mesh, int vid, bool force = false)
+ {
+ if ( force || 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();
+ }
+
+ }
+
+
+
+
+
+
+
+
+ ///
+ /// 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 Action, IEnumerable> OnApplyF;
+ public Action, IEnumerable> OnRevertF;
+
+
+ public AddTrianglesMeshChange()
+ {
+ }
+
+
+ public void InitializeFromExisting(DMesh3 mesh, IEnumerable added_v, IEnumerable added_t)
+ {
+ initialize_buffers(mesh);
+ bool has_groups = mesh.HasTriangleGroups;
+
+ 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) {
+ 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();
+ }
+
+ if (OnApplyF != null)
+ OnApplyF(AddedV, AddedT);
+ }
+
+
+ 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());
+ }
+
+ if ( OnRevertF != null )
+ OnRevertF(AddedV, AddedT);
+ }
+
+
+
+
+ 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/DMesh3_edge_operators.cs b/mesh/DMesh3_edge_operators.cs
index d7862393..1df7b540 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;
@@ -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))
@@ -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);
@@ -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)
{
@@ -272,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) )
@@ -287,24 +293,27 @@ 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;
+ 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) ) {
- // look up edge bc, which needs to be modified
- Index3i T0te = GetTriEdges(t0);
+ // create new vertex
+ Vector3d vNew = Vector3d.Lerp(GetVertex(a), GetVertex(b), split_t);
+ int f = AppendVertex(vNew);
+ if (HasVertexNormals)
+ SetVertexNormal(f, Vector3f.Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized);
+ if (HasVertexColors)
+ SetVertexColor(f, Colorf.Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t));
+ if (HasVertexUVs)
+ SetVertexUV(f, Vector2f.Lerp(GetVertexUV(a), GetVertexUV(b), (float)split_t));
+
+ // 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
@@ -339,6 +348,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;
@@ -350,10 +361,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 = Vector3d.Lerp(GetVertex(a), GetVertex(b), split_t);
+ int f = AppendVertex(vNew);
+ if (HasVertexNormals)
+ SetVertexNormal(f, Vector3f.Lerp(GetVertexNormal(a), GetVertexNormal(b), (float)split_t).Normalized);
+ if (HasVertexColors)
+ SetVertexColor(f, Colorf.Lerp(GetVertexColor(a), GetVertexColor(b), (float)split_t));
+ if (HasVertexUVs)
+ 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
+ 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 )];
@@ -403,8 +426,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;
}
@@ -819,9 +844,41 @@ 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 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;
+
+ // 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;
+ }
+ }
+ }
+
- if (a != c) {
+ // [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)
@@ -905,6 +962,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)
@@ -925,11 +983,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
+ }
}
}
}
@@ -1081,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) {
diff --git a/mesh/DSubmesh3.cs b/mesh/DSubmesh3.cs
index c6e4980a..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)
@@ -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/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
+ 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
{
public bool Valid;
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)
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/MeshRefinerBase.cs b/mesh/MeshRefinerBase.cs
index a0a8f8a9..96ea5075 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,28 @@ 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) {
+ return n0.Dot(n1);
+ } else {
+ return n0.Normalized.Dot(n1.Normalized);
+ }
+ }
+
///
/// check if edge collapse will create a face-normal flip.
@@ -65,16 +95,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 +128,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
@@ -173,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;
}
@@ -235,7 +270,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/MeshTransforms.cs b/mesh/MeshTransforms.cs
index 6f4f744d..99cfe776 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;
@@ -57,33 +63,42 @@ 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)) );
}
}
}
- 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);
}
-
+ ///Map mesh *into* local coordinates of Frame
public static void ToFrame(IDeformableMesh mesh, Frame3f f)
{
int NV = mesh.MaxVertexID;
@@ -91,16 +106,18 @@ 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);
}
}
}
}
+
+ /// Map mesh *from* local frame coordinates into "world" coordinates
public static void FromFrame(IDeformableMesh mesh, Frame3f f)
{
int NV = mesh.MaxVertexID;
@@ -108,11 +125,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);
}
}
@@ -268,6 +285,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
///
@@ -295,5 +351,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);
+ }
+ }
+ }
+
+
+
}
}
diff --git a/mesh/MeshUtil.cs b/mesh/MeshUtil.cs
index d378ea9c..251203fa 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)
@@ -36,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
@@ -47,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);
@@ -60,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;
+ }
+
+
///
@@ -106,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;
+
+ 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/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)) )
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/mesh/Reducer.cs b/mesh/Reducer.cs
index b65ec6f0..99757e6f 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();
@@ -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();
@@ -155,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;
@@ -169,7 +180,9 @@ public virtual void FastCollapsePass(double fMinEdgeLength)
begin_pass();
begin_setup();
- Precompute();
+ Precompute(MeshIsClosedHint);
+ if (Cancelled())
+ return;
end_setup();
begin_ops();
@@ -177,29 +190,42 @@ 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;
-
- 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();
+ if (Cancelled())
+ return;
+
Reproject();
end_pass();
@@ -326,7 +352,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 {
@@ -402,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)
@@ -513,7 +541,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))
diff --git a/mesh/Remesher.cs b/mesh/Remesher.cs
index 4c06fbd5..4f784eba 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();
}
@@ -451,13 +462,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;
+ }
}
}
@@ -604,7 +626,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);
@@ -653,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)
{
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/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();
}
diff --git a/mesh_generators/GenCylGenerators.cs b/mesh_generators/GenCylGenerators.cs
index c365cccc..49c5996a 100644
--- a/mesh_generators/GenCylGenerators.cs
+++ b/mesh_generators/GenCylGenerators.cs
@@ -11,6 +11,14 @@ 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 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
+ /// vertex normals
+ ///
///
public class TubeGenerator : MeshGenerator
{
@@ -20,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;
@@ -33,7 +42,6 @@ public class TubeGenerator : MeshGenerator
public int startCapCenterIndex = -1;
public int endCapCenterIndex = -1;
-
public TubeGenerator()
{
}
@@ -47,6 +55,22 @@ 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;
+ }
+ public TubeGenerator(DCurve3 tubePath, Polygon2d tubeShape)
+ {
+ Vertices = new List(tubePath.Vertices);
+ Polygon = new Polygon2d(tubeShape);
+ ClosedLoop = tubePath.Closed;
+ Capped = ! ClosedLoop;
+ }
@@ -55,8 +79,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)
@@ -66,76 +91,85 @@ 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);
- 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);
+ 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];
- if (ri == 11)
- dv = tan;
- 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;
- for (int j = 0; j < nRingSize; ++j) {
- float uv_around = (float)j / (float)(nRings);
+ double accum_ring_v = 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);
+
+ 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;
}
+
+ 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;
@@ -146,7 +180,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);
@@ -156,7 +191,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);
@@ -170,4 +205,4 @@ override public MeshGenerator Generate()
return this;
}
}
-}
+}
\ No newline at end of file
diff --git a/mesh_generators/MarchingCubes.cs b/mesh_generators/MarchingCubes.cs
index de6bb5f1..fdf850ff 100644
--- a/mesh_generators/MarchingCubes.cs
+++ b/mesh_generators/MarchingCubes.cs
@@ -19,28 +19,52 @@ 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;
+
+
+ /// if this function returns true, we should abort calculation
+ public Func CancelF = () => { return false; };
+
/*
* Outputs
*/
@@ -187,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);
@@ -215,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);
@@ -354,7 +382,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 +409,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.
*/
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_generators/MeshGenerators.cs b/mesh_generators/MeshGenerators.cs
index abac629a..eae88b8e 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];
}
@@ -293,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_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_generators/TriangulatedPolygonGenerator.cs b/mesh_generators/TriangulatedPolygonGenerator.cs
new file mode 100644
index 00000000..d09102a8
--- /dev/null
+++ b/mesh_generators/TriangulatedPolygonGenerator.cs
@@ -0,0 +1,100 @@
+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;
+
+ public int Subdivisions = 1;
+
+ 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;
+ bounds.Expand(padding);
+
+ 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;
+ 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);
+
+ Vector3d shift3 = new Vector3d(shift.x, shift.y, 0);
+ MeshTransforms.Translate(base_mesh, shift3);
+
+ insertion = insert;
+ return base_mesh;
+ }
+
+
+
+
+ }
+
+
+
+
+
+}
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/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);
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/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;
}
diff --git a/mesh_ops/MeshInsertPolygon.cs b/mesh_ops/MeshInsertPolygon.cs
new file mode 100644
index 00000000..65830167
--- /dev/null
+++ b/mesh_ops/MeshInsertPolygon.cs
@@ -0,0 +1,86 @@
+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);
+ bool outerApplyOK = OuterInsert.Apply();
+ if (outerApplyOK == false || OuterInsert.Loops.Count == 0)
+ return false;
+ 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;
+ }
+
+ }
+}
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/MeshInsertUVPolyCurve.cs b/mesh_ops/MeshInsertUVPolyCurve.cs
index 145c620e..68676e19 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
{
@@ -26,6 +34,11 @@ 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?
+ public bool UseTriSpatial = true;
+
+ // points/edges within this distance are considered the same
+ public double SpatialEpsilon = MathUtil.ZeroTolerance;
// Results
@@ -96,39 +109,122 @@ 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()
+ void insert_corners(HashSet MeshVertsOnCurve)
{
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) {
+ 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, bins);
+ 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));
+ // find the triangle that contains this curve point
+ 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 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) {
+ CurveVertices[i] = vid;
+ if (is_existing_v)
+ MeshVertsOnCurve.Add(vid);
+ inserted = true;
}
}
+ // 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!");
@@ -140,45 +236,70 @@ 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, double spatial_tol, out bool is_existing_v)
{
+ is_existing_v = false;
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) {
+ is_existing_v = true;
+ 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);
- 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");
- SetPointF(split_info.vNew, vInsert);
- return split_info.vNew;
+ 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);
+
+ spatial_add_triangles(et.a, et.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());
SetPointF(pokeinfo.new_vid, vInsert);
+
+ spatial_add_triangle(tid);
+ spatial_add_triangle(pokeinfo.new_t1);
+ spatial_add_triangle(pokeinfo.new_t2);
+
return pokeinfo.new_vid;
}
@@ -189,7 +310,8 @@ int insert_corner_from_bary(int iCorner, int tid, Vector3d bary_coords, double t
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);
@@ -199,6 +321,14 @@ 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];
+
+ 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 ) {
@@ -212,38 +342,55 @@ 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;
}
+ 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;
- int[] signs = new int[MaxVID];
- gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => {
+ if ( signs.Length < MaxVID )
+ signs = new sbyte[2*MaxVID];
+ gParallel.ForEach(vertices, (vid) => {
if (Mesh.IsVertex(vid)) {
if (vid == i0_vid || vid == i1_vid) {
signs[vid] = 0;
} 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, SpatialEpsilon);
}
} 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);
// 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))
@@ -257,12 +404,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 + MathUtil.ZeroTolerance);
+ 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 + MathUtil.ZeroTolerance);
+ 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.
@@ -270,9 +420,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;
}
@@ -291,27 +444,32 @@ 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
}
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
// 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;
}
+ Index2i et = Mesh.GetEdgeT(eid);
+ spatial_remove_triangles(et.a, et.b);
+
// 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.Cut: failed in SplitEdge");
+ throw new Exception("MeshInsertUVSegment.Apply: SplitEdge failed - " + result.ToString());
//return false;
}
@@ -322,29 +480,25 @@ 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);
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);
@@ -354,7 +508,10 @@ public virtual bool Apply()
} // Apply()
-
+ // useful to have all these calls centralized for debugging...
+ void add_cut_edge(int eid) {
+ OnCutEdges.Add(eid);
+ }
@@ -504,7 +661,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)) {
diff --git a/mesh_ops/MeshIsoCurves.cs b/mesh_ops/MeshIsoCurves.cs
index e14d62e4..6169ba32 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
@@ -26,14 +46,23 @@ 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. key is edge id.
+ Dictionary EdgeLocations = new Dictionary();
+
public MeshIsoCurves(DMesh3 mesh, Func valueF)
{
@@ -43,7 +72,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 +86,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 +94,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();
@@ -93,11 +140,14 @@ protected void compute_full(IEnumerable Triangles)
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]);
+ if (graph_eid >= 0 && WantGraphEdgeInfo)
+ add_on_edge(graph_eid, tid, triEdges[z0], new Index2i(e0, e1));
} else {
// edge/vertex case
@@ -111,26 +161,45 @@ 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)
- add_edge_edge(graph_eid, tid, new Index2i(triEdges[(z0+1)%3], triVerts[z0]));
+ 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 {
Index3i cross_verts = Index3i.Min;
- for (int ti = 0; ti < 3; ++ti) {
- int i = ti, j = (ti + 1) % 3;
+ int less_than = 0;
+ for (int tei = 0; tei < 3; ++tei) {
+ int i = tei, j = (tei + 1) % 3;
+ if (f[i] < 0)
+ less_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 are "cutting off" one vertex, orientation would end up flipped
+ 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
@@ -139,8 +208,8 @@ protected void compute_full(IEnumerable Triangles)
if (ev0 != ev1) {
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]));
+ if (graph_eid >= 0 && WantGraphEdgeInfo)
+ add_edge_edge(graph_eid, tid, new Index2i(triEdges[e0], triEdges[e1]), new Index2i(ev0,ev1));
}
}
}
@@ -161,53 +230,149 @@ 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);
}
+ // [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);
+ }
+ }
+
+
+
+
+ ///
+ /// 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;
+ }
+
}
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