Accelerating csg operations
This commit is contained in:
parent
aa461bc764
commit
fa7447688f
4 changed files with 165 additions and 57 deletions
|
|
@ -245,7 +245,7 @@ namespace MatterHackers.PolygonMesh
|
|||
return implicitResult;
|
||||
}
|
||||
|
||||
private static Mesh ExactBySlicing(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> items2,
|
||||
private static Mesh ExactBySlicing(IEnumerable<(Mesh mesh, Matrix4X4 matrix)> meshAndMatrix,
|
||||
CsgModes operation,
|
||||
IProgress<ProgressStatus> reporter,
|
||||
CancellationToken cancellationToken,
|
||||
|
|
@ -255,13 +255,36 @@ namespace MatterHackers.PolygonMesh
|
|||
var progressStatus = new ProgressStatus();
|
||||
var totalOperations = 0;
|
||||
var transformedMeshes = new List<Mesh>();
|
||||
foreach (var (mesh, matrix) in items2)
|
||||
foreach (var (mesh, matrix) in meshAndMatrix)
|
||||
{
|
||||
totalOperations += mesh.Faces.Count;
|
||||
transformedMeshes.Add(mesh.Copy(CancellationToken.None));
|
||||
transformedMeshes.Last().Transform(matrix);
|
||||
}
|
||||
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
|
||||
PlaneNormalXSorter planeSorter = new PlaneNormalXSorter(uniquePlanes);
|
||||
var transformTo0Planes = new Dictionary<Plane, (Matrix4X4 matrix, Matrix4X4 inverted)>();
|
||||
foreach(var plane in uniquePlanes)
|
||||
{
|
||||
var matrix = SliceLayer.GetTransformTo0Plane(plane);
|
||||
transformTo0Planes[plane] = (matrix, matrix.Inverted);
|
||||
}
|
||||
|
||||
double amountPerOperation = 1.0 / totalOperations * amountPerOperationIn;
|
||||
double percentCompleted = percentCompletedIn;
|
||||
|
||||
|
|
@ -279,12 +302,11 @@ namespace MatterHackers.PolygonMesh
|
|||
{
|
||||
var face = mesh1.Faces[faceIndex];
|
||||
|
||||
var cutPlane = new Plane(mesh1.Vertices[face.v0].AsVector3(), mesh1.Vertices[face.v1].AsVector3(), mesh1.Vertices[face.v2].AsVector3());
|
||||
var flattenedMatrix = CoPlanarFaces.GetFlattenedMatrix(cutPlane);
|
||||
var flattenedMatrixInverted = flattenedMatrix.Inverted;
|
||||
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)
|
||||
|
|
@ -294,7 +316,7 @@ namespace MatterHackers.PolygonMesh
|
|||
|
||||
var mesh2 = transformedMeshes[sliceMeshIndex];
|
||||
// calculate and add the PWN face from the loops
|
||||
var slice = SliceLayer.CreateSlice(mesh2, cutPlane);
|
||||
var slice = SliceLayer.CreateSlice(mesh2, cutPlane, transformTo0Plane);
|
||||
if (firstSlice)
|
||||
{
|
||||
totalSlice = slice;
|
||||
|
|
@ -308,7 +330,7 @@ namespace MatterHackers.PolygonMesh
|
|||
|
||||
// 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, cutPlane, flattenedMatrix);
|
||||
var facePolygon = CoPlanarFaces.GetFacePolygon(mesh1, faceIndex, transformTo0Plane);
|
||||
|
||||
var polygonShape = new Polygons();
|
||||
// clip against the slice based on the parameters
|
||||
|
|
@ -355,7 +377,7 @@ namespace MatterHackers.PolygonMesh
|
|||
{
|
||||
var preAddCount = resultsMesh.Vertices.Count;
|
||||
// mesh the new polygon and add it to the resultsMesh
|
||||
polygonShape.Vertices().TriangulateFaces(null, resultsMesh, 0, flattenedMatrixInverted);
|
||||
polygonShape.Vertices(1).TriangulateFaces(null, resultsMesh, 0, transformTo0Planes[cutPlane].inverted);
|
||||
|
||||
// TODO: map all the added vertices that can be back to the original polygon positions
|
||||
// for (int i = preAddCount; i< resultsMesh.Vertices.Count; i++)
|
||||
|
|
@ -369,7 +391,7 @@ namespace MatterHackers.PolygonMesh
|
|||
// keep track of the adds so we can process the coplanar faces after
|
||||
for (int i = faceCountPreAdd; i < resultsMesh.Faces.Count; i++)
|
||||
{
|
||||
coPlanarFaces.StoreFaceAdd(cutPlane, mesh1Index, faceIndex, 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)
|
||||
{
|
||||
|
|
@ -379,7 +401,7 @@ namespace MatterHackers.PolygonMesh
|
|||
}
|
||||
else // we did not add any faces but we will still keep track of this polygons plan
|
||||
{
|
||||
coPlanarFaces.StoreFaceAdd(cutPlane, mesh1Index, faceIndex, -1);
|
||||
coPlanarFaces.StoreFaceAdd(planeSorter, cutPlane, mesh1Index, faceIndex, -1);
|
||||
}
|
||||
|
||||
percentCompleted += amountPerOperation;
|
||||
|
|
@ -393,6 +415,7 @@ namespace MatterHackers.PolygonMesh
|
|||
}
|
||||
}
|
||||
|
||||
// handle the co-planar faces
|
||||
var faceIndicesToRemove = new HashSet<int>();
|
||||
foreach (var plane in coPlanarFaces.Planes)
|
||||
{
|
||||
|
|
@ -400,7 +423,7 @@ namespace MatterHackers.PolygonMesh
|
|||
if (meshIndices.Count() > 1)
|
||||
{
|
||||
// check if more than one mesh has this polygons on this plan
|
||||
var flattenedMatrix = CoPlanarFaces.GetFlattenedMatrix(plane);
|
||||
var flattenedMatrix = transformTo0Planes[plane].matrix;
|
||||
|
||||
// depending on the operation add or remove polygons that are planar
|
||||
switch (operation)
|
||||
|
|
|
|||
|
|
@ -36,10 +36,10 @@ using MatterHackers.VectorMath;
|
|||
|
||||
namespace MatterHackers.PolygonMesh
|
||||
{
|
||||
using Polygon = List<IntPoint>;
|
||||
using Polygons = List<List<IntPoint>>;
|
||||
|
||||
public class CoPlanarFaces
|
||||
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)>>>();
|
||||
|
|
@ -64,7 +64,7 @@ namespace MatterHackers.PolygonMesh
|
|||
}
|
||||
|
||||
public IEnumerable<(int sourceFaceIndex, int destFaceIndex)> FacesSetsForPlaneAndMesh(Plane plane, int meshIndex)
|
||||
{
|
||||
{
|
||||
if (coPlanarFaces[plane].ContainsKey(meshIndex))
|
||||
{
|
||||
foreach (var faceIndices in coPlanarFaces[plane][meshIndex])
|
||||
|
|
@ -72,20 +72,10 @@ namespace MatterHackers.PolygonMesh
|
|||
yield return faceIndices;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Matrix4X4 GetFlattenedMatrix(Plane cutPlane)
|
||||
{
|
||||
var rotation = new Quaternion(cutPlane.Normal, Vector3.UnitZ);
|
||||
var flattenedMatrix = Matrix4X4.CreateRotation(rotation);
|
||||
flattenedMatrix *= Matrix4X4.CreateTranslation(0, 0, -cutPlane.DistanceFromOrigin);
|
||||
|
||||
return flattenedMatrix;
|
||||
}
|
||||
|
||||
public static Polygon GetFacePolygon(Mesh mesh1, int faceIndex, Plane cutPlane, Matrix4X4 flattenedMatrix)
|
||||
public static Polygon GetFacePolygon(Mesh mesh1, int faceIndex, Matrix4X4 meshTo0Plane)
|
||||
{
|
||||
var meshTo0Plane = flattenedMatrix * Matrix4X4.CreateScale(1000);
|
||||
var facePolygon = new Polygon();
|
||||
var vertices = mesh1.Vertices;
|
||||
var face = mesh1.Faces[faceIndex];
|
||||
|
|
@ -119,29 +109,29 @@ namespace MatterHackers.PolygonMesh
|
|||
meshesWithFaces.Sort();
|
||||
|
||||
// add the faces that we should
|
||||
foreach(var meshIndex in meshesWithFaces)
|
||||
{
|
||||
foreach(var faces in FacesSetsForPlaneAndMesh(plane, meshIndex))
|
||||
{
|
||||
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))
|
||||
{
|
||||
keepPolygons = keepPolygons.Union(GetFacePolygon(transformedMeshes[0], keepFaceSets.sourceFaceIndex, plane, flattenedMatrix));
|
||||
keepPolygons = keepPolygons.Union(GetFacePolygon(transformedMeshes[0], keepFaceSets.sourceFaceIndex, flattenedMatrix));
|
||||
}
|
||||
|
||||
// iterate all the meshes that need to be subtracted
|
||||
var removePoygons = new Polygons();
|
||||
for (int removeMeshIndex= 1; removeMeshIndex < meshesWithFaces.Count; removeMeshIndex++)
|
||||
{
|
||||
for (int removeMeshIndex = 1; removeMeshIndex < meshesWithFaces.Count; removeMeshIndex++)
|
||||
{
|
||||
foreach (var removeFaceSets in FacesSetsForPlaneAndMesh(plane, removeMeshIndex))
|
||||
{
|
||||
removePoygons = removePoygons.Union(GetFacePolygon(transformedMeshes[removeMeshIndex], removeFaceSets.sourceFaceIndex, plane, flattenedMatrix));
|
||||
removePoygons = removePoygons.Union(GetFacePolygon(transformedMeshes[removeMeshIndex], removeFaceSets.sourceFaceIndex, flattenedMatrix));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +142,7 @@ namespace MatterHackers.PolygonMesh
|
|||
clipper.Execute(ClipType.ctDifference, polygonShape);
|
||||
|
||||
// teselate and add all the new polygons
|
||||
polygonShape.Vertices().TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
polygonShape.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
}
|
||||
|
||||
public void IntersectFaces(Plane plane, List<Mesh> transformedMeshes, Mesh resultsMesh, Matrix4X4 flattenedMatrix, HashSet<int> faceIndicesToRemove)
|
||||
|
|
@ -183,14 +173,14 @@ namespace MatterHackers.PolygonMesh
|
|||
var unionedPoygons = new Polygons();
|
||||
foreach (var removeFaceSets in FacesSetsForPlaneAndMesh(plane, meshIndex))
|
||||
{
|
||||
unionedPoygons = unionedPoygons.Union(GetFacePolygon(transformedMeshes[meshIndex], removeFaceSets.sourceFaceIndex, plane, flattenedMatrix));
|
||||
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++)
|
||||
for (int i = 1; i < polygonsByMesh.Count; i++)
|
||||
{
|
||||
var polygonShape = new Polygons();
|
||||
var clipper = new Clipper();
|
||||
|
|
@ -202,7 +192,7 @@ namespace MatterHackers.PolygonMesh
|
|||
}
|
||||
|
||||
// teselate and add all the new polygons
|
||||
total.Vertices().TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
total.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
}
|
||||
|
||||
public void UnionFaces(Plane plane, List<Mesh> transformedMeshes, Mesh resultsMesh, Matrix4X4 flattenedMatrix)
|
||||
|
|
@ -211,10 +201,10 @@ namespace MatterHackers.PolygonMesh
|
|||
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();
|
||||
|
|
@ -228,7 +218,7 @@ namespace MatterHackers.PolygonMesh
|
|||
{
|
||||
if (!addedFaces.Contains(sourceFaceIndex))
|
||||
{
|
||||
meshPolygons[i].Add(GetFacePolygon(transformedMeshes[i], sourceFaceIndex, plane, flattenedMatrix));
|
||||
meshPolygons[i].Add(GetFacePolygon(transformedMeshes[i], sourceFaceIndex, flattenedMatrix));
|
||||
addedFaces.Add(sourceFaceIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -239,8 +229,8 @@ namespace MatterHackers.PolygonMesh
|
|||
for (int i = 0; i < meshesWithFaces.Count; i++)
|
||||
{
|
||||
// add all the faces for mesh j
|
||||
for (int j=i+1; j<meshesWithFaces.Count; 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);
|
||||
|
|
@ -255,7 +245,7 @@ namespace MatterHackers.PolygonMesh
|
|||
// 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);
|
||||
|
|
@ -264,23 +254,25 @@ namespace MatterHackers.PolygonMesh
|
|||
}
|
||||
|
||||
// teselate and add all the new polygons
|
||||
totalSlices.Vertices().TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
totalSlices.Vertices(1).TriangulateFaces(null, resultsMesh, 0, flattenedMatrix.Inverted);
|
||||
}
|
||||
|
||||
public void StoreFaceAdd(Plane facePlane,
|
||||
public void StoreFaceAdd(PlaneNormalXSorter planeSorter,
|
||||
Plane facePlane,
|
||||
int sourceMeshIndex,
|
||||
int sourceFaceIndex,
|
||||
int destFaceIndex)
|
||||
{
|
||||
foreach (var plane in coPlanarFaces.Keys)
|
||||
// look through all the planes that are close to this one
|
||||
var plane = planeSorter.FindPlane(facePlane, .02, .0002);
|
||||
if (plane != null)
|
||||
{
|
||||
// check if they are close enough
|
||||
if (facePlane.Equals(plane))
|
||||
{
|
||||
facePlane = plane;
|
||||
break;
|
||||
}
|
||||
facePlane = plane.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
int a = 0;
|
||||
}
|
||||
|
||||
if (!coPlanarFaces.ContainsKey(facePlane))
|
||||
{
|
||||
|
|
|
|||
93
MatterControl.MeshOperations/PlaneNormalXSorter.cs
Normal file
93
MatterControl.MeshOperations/PlaneNormalXSorter.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit b380ed2929a7fbcc1dd757b5a697d8b1e535456a
|
||||
Subproject commit beea93ece48c0ec46fbf0b037106aa3a9e3baf5a
|
||||
Loading…
Add table
Add a link
Reference in a new issue