Took out legacy csg code
refactoring
This commit is contained in:
parent
d5ac8376aa
commit
9cfc85f90e
60 changed files with 67 additions and 1807 deletions
|
|
@ -1,567 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using ClipperLib;
|
||||
using DualContouring;
|
||||
using g3;
|
||||
using gs;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.MatterControl.DesignTools;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.PolygonMesh
|
||||
{
|
||||
using Polygon = List<IntPoint>;
|
||||
|
||||
public enum CsgModes
|
||||
{
|
||||
Union,
|
||||
Subtract,
|
||||
Intersect
|
||||
}
|
||||
|
||||
public enum IplicitSurfaceMethod
|
||||
{
|
||||
[Description("Faster but less accurate")]
|
||||
Grid,
|
||||
[Description("Slower but more accurate")]
|
||||
Exact
|
||||
};
|
||||
|
||||
public enum ProcessingModes
|
||||
{
|
||||
[Description("Default CSG processing")]
|
||||
Polygons,
|
||||
[Description("Use libigl (windows only)")]
|
||||
libigl,
|
||||
[Description("Experimental Marching Cubes")]
|
||||
Marching_Cubes,
|
||||
[Description("Experimental Dual Contouring")]
|
||||
Dual_Contouring,
|
||||
}
|
||||
|
||||
public enum ProcessingResolution
|
||||
{
|
||||
_64 = 6,
|
||||
_128 = 7,
|
||||
_256 = 8,
|
||||
_512 = 9,
|
||||
}
|
||||
|
||||
public static class BooleanProcessing
|
||||
{
|
||||
private const string BooleanAssembly = "609_Boolean_bin.dll";
|
||||
|
||||
[DllImport(BooleanAssembly, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int DeleteDouble(ref IntPtr handle);
|
||||
|
||||
[DllImport(BooleanAssembly, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int DeleteInt(ref IntPtr handle);
|
||||
|
||||
[DllImport(BooleanAssembly, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void DoBooleanOperation(double[] va, int vaCount, int[] fa, int faCount, double[] vb, int vbCount, int[] fb, int fbCount, int operation, out IntPtr pVc, out int vcCount, out IntPtr pVf, out int vfCount);
|
||||
|
||||
public static Mesh DoArray(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> items,
|
||||
CsgModes operation,
|
||||
ProcessingModes processingMode,
|
||||
ProcessingResolution inputResolution,
|
||||
ProcessingResolution outputResolution,
|
||||
IProgress<ProgressStatus> reporter,
|
||||
CancellationToken cancellationToken,
|
||||
double amountPerOperation = 1,
|
||||
double ratioCompleted = 0)
|
||||
{
|
||||
if (processingMode == ProcessingModes.Polygons)
|
||||
{
|
||||
var csgBySlicing = new CsgBySlicing();
|
||||
csgBySlicing.Setup(items, null, cancellationToken);
|
||||
|
||||
return csgBySlicing.Calculate(operation,
|
||||
(ratio, message) =>
|
||||
{
|
||||
reporter?.Report(new ProgressStatus()
|
||||
{
|
||||
Progress0To1 = ratio * amountPerOperation + ratioCompleted,
|
||||
Status = message
|
||||
});
|
||||
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
else if (processingMode == ProcessingModes.libigl)
|
||||
{
|
||||
return ExactLegacy(items, operation, processingMode, inputResolution, outputResolution, reporter, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
return AsImplicitMeshes(items, operation, processingMode, inputResolution, outputResolution);
|
||||
}
|
||||
}
|
||||
|
||||
private static Mesh AsImplicitMeshes(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> items,
|
||||
CsgModes operation,
|
||||
ProcessingModes processingMode,
|
||||
ProcessingResolution inputResolution,
|
||||
ProcessingResolution outputResolution)
|
||||
{
|
||||
Mesh implicitResult = null;
|
||||
|
||||
var implicitMeshs = new List<BoundedImplicitFunction3d>();
|
||||
foreach (var (mesh, matrix) in items)
|
||||
{
|
||||
var meshCopy = mesh.Copy(CancellationToken.None);
|
||||
meshCopy.Transform(matrix);
|
||||
|
||||
implicitMeshs.Add(GetImplicitFunction(meshCopy, processingMode == ProcessingModes.Polygons, 1 << (int)inputResolution));
|
||||
}
|
||||
|
||||
DMesh3 GenerateMeshF(BoundedImplicitFunction3d root, int numCells)
|
||||
{
|
||||
var bounds = root.Bounds();
|
||||
|
||||
var c = new MarchingCubesPro()
|
||||
{
|
||||
Implicit = root,
|
||||
RootMode = MarchingCubesPro.RootfindingModes.LerpSteps, // cube-edge convergence method
|
||||
RootModeSteps = 5, // number of iterations
|
||||
Bounds = bounds,
|
||||
CubeSize = bounds.MaxDim / numCells,
|
||||
};
|
||||
|
||||
c.Bounds.Expand(3 * c.CubeSize); // leave a buffer of cells
|
||||
c.Generate();
|
||||
|
||||
MeshNormals.QuickCompute(c.Mesh); // generate normals
|
||||
return c.Mesh;
|
||||
}
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case CsgModes.Union:
|
||||
if (processingMode == ProcessingModes.Dual_Contouring)
|
||||
{
|
||||
var union = new ImplicitNaryUnion3d()
|
||||
{
|
||||
Children = implicitMeshs
|
||||
};
|
||||
var bounds = union.Bounds();
|
||||
var size = bounds.Max - bounds.Min;
|
||||
var root = Octree.BuildOctree((pos) =>
|
||||
{
|
||||
var pos2 = new Vector3d(pos.X, pos.Y, pos.Z);
|
||||
return union.Value(ref pos2);
|
||||
}, new Vector3(bounds.Min.x, bounds.Min.y, bounds.Min.z),
|
||||
new Vector3(size.x, size.y, size.z),
|
||||
(int)outputResolution,
|
||||
.001);
|
||||
implicitResult = Octree.GenerateMeshFromOctree(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
implicitResult = GenerateMeshF(new ImplicitNaryUnion3d()
|
||||
{
|
||||
Children = implicitMeshs
|
||||
}, 1 << (int)outputResolution).ToMesh();
|
||||
}
|
||||
break;
|
||||
|
||||
case CsgModes.Subtract:
|
||||
{
|
||||
if (processingMode == ProcessingModes.Dual_Contouring)
|
||||
{
|
||||
var subtract = new ImplicitNaryIntersection3d()
|
||||
{
|
||||
Children = implicitMeshs
|
||||
};
|
||||
var bounds = subtract.Bounds();
|
||||
var root = Octree.BuildOctree((pos) =>
|
||||
{
|
||||
var pos2 = new Vector3d(pos.X, pos.Y, pos.Z);
|
||||
return subtract.Value(ref pos2);
|
||||
}, new Vector3(bounds.Min.x, bounds.Min.y, bounds.Min.z),
|
||||
new Vector3(bounds.Width, bounds.Depth, bounds.Height),
|
||||
(int)outputResolution,
|
||||
.001);
|
||||
implicitResult = Octree.GenerateMeshFromOctree(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
implicitResult = GenerateMeshF(new ImplicitNaryDifference3d()
|
||||
{
|
||||
A = implicitMeshs.First(),
|
||||
BSet = implicitMeshs.GetRange(0, implicitMeshs.Count - 1)
|
||||
}, 1 << (int)outputResolution).ToMesh();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CsgModes.Intersect:
|
||||
if (processingMode == ProcessingModes.Dual_Contouring)
|
||||
{
|
||||
var intersect = new ImplicitNaryIntersection3d()
|
||||
{
|
||||
Children = implicitMeshs
|
||||
};
|
||||
var bounds = intersect.Bounds();
|
||||
var root = Octree.BuildOctree((pos) =>
|
||||
{
|
||||
var pos2 = new Vector3d(pos.X, pos.Y, pos.Z);
|
||||
return intersect.Value(ref pos2);
|
||||
}, new Vector3(bounds.Min.x, bounds.Min.y, bounds.Min.z),
|
||||
new Vector3(bounds.Width, bounds.Depth, bounds.Height),
|
||||
(int)outputResolution,
|
||||
.001);
|
||||
implicitResult = Octree.GenerateMeshFromOctree(root);
|
||||
}
|
||||
else
|
||||
{
|
||||
implicitResult = GenerateMeshF(new ImplicitNaryIntersection3d()
|
||||
{
|
||||
Children = implicitMeshs
|
||||
}, 1 << (int)outputResolution).ToMesh();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return implicitResult;
|
||||
}
|
||||
|
||||
private static Mesh ExactLegacy(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> items,
|
||||
CsgModes operation,
|
||||
ProcessingModes processingMode,
|
||||
ProcessingResolution inputResolution,
|
||||
ProcessingResolution outputResolution,
|
||||
IProgress<ProgressStatus> reporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var progressStatus = new ProgressStatus();
|
||||
var totalOperations = items.Count() - 1;
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
var first = true;
|
||||
var resultsMesh = items.First().mesh;
|
||||
var firstWorldMatrix = items.First().matrix;
|
||||
|
||||
foreach (var (mesh, matrix) in items)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemWorldMatrix = matrix;
|
||||
resultsMesh = Do(
|
||||
// other mesh
|
||||
resultsMesh,
|
||||
firstWorldMatrix,
|
||||
mesh,
|
||||
itemWorldMatrix,
|
||||
// operation
|
||||
operation,
|
||||
processingMode,
|
||||
inputResolution,
|
||||
outputResolution,
|
||||
// reporting
|
||||
reporter,
|
||||
amountPerOperation,
|
||||
ratioCompleted,
|
||||
progressStatus,
|
||||
cancellationToken);
|
||||
|
||||
// after the first union we are working with the transformed mesh and don't need the first transform
|
||||
firstWorldMatrix = Matrix4X4.Identity;
|
||||
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressStatus.Progress0To1 = ratioCompleted;
|
||||
reporter?.Report(progressStatus);
|
||||
}
|
||||
|
||||
return resultsMesh;
|
||||
}
|
||||
|
||||
public static Mesh Do(Mesh inMeshA,
|
||||
Matrix4X4 matrixA,
|
||||
// mesh B
|
||||
Mesh inMeshB,
|
||||
Matrix4X4 matrixB,
|
||||
// operation
|
||||
CsgModes operation,
|
||||
ProcessingModes processingMode = ProcessingModes.Polygons,
|
||||
ProcessingResolution inputResolution = ProcessingResolution._64,
|
||||
ProcessingResolution outputResolution = ProcessingResolution._64,
|
||||
// reporting
|
||||
IProgress<ProgressStatus> reporter = null,
|
||||
double amountPerOperation = 1,
|
||||
double ratioCompleted = 0,
|
||||
ProgressStatus progressStatus = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
bool externalAssemblyExists = File.Exists(BooleanAssembly);
|
||||
if (processingMode == ProcessingModes.Polygons)
|
||||
{
|
||||
return BooleanProcessing.DoArray(new (Mesh, Matrix4X4)[] { (inMeshA, matrixA), (inMeshB, matrixB) },
|
||||
operation,
|
||||
processingMode,
|
||||
inputResolution,
|
||||
outputResolution,
|
||||
reporter,
|
||||
cancellationToken,
|
||||
amountPerOperation,
|
||||
ratioCompleted);
|
||||
}
|
||||
else if (processingMode == ProcessingModes.libigl)
|
||||
{
|
||||
// only try to run the improved booleans if we are 64 bit and it is there
|
||||
if (externalAssemblyExists && IntPtr.Size == 8)
|
||||
{
|
||||
IntPtr pVc = IntPtr.Zero;
|
||||
IntPtr pFc = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
double[] va;
|
||||
int[] fa;
|
||||
va = inMeshA.Vertices.ToDoubleArray(matrixA);
|
||||
fa = inMeshA.Faces.ToIntArray();
|
||||
double[] vb;
|
||||
int[] fb;
|
||||
vb = inMeshB.Vertices.ToDoubleArray(matrixB);
|
||||
fb = inMeshB.Faces.ToIntArray();
|
||||
|
||||
DoBooleanOperation(va,
|
||||
va.Length,
|
||||
fa,
|
||||
fa.Length,
|
||||
// object B
|
||||
vb,
|
||||
vb.Length,
|
||||
fb,
|
||||
fb.Length,
|
||||
// operation
|
||||
(int)operation,
|
||||
// results
|
||||
out pVc,
|
||||
out int vcCount,
|
||||
out pFc,
|
||||
out int fcCount);
|
||||
|
||||
var vcArray = new double[vcCount];
|
||||
Marshal.Copy(pVc, vcArray, 0, vcCount);
|
||||
|
||||
var fcArray = new int[fcCount];
|
||||
Marshal.Copy(pFc, fcArray, 0, fcCount);
|
||||
|
||||
return new Mesh(vcArray, fcArray);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// ApplicationController.Instance.LogInfo("Error performing boolean operation: ");
|
||||
// ApplicationController.Instance.LogInfo(ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (pVc != IntPtr.Zero)
|
||||
{
|
||||
DeleteDouble(ref pVc);
|
||||
}
|
||||
|
||||
if (pFc != IntPtr.Zero)
|
||||
{
|
||||
DeleteInt(ref pFc);
|
||||
}
|
||||
|
||||
if (progressStatus != null)
|
||||
{
|
||||
progressStatus.Progress0To1 = ratioCompleted + amountPerOperation;
|
||||
reporter.Report(progressStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"libigl skipped - AssemblyExists: {externalAssemblyExists}; Is64Bit: {IntPtr.Size == 8};");
|
||||
|
||||
var meshA = inMeshA.Copy(CancellationToken.None);
|
||||
meshA.Transform(matrixA);
|
||||
|
||||
var meshB = inMeshB.Copy(CancellationToken.None);
|
||||
meshB.Transform(matrixB);
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case CsgModes.Union:
|
||||
return Csg.CsgOperations.Union(meshA,
|
||||
meshB,
|
||||
(status, progress0To1) =>
|
||||
{
|
||||
// Abort if flagged
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
progressStatus.Status = status;
|
||||
progressStatus.Progress0To1 = ratioCompleted + (amountPerOperation * progress0To1);
|
||||
reporter?.Report(progressStatus);
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
case CsgModes.Subtract:
|
||||
return Csg.CsgOperations.Subtract(meshA,
|
||||
meshB,
|
||||
(status, progress0To1) =>
|
||||
{
|
||||
progressStatus.Status = status;
|
||||
progressStatus.Progress0To1 = ratioCompleted + (amountPerOperation * progress0To1);
|
||||
reporter?.Report(progressStatus);
|
||||
},
|
||||
cancellationToken);
|
||||
|
||||
case CsgModes.Intersect:
|
||||
return Csg.CsgOperations.Intersect(meshA,
|
||||
meshB,
|
||||
(status, progress0To1) =>
|
||||
{
|
||||
// Abort if flagged
|
||||
cancellationToken.ThrowIfCancellationRequested(); progressStatus.Status = status;
|
||||
progressStatus.Progress0To1 = ratioCompleted + (amountPerOperation * progress0To1);
|
||||
reporter.Report(progressStatus);
|
||||
},
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var meshA = inMeshA.Copy(CancellationToken.None);
|
||||
meshA.Transform(matrixA);
|
||||
|
||||
var meshB = inMeshB.Copy(CancellationToken.None);
|
||||
meshB.Transform(matrixB);
|
||||
|
||||
if (meshA.Faces.Count < 4)
|
||||
{
|
||||
return meshB;
|
||||
}
|
||||
else if (meshB.Faces.Count < 4)
|
||||
{
|
||||
return meshA;
|
||||
}
|
||||
|
||||
var implicitA = GetImplicitFunction(meshA, processingMode == ProcessingModes.Polygons, (int)inputResolution);
|
||||
var implicitB = GetImplicitFunction(meshB, processingMode == ProcessingModes.Polygons, (int)inputResolution);
|
||||
|
||||
DMesh3 GenerateMeshF(BoundedImplicitFunction3d root, int numCells)
|
||||
{
|
||||
var bounds = root.Bounds();
|
||||
|
||||
var c = new MarchingCubes()
|
||||
{
|
||||
Implicit = root,
|
||||
RootMode = MarchingCubes.RootfindingModes.LerpSteps, // cube-edge convergence method
|
||||
RootModeSteps = 5, // number of iterations
|
||||
Bounds = bounds,
|
||||
CubeSize = bounds.MaxDim / numCells,
|
||||
};
|
||||
|
||||
c.Bounds.Expand(3 * c.CubeSize); // leave a buffer of cells
|
||||
c.Generate();
|
||||
|
||||
MeshNormals.QuickCompute(c.Mesh); // generate normals
|
||||
return c.Mesh;
|
||||
}
|
||||
|
||||
var marchingCells = 1 << (int)outputResolution;
|
||||
switch (operation)
|
||||
{
|
||||
case CsgModes.Union:
|
||||
return GenerateMeshF(new ImplicitUnion3d()
|
||||
{
|
||||
A = implicitA,
|
||||
B = implicitB
|
||||
}, marchingCells).ToMesh();
|
||||
|
||||
case CsgModes.Subtract:
|
||||
return GenerateMeshF(new ImplicitDifference3d()
|
||||
{
|
||||
A = implicitA,
|
||||
B = implicitB
|
||||
}, marchingCells).ToMesh();
|
||||
|
||||
case CsgModes.Intersect:
|
||||
return GenerateMeshF(new ImplicitIntersection3d()
|
||||
{
|
||||
A = implicitA,
|
||||
B = implicitB
|
||||
}, marchingCells).ToMesh();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class MWNImplicit : BoundedImplicitFunction3d
|
||||
{
|
||||
public DMeshAABBTree3 MeshAABBTree3;
|
||||
public AxisAlignedBox3d Bounds() { return MeshAABBTree3.Bounds; }
|
||||
public double Value(ref Vector3d pt)
|
||||
{
|
||||
return -(MeshAABBTree3.FastWindingNumber(pt) - 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static BoundedImplicitFunction3d GetImplicitFunction(Mesh mesh, bool exact, int numCells)
|
||||
{
|
||||
var meshA3 = mesh.ToDMesh3();
|
||||
|
||||
// Interesting experiment, this produces an extremely accurate surface representation but is quite slow (even though fast) compared to voxel lookups.
|
||||
if (exact)
|
||||
{
|
||||
DMeshAABBTree3 meshAABBTree3 = new DMeshAABBTree3(meshA3, true);
|
||||
meshAABBTree3.FastWindingNumber(Vector3d.Zero); // build approximation
|
||||
return new MWNImplicit()
|
||||
{
|
||||
MeshAABBTree3 = meshAABBTree3
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
double meshCellsize = meshA3.CachedBounds.MaxDim / numCells;
|
||||
var signedDistance = new MeshSignedDistanceGrid(meshA3, meshCellsize);
|
||||
signedDistance.Compute();
|
||||
return new DenseGridTrilinearImplicit(signedDistance.Grid, signedDistance.GridOrigin, signedDistance.CellSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,314 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ClipperLib;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.PolygonMesh.Csg;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.PolygonMesh
|
||||
{
|
||||
using Polygon = List<IntPoint>;
|
||||
using Polygons = List<List<IntPoint>>;
|
||||
|
||||
public class CoPlanarFaces
|
||||
{
|
||||
private new Dictionary<Plane, Dictionary<int, List<(int sourceFaceIndex, int destFaceIndex)>>> coPlanarFaces
|
||||
= new Dictionary<Plane, Dictionary<int, List<(int sourceFaceIndex, int destFaceIndex)>>>();
|
||||
|
||||
public IEnumerable<Plane> Planes
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var plane in coPlanarFaces.Keys)
|
||||
{
|
||||
yield return plane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> MeshIndicesForPlane(Plane plane)
|
||||
{
|
||||
foreach (var kvp in coPlanarFaces[plane])
|
||||
{
|
||||
yield return kvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<(int sourceFaceIndex, int destFaceIndex)> FacesSetsForPlaneAndMesh(Plane plane, int meshIndex)
|
||||
{
|
||||
if (coPlanarFaces[plane].ContainsKey(meshIndex))
|
||||
{
|
||||
foreach (var faceIndices in coPlanarFaces[plane][meshIndex])
|
||||
{
|
||||
yield return faceIndices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Polygon GetFacePolygon(Mesh mesh1, int faceIndex, Matrix4X4 meshTo0Plane)
|
||||
{
|
||||
var facePolygon = new Polygon();
|
||||
var vertices = mesh1.Vertices;
|
||||
var face = mesh1.Faces[faceIndex];
|
||||
var vertIndices = new int[] { face.v0, face.v1, face.v2 };
|
||||
var vertsOnPlane = new Vector3[3];
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
vertsOnPlane[i] = Vector3Ex.Transform(vertices[vertIndices[i]].AsVector3(), meshTo0Plane);
|
||||
var pointOnPlane = new IntPoint(vertsOnPlane[i].X, vertsOnPlane[i].Y);
|
||||
facePolygon.Add(pointOnPlane);
|
||||
}
|
||||
|
||||
return facePolygon;
|
||||
|
||||
}
|
||||
|
||||
public void SubtractFaces(Plane plane, List<Mesh> transformedMeshes, Mesh resultsMesh, Matrix4X4 flattenedMatrix, HashSet<int> faceIndicesToRemove)
|
||||
{
|
||||
// get all meshes that have faces on this plane
|
||||
var meshesWithFaces = MeshIndicesForPlane(plane).ToList();
|
||||
|
||||
// we need more than one mesh and one of them needs to be the source (mesh 0)
|
||||
if (meshesWithFaces.Count < 2
|
||||
|| !meshesWithFaces.Contains(0))
|
||||
{
|
||||
// no faces to add
|
||||
return;
|
||||
}
|
||||
|
||||
// sort them so we can process each group into intersections
|
||||
meshesWithFaces.Sort();
|
||||
|
||||
// add the faces that we should
|
||||
foreach (var meshIndex in meshesWithFaces)
|
||||
{
|
||||
foreach (var faces in FacesSetsForPlaneAndMesh(plane, meshIndex))
|
||||
{
|
||||
faceIndicesToRemove.Add(faces.destFaceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// subtract every face from the mesh 0 faces
|
||||
// teselate and add what is left
|
||||
var keepPolygons = new Polygons();
|
||||
foreach (var keepFaceSets in FacesSetsForPlaneAndMesh(plane, 0))
|
||||
{
|
||||
var facePolygon = GetFacePolygon(transformedMeshes[0], keepFaceSets.sourceFaceIndex, flattenedMatrix);
|
||||
keepPolygons = keepPolygons.Union(facePolygon);
|
||||
}
|
||||
|
||||
// iterate all the meshes that need to be subtracted
|
||||
var removePoygons = new Polygons();
|
||||
for (int removeMeshIndex = 1; removeMeshIndex < meshesWithFaces.Count; removeMeshIndex++)
|
||||
{
|
||||
foreach (var removeFaceSets in FacesSetsForPlaneAndMesh(plane, removeMeshIndex))
|
||||
{
|
||||
removePoygons = removePoygons.Union(GetFacePolygon(transformedMeshes[removeMeshIndex], removeFaceSets.sourceFaceIndex, flattenedMatrix));
|
||||
}
|
||||
}
|
||||
|
||||
var polygonShape = new Polygons();
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(keepPolygons, PolyType.ptSubject, true);
|
||||
clipper.AddPaths(removePoygons, PolyType.ptClip, true);
|
||||
clipper.Execute(ClipType.ctDifference, polygonShape);
|
||||
|
||||
// teselate and add all the new polygons
|
||||
var countPreAdd = resultsMesh.Faces.Count;
|
||||
polygonShape.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
EnsureFaceNormals(plane, resultsMesh, countPreAdd);
|
||||
}
|
||||
|
||||
private static void EnsureFaceNormals(Plane plane, Mesh resultsMesh, int countPreAdd)
|
||||
{
|
||||
if (countPreAdd >= resultsMesh.Faces.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the new face normals are pointed in the right direction
|
||||
if ((new Vector3(resultsMesh.Faces[countPreAdd].normal) - plane.Normal).LengthSquared > .1)
|
||||
{
|
||||
for (int i = countPreAdd; i < resultsMesh.Faces.Count; i++)
|
||||
{
|
||||
resultsMesh.FlipFace(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectFaces(Plane plane, List<Mesh> transformedMeshes, Mesh resultsMesh, Matrix4X4 flattenedMatrix, HashSet<int> faceIndicesToRemove)
|
||||
{
|
||||
// get all meshes that have faces on this plane
|
||||
var meshesWithFaces = MeshIndicesForPlane(plane).ToList();
|
||||
|
||||
// we need more than one mesh
|
||||
if (meshesWithFaces.Count < 2)
|
||||
{
|
||||
// no faces to add
|
||||
return;
|
||||
}
|
||||
|
||||
// add the faces that we should remove
|
||||
foreach (var meshIndex in meshesWithFaces)
|
||||
{
|
||||
foreach (var faces in FacesSetsForPlaneAndMesh(plane, meshIndex))
|
||||
{
|
||||
faceIndicesToRemove.Add(faces.destFaceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
var polygonsByMesh = new List<Polygons>();
|
||||
// iterate all the meshes that need to be intersected
|
||||
for (int meshIndex = 0; meshIndex < meshesWithFaces.Count; meshIndex++)
|
||||
{
|
||||
var unionedPoygons = new Polygons();
|
||||
foreach (var removeFaceSets in FacesSetsForPlaneAndMesh(plane, meshIndex))
|
||||
{
|
||||
unionedPoygons = unionedPoygons.Union(GetFacePolygon(transformedMeshes[meshIndex], removeFaceSets.sourceFaceIndex, flattenedMatrix));
|
||||
}
|
||||
|
||||
polygonsByMesh.Add(unionedPoygons);
|
||||
}
|
||||
|
||||
var total = new Polygons(polygonsByMesh[0]);
|
||||
for (int i = 1; i < polygonsByMesh.Count; i++)
|
||||
{
|
||||
var polygonShape = new Polygons();
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(total, PolyType.ptSubject, true);
|
||||
clipper.AddPaths(polygonsByMesh[i], PolyType.ptClip, true);
|
||||
clipper.Execute(ClipType.ctIntersection, polygonShape);
|
||||
|
||||
total = polygonShape;
|
||||
}
|
||||
|
||||
// teselate and add all the new polygons
|
||||
var countPreAdd = resultsMesh.Faces.Count;
|
||||
total.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
EnsureFaceNormals(plane, resultsMesh, countPreAdd);
|
||||
}
|
||||
|
||||
public void UnionFaces(Plane plane, List<Mesh> transformedMeshes, Mesh resultsMesh, Matrix4X4 flattenedMatrix)
|
||||
{
|
||||
// get all meshes that have faces on this plane
|
||||
var meshesWithFaces = MeshIndicesForPlane(plane).ToList();
|
||||
|
||||
if (meshesWithFaces.Count < 2)
|
||||
{
|
||||
// no faces to add
|
||||
return;
|
||||
}
|
||||
|
||||
// sort them so we can process each group into intersections
|
||||
meshesWithFaces.Sort();
|
||||
|
||||
var meshPolygons = new List<Polygons>();
|
||||
for (int i = 0; i < meshesWithFaces.Count; i++)
|
||||
{
|
||||
meshPolygons.Add(new Polygons());
|
||||
var addedFaces = new HashSet<int>();
|
||||
foreach (var (sourceFaceIndex, destFaceIndex) in this.FacesSetsForPlaneAndMesh(plane, i))
|
||||
{
|
||||
if (!addedFaces.Contains(sourceFaceIndex))
|
||||
{
|
||||
meshPolygons[i].Add(GetFacePolygon(transformedMeshes[i], sourceFaceIndex, flattenedMatrix));
|
||||
addedFaces.Add(sourceFaceIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var intersectionSets = new List<Polygons>();
|
||||
// now intersect each set of meshes to get all the sets of intersections
|
||||
for (int i = 0; i < meshesWithFaces.Count; i++)
|
||||
{
|
||||
// add all the faces for mesh j
|
||||
for (int j = i + 1; j < meshesWithFaces.Count; j++)
|
||||
{
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(meshPolygons[i], PolyType.ptSubject, true);
|
||||
clipper.AddPaths(meshPolygons[j], PolyType.ptClip, true);
|
||||
|
||||
var intersection = new Polygons();
|
||||
clipper.Execute(ClipType.ctIntersection, intersection);
|
||||
|
||||
intersectionSets.Add(intersection);
|
||||
}
|
||||
}
|
||||
|
||||
// now union all the intersections
|
||||
var totalSlices = new Polygons(intersectionSets[0]);
|
||||
for (int i = 1; i < intersectionSets.Count; i++)
|
||||
{
|
||||
// clip against the slice based on the parameters
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPaths(totalSlices, PolyType.ptSubject, true);
|
||||
clipper.AddPaths(intersectionSets[i], PolyType.ptClip, true);
|
||||
clipper.Execute(ClipType.ctUnion, totalSlices);
|
||||
}
|
||||
|
||||
// teselate and add all the new polygons
|
||||
var countPreAdd = resultsMesh.Faces.Count;
|
||||
totalSlices.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
EnsureFaceNormals(plane, resultsMesh, countPreAdd);
|
||||
}
|
||||
|
||||
public void StoreFaceAdd(PlaneNormalXSorter planeSorter,
|
||||
Plane facePlane,
|
||||
int sourceMeshIndex,
|
||||
int sourceFaceIndex,
|
||||
int destFaceIndex)
|
||||
{
|
||||
// look through all the planes that are close to this one
|
||||
var plane = planeSorter.FindPlane(facePlane, .02, .0002);
|
||||
if (plane != null)
|
||||
{
|
||||
facePlane = plane.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
if (!coPlanarFaces.ContainsKey(facePlane))
|
||||
{
|
||||
coPlanarFaces[facePlane] = new Dictionary<int, List<(int sourceFace, int destFace)>>();
|
||||
}
|
||||
|
||||
if (!coPlanarFaces[facePlane].ContainsKey(sourceMeshIndex))
|
||||
{
|
||||
coPlanarFaces[facePlane][sourceMeshIndex] = new List<(int sourceFace, int destFace)>();
|
||||
}
|
||||
|
||||
coPlanarFaces[facePlane][sourceMeshIndex].Add((sourceFaceIndex, destFaceIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using ClipperLib;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.PolygonMesh.Csg;
|
||||
using MatterHackers.PolygonMesh.Processors;
|
||||
using MatterHackers.RayTracer;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.PolygonMesh
|
||||
{
|
||||
using Polygons = List<List<IntPoint>>;
|
||||
|
||||
public class CsgBySlicing
|
||||
{
|
||||
private int totalOperations;
|
||||
private List<Mesh> transformedMeshes;
|
||||
private List<ITraceable> bvhAccelerators;
|
||||
private List<List<Plane>> plansByMesh;
|
||||
private PlaneNormalXSorter planeSorter;
|
||||
private Dictionary<Plane, (Matrix4X4 matrix, Matrix4X4 inverted)> transformTo0Planes;
|
||||
|
||||
public CsgBySlicing()
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> meshAndMatrix,
|
||||
Action<double, string> progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
totalOperations = 0;
|
||||
transformedMeshes = new List<Mesh>();
|
||||
bvhAccelerators = new List<ITraceable>();
|
||||
foreach (var (mesh, matrix) in meshAndMatrix)
|
||||
{
|
||||
totalOperations += mesh.Faces.Count;
|
||||
var meshCopy = mesh.Copy(cancellationToken);
|
||||
transformedMeshes.Add(meshCopy);
|
||||
meshCopy.Transform(matrix);
|
||||
bvhAccelerators.Add(MeshToBVH.Convert(meshCopy));
|
||||
}
|
||||
|
||||
plansByMesh = new List<List<Plane>>();
|
||||
var uniquePlanes = new HashSet<Plane>();
|
||||
for (int i = 0; i < transformedMeshes.Count; i++)
|
||||
{
|
||||
var mesh = transformedMeshes[i];
|
||||
plansByMesh.Add(new List<Plane>());
|
||||
for (int j = 0; j < transformedMeshes[i].Faces.Count; j++)
|
||||
{
|
||||
var face = mesh.Faces[j];
|
||||
var cutPlane = new Plane(mesh.Vertices[face.v0].AsVector3(), mesh.Vertices[face.v1].AsVector3(), mesh.Vertices[face.v2].AsVector3());
|
||||
plansByMesh[i].Add(cutPlane);
|
||||
uniquePlanes.Add(cutPlane);
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
planeSorter = new PlaneNormalXSorter(uniquePlanes);
|
||||
transformTo0Planes = new Dictionary<Plane, (Matrix4X4 matrix, Matrix4X4 inverted)>();
|
||||
foreach (var plane in uniquePlanes)
|
||||
{
|
||||
var matrix = SliceLayer.GetTransformTo0Plane(plane);
|
||||
transformTo0Planes[plane] = (matrix, matrix.Inverted);
|
||||
}
|
||||
}
|
||||
|
||||
public Mesh Calculate(CsgModes operation,
|
||||
Action<double, string> progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
double amountPerOperation = 1.0 / totalOperations;
|
||||
double ratioCompleted = 0;
|
||||
|
||||
var resultsMesh = new Mesh();
|
||||
|
||||
// keep track of all the faces added by their plane
|
||||
var coPlanarFaces = new CoPlanarFaces();
|
||||
|
||||
for (var mesh1Index = 0; mesh1Index < transformedMeshes.Count; mesh1Index++)
|
||||
{
|
||||
var mesh1 = transformedMeshes[mesh1Index];
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
for (int faceIndex = 0; faceIndex < mesh1.Faces.Count; faceIndex++)
|
||||
{
|
||||
var face = mesh1.Faces[faceIndex];
|
||||
|
||||
var cutPlane = plansByMesh[mesh1Index][faceIndex];
|
||||
var totalSlice = new Polygons();
|
||||
var firstSlice = true;
|
||||
|
||||
var transformTo0Plane = transformTo0Planes[cutPlane].matrix;
|
||||
for (var sliceMeshIndex = 0; sliceMeshIndex < transformedMeshes.Count; sliceMeshIndex++)
|
||||
{
|
||||
if (mesh1Index == sliceMeshIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var mesh2 = transformedMeshes[sliceMeshIndex];
|
||||
// calculate and add the PWN face from the loops
|
||||
var slice = SliceLayer.CreateSlice(mesh2, cutPlane, transformTo0Plane, bvhAccelerators[sliceMeshIndex]);
|
||||
if (firstSlice)
|
||||
{
|
||||
totalSlice = slice;
|
||||
firstSlice = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalSlice = totalSlice.Union(slice);
|
||||
}
|
||||
}
|
||||
|
||||
// now we have the total loops that this polygon can intersect from the other meshes
|
||||
// make a polygon for this face
|
||||
var facePolygon = CoPlanarFaces.GetFacePolygon(mesh1, faceIndex, transformTo0Plane);
|
||||
|
||||
var polygonShape = new Polygons();
|
||||
// clip against the slice based on the parameters
|
||||
var clipper = new Clipper();
|
||||
clipper.AddPath(facePolygon, PolyType.ptSubject, true);
|
||||
clipper.AddPaths(totalSlice, PolyType.ptClip, true);
|
||||
var expectedFaceNormal = face.normal;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case CsgModes.Union:
|
||||
clipper.Execute(ClipType.ctDifference, polygonShape);
|
||||
break;
|
||||
|
||||
case CsgModes.Subtract:
|
||||
if (mesh1Index == 0)
|
||||
{
|
||||
clipper.Execute(ClipType.ctDifference, polygonShape);
|
||||
}
|
||||
else
|
||||
{
|
||||
expectedFaceNormal *= -1;
|
||||
clipper.Execute(ClipType.ctIntersection, polygonShape);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CsgModes.Intersect:
|
||||
clipper.Execute(ClipType.ctIntersection, polygonShape);
|
||||
break;
|
||||
}
|
||||
|
||||
var faceCountPreAdd = resultsMesh.Faces.Count;
|
||||
|
||||
if (polygonShape.Count == 1
|
||||
&& polygonShape[0].Count == 3
|
||||
&& facePolygon.Contains(polygonShape[0][0])
|
||||
&& facePolygon.Contains(polygonShape[0][1])
|
||||
&& facePolygon.Contains(polygonShape[0][2]))
|
||||
{
|
||||
resultsMesh.AddFaceCopy(mesh1, faceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
var preAddCount = resultsMesh.Vertices.Count;
|
||||
// mesh the new polygon and add it to the resultsMesh
|
||||
polygonShape.Vertices(1).TriangulateFaces(null, resultsMesh, 0, transformTo0Planes[cutPlane].inverted);
|
||||
var postAddCount = resultsMesh.Vertices.Count;
|
||||
|
||||
for (int addedIndex = preAddCount; addedIndex < postAddCount; addedIndex++)
|
||||
{
|
||||
// TODO: map all the added vertices that can be back to the original polygon positions
|
||||
for (int meshIndex = 0; meshIndex < transformedMeshes.Count; meshIndex++)
|
||||
{
|
||||
var bvhAccelerator = bvhAccelerators[meshIndex];
|
||||
var mesh = transformedMeshes[meshIndex];
|
||||
var addedPosition = resultsMesh.Vertices[addedIndex];
|
||||
var touchingBvhItems = bvhAccelerator.GetTouching(new Vector3(addedPosition), .0001);
|
||||
foreach (var touchingBvhItem in touchingBvhItems)
|
||||
{
|
||||
if (touchingBvhItem is TriangleShape triangleShape)
|
||||
{
|
||||
var sourceFaceIndex = triangleShape.Index;
|
||||
var sourceFace = mesh.Faces[sourceFaceIndex];
|
||||
var sourceVertexIndices = new int[] { sourceFace.v0, sourceFace.v1, sourceFace.v2 };
|
||||
foreach (var sourceVertexIndex in sourceVertexIndices)
|
||||
{
|
||||
var sourcePosition = mesh.Vertices[sourceVertexIndex];
|
||||
var deltaSquared = (addedPosition - sourcePosition).LengthSquared;
|
||||
if (deltaSquared > 0 && deltaSquared < .00001)
|
||||
{
|
||||
// add the vertex and set the face position index to the new vertex
|
||||
resultsMesh.Vertices[addedIndex] = sourcePosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resultsMesh.Faces.Count - faceCountPreAdd > 0)
|
||||
{
|
||||
// keep track of the adds so we can process the coplanar faces after
|
||||
for (int i = faceCountPreAdd; i < resultsMesh.Faces.Count; i++)
|
||||
{
|
||||
coPlanarFaces.StoreFaceAdd(planeSorter, cutPlane, mesh1Index, faceIndex, i);
|
||||
// make sure our added faces are the right direction
|
||||
if (resultsMesh.Faces[i].normal.Dot(expectedFaceNormal) < 0)
|
||||
{
|
||||
resultsMesh.FlipFace(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
else // we did not add any faces but we will still keep track of this polygons plan
|
||||
{
|
||||
coPlanarFaces.StoreFaceAdd(planeSorter, cutPlane, mesh1Index, faceIndex, -1);
|
||||
}
|
||||
|
||||
ratioCompleted += amountPerOperation;
|
||||
progressReporter?.Invoke(ratioCompleted, "");
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle the co-planar faces
|
||||
ProcessCoplanarFaces(operation, resultsMesh, coPlanarFaces);
|
||||
|
||||
resultsMesh.CleanAndMerge();
|
||||
return resultsMesh;
|
||||
}
|
||||
|
||||
private void ProcessCoplanarFaces(CsgModes operation, Mesh resultsMesh, CoPlanarFaces coPlanarFaces)
|
||||
{
|
||||
var faceIndicesToRemove = new HashSet<int>();
|
||||
foreach (var plane in coPlanarFaces.Planes)
|
||||
{
|
||||
var meshIndices = coPlanarFaces.MeshIndicesForPlane(plane);
|
||||
if (meshIndices.Count() > 1)
|
||||
{
|
||||
// check if more than one mesh has this polygons on this plan
|
||||
var flattenedMatrix = transformTo0Planes[plane].matrix;
|
||||
|
||||
// depending on the operation add or remove polygons that are planar
|
||||
switch (operation)
|
||||
{
|
||||
case CsgModes.Union:
|
||||
coPlanarFaces.UnionFaces(plane, transformedMeshes, resultsMesh, flattenedMatrix);
|
||||
break;
|
||||
|
||||
case CsgModes.Subtract:
|
||||
coPlanarFaces.SubtractFaces(plane, transformedMeshes, resultsMesh, flattenedMatrix, faceIndicesToRemove);
|
||||
break;
|
||||
|
||||
case CsgModes.Intersect:
|
||||
coPlanarFaces.IntersectFaces(plane, transformedMeshes, resultsMesh, flattenedMatrix, faceIndicesToRemove);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// now rebuild the face list without the remove polygons
|
||||
if (faceIndicesToRemove.Count > 0)
|
||||
{
|
||||
var newFaces = new FaceList();
|
||||
for (int i = 0; i < resultsMesh.Faces.Count; i++)
|
||||
{
|
||||
// if the face is NOT in the remove faces
|
||||
if (!faceIndicesToRemove.Contains(i))
|
||||
{
|
||||
var face = resultsMesh.Faces[i];
|
||||
newFaces.Add(face);
|
||||
}
|
||||
}
|
||||
|
||||
resultsMesh.Faces = newFaces;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using g3;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public static class MeshExtensions
|
||||
{
|
||||
public static DMesh3 ToDMesh3(this Mesh inMesh)
|
||||
{
|
||||
var outMesh = new DMesh3();
|
||||
foreach (var vertex in inMesh.Vertices)
|
||||
{
|
||||
outMesh.AppendVertex(new Vector3d(vertex.X, vertex.Y, vertex.Z));
|
||||
}
|
||||
|
||||
foreach (var face in inMesh.Faces)
|
||||
{
|
||||
outMesh.AppendTriangle(face.v0, face.v1, face.v2);
|
||||
}
|
||||
|
||||
return outMesh;
|
||||
}
|
||||
|
||||
public static Mesh ToMesh(this DMesh3 mesh)
|
||||
{
|
||||
var outMesh = new Mesh();
|
||||
int[] mapV = new int[mesh.MaxVertexID];
|
||||
int nAccumCountV = 0;
|
||||
foreach (int vi in mesh.VertexIndices())
|
||||
{
|
||||
mapV[vi] = nAccumCountV++;
|
||||
Vector3d v = mesh.GetVertex(vi);
|
||||
outMesh.Vertices.Add(new Vector3(v[0], v[1], v[2]));
|
||||
}
|
||||
|
||||
foreach (int ti in mesh.TriangleIndices())
|
||||
{
|
||||
Index3i t = mesh.GetTriangle(ti);
|
||||
t[0] = mapV[t[0]];
|
||||
t[1] = mapV[t[1]];
|
||||
t[2] = mapV[t[2]];
|
||||
outMesh.Faces.Add(t[0], t[1], t[2], outMesh.Vertices);
|
||||
}
|
||||
|
||||
return outMesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2019, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ClipperLib;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.PolygonMesh
|
||||
{
|
||||
public class PlaneNormalXSorter : IComparer<Plane>
|
||||
{
|
||||
private readonly List<Plane> planes;
|
||||
|
||||
public PlaneNormalXSorter(IEnumerable<Plane> inputPlanes)
|
||||
{
|
||||
planes = new List<Plane>(inputPlanes);
|
||||
planes.Sort(this);
|
||||
}
|
||||
|
||||
public int Compare(Plane a, Plane b)
|
||||
{
|
||||
return a.Normal.X.CompareTo(b.Normal.X);
|
||||
}
|
||||
|
||||
public Plane? FindPlane(Plane searchPlane,
|
||||
double distanceErrorValue = .01,
|
||||
double normalErrorValue = .0001)
|
||||
{
|
||||
Plane testPlane = searchPlane;
|
||||
int index = planes.BinarySearch(testPlane, this);
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
// we have the starting index now get all the vertices that are close enough starting from here
|
||||
for (int i = index; i < planes.Count; i++)
|
||||
{
|
||||
if (Math.Abs(planes[i].Normal.X - searchPlane.Normal.X) > normalErrorValue)
|
||||
{
|
||||
// we are too far away in x, we are done with this direction
|
||||
break;
|
||||
}
|
||||
|
||||
if (planes[i].Equals(searchPlane, distanceErrorValue, normalErrorValue))
|
||||
{
|
||||
return planes[i];
|
||||
}
|
||||
}
|
||||
for (int i = index - 1; i >= 0; i--)
|
||||
{
|
||||
if (Math.Abs(planes[i].Normal.X - searchPlane.Normal.X) > normalErrorValue)
|
||||
{
|
||||
// we are too far away in x, we are done with this direction
|
||||
break;
|
||||
}
|
||||
|
||||
if (planes[i].Equals(searchPlane, distanceErrorValue, normalErrorValue))
|
||||
{
|
||||
return planes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue