/// <summary> /// Shows the specified tessellated solid in a Helix toolkit window. /// </summary> /// <param name="tessellatedSolid">The tessellated solid.</param> public static void Show(TessellatedSolid tessellatedSolid) { var window = new MainWindow(); window.view1.Children.Add(MakeModelVisual3D(tessellatedSolid)); window.view1.ZoomExtentsWhenLoaded = true; window.ShowDialog(); }
/// <summary> /// When the tessellated solid is sliced at the specified plane, the contact surfaces are /// described by the return ContactData object. This is a non-destructive function typically /// used to find the shape and size of 2D surface on the prescribed plane.. /// </summary> /// <param name="plane">The plane.</param> /// <param name="ts">The ts.</param> /// <returns>ContactData.</returns> /// <exception cref="System.Exception">Contact Edges found that are not contained in loop.</exception> public static ContactData DefineContact(Flat plane, TessellatedSolid ts) { var vertexDistancesToPlane = new double[ts.NumberOfVertices]; for (int i = 0; i < ts.NumberOfVertices; i++) vertexDistancesToPlane[i] = ts.Vertices[i].Position.dotProduct(plane.Normal) - plane.DistanceToOrigin; // the edges serve as the easiest way to identify where the solid is interacting with the plane. // Instead of a foreach, the while loop lets us look ahead to known edges that are irrelevant. var edgeHashSet = new HashSet<Edge>(ts.Edges); // Contact elements are constructed and then later arranged into loops. Loops make up the returned object, ContactData. var straddleContactElts = new List<ContactElement>(); var inPlaneContactElts = new List<CoincidentEdgeContactElement>(); while (edgeHashSet.Any()) { // instead of the foreach, we have this while statement and these first 2 lines to enumerate over the edges. var edge = edgeHashSet.First(); edgeHashSet.Remove(edge); var toDistance = vertexDistancesToPlane[edge.To.IndexInList]; var fromDistance = vertexDistancesToPlane[edge.From.IndexInList]; if (StarMath.IsNegligible(toDistance) && StarMath.IsNegligible(fromDistance)) ContactElement.MakeInPlaneContactElement(plane, edge, edgeHashSet, vertexDistancesToPlane, inPlaneContactElts); else if ((toDistance > 0 && fromDistance < 0) || (toDistance < 0 && fromDistance > 0)) straddleContactElts.Add(new ThroughFaceContactElement(plane, edge, toDistance)); } foreach (var contactElement in inPlaneContactElts) { // next, we find any additional vertices that just touch the plane but don't have in-plane edges // to facilitate this we negate all vertices already captures in the inPlaneContactElts vertexDistancesToPlane[contactElement.StartVertex.IndexInList] = double.NaN; vertexDistancesToPlane[contactElement.EndVertex.IndexInList] = double.NaN; } for (int i = 0; i < ts.NumberOfVertices; i++) { if (!StarMath.IsNegligible(vertexDistancesToPlane[i])) continue; var v = ts.Vertices[i]; PolygonalFace negativeFace, positiveFace; if (ThroughVertexContactElement.FindNegativeAndPositiveFaces(plane, v, vertexDistancesToPlane, out negativeFace, out positiveFace)) straddleContactElts.Add(new ThroughVertexContactElement(v, negativeFace, positiveFace)); } straddleContactElts.AddRange(inPlaneContactElts); var loops = new List<Loop>(); var numberOfTries = 0; while (straddleContactElts.Any() && numberOfTries < straddleContactElts.Count) { // now build loops from stringing together contact elements var loop = FindLoop(plane, straddleContactElts, vertexDistancesToPlane); if (loop != null) { Debug.WriteLine(loops.Count + ": " + loop.MakeDebugContactString() + " "); loops.Add(loop); numberOfTries = 0; } else numberOfTries++; } if (straddleContactElts.Any()) Debug.WriteLine("Contact Edges found that are not contained in loop."); return new ContactData(loops); }
private static Visual3D MakeModelVisual3D(TessellatedSolid ts) { var defaultMaterial = MaterialHelper.CreateMaterial( new Color { A = ts.SolidColor.A, B = ts.SolidColor.B, G = ts.SolidColor.G, R = ts.SolidColor.R }); if (ts.HasUniformColor) { var positions = ts.Faces.SelectMany(f => f.Vertices.Select(v => new Point3D(v.Position[0], v.Position[1], v.Position[2]))); var normals = ts.Faces.SelectMany(f => f.Vertices.Select(v => new Vector3D(f.Normal[0], f.Normal[1], f.Normal[2]))); return new ModelVisual3D { Content = new GeometryModel3D { Geometry = new MeshGeometry3D { Positions = new Point3DCollection(positions), // TriangleIndices = new Int32Collection(triIndices), Normals = new Vector3DCollection(normals) }, Material = defaultMaterial } }; } var result = new ModelVisual3D(); foreach (var f in ts.Faces) { var vOrder = new Point3DCollection(); for (var i = 0; i < 3; i++) vOrder.Add(new Point3D(f.Vertices[i].X, f.Vertices[i].Y, f.Vertices[i].Z)); var c = (f.color == null) ? defaultMaterial : MaterialHelper.CreateMaterial(new Color { A = f.color.A, B = f.color.B, G = f.color.G, R = f.color.R }); result.Children.Add(new ModelVisual3D { Content = new GeometryModel3D { Geometry = new MeshGeometry3D { Positions = vOrder }, Material = c } }); } return result; }
/// <summary> /// Divides up contact. /// </summary> /// <param name="ts">The ts.</param> /// <param name="contactData">The contact data.</param> /// <param name="plane">The plane.</param> /// <exception cref="System.Exception">face is supposed to be split at plane but lives only on one side</exception> private static void DivideUpContact(TessellatedSolid ts, ContactData contactData, Flat plane) { var edgesToAdd = new List<Edge>(); var facesToAdd = new List<PolygonalFace>(); var verticesToAdd = new List<Vertex>(); var edgesToDelete = new List<Edge>(); var facesToDelete = new List<PolygonalFace>(); var edgesToModify = new List<Edge>(); foreach (var loop in contactData.AllLoops) { for (int i = 0; i < loop.Count; i++) { var ce = loop[i]; if (ce is CoincidentEdgeContactElement) // If the contact element is at a coincident edge, then there is nothing to do in this stage. When contact element was // created, it properly defined SplitFacePositive and SplitFaceNegative. continue; edgesToAdd.Add(ce.ContactEdge); // the contact edge is a new edge for the solid edgesToModify.Add(ce.ContactEdge); // the contact edge will need to be linked to vertices and faces further down. var faceToSplit = ce.SplitFacePositive; //faceToSplit will be removed, but before we do that, we use facesToDelete.Add(faceToSplit); // use it to build the new 2 to 3 triangles PolygonalFace positiveFace, negativeFace; if (ce is ThroughVertexContactElement) { var vertPlaneDistances = //signed distances of faceToSplit's vertices from the plane faceToSplit.Vertices.Select( v => v.Position.dotProduct(plane.Normal) - plane.DistanceToOrigin).ToArray(); var maxIndex = vertPlaneDistances.FindIndex(vertPlaneDistances.Max()); var maxVert = faceToSplit.Vertices[maxIndex]; var minIndex = vertPlaneDistances.FindIndex(vertPlaneDistances.Min()); var minVert = faceToSplit.Vertices[minIndex]; positiveFace = new PolygonalFace(new[] { ce.ContactEdge.From, ce.ContactEdge.To, maxVert }); facesToAdd.Add(positiveFace); negativeFace = new PolygonalFace(new[] { ce.ContactEdge.To, ce.ContactEdge.From, minVert }); facesToAdd.Add(negativeFace); } //#+1 add v to f (both of these are done in the preceding PolygonalFace //#+2 add f to v constructors as well as the one for thirdFace below) else // then ce is a ThroughFaceContactElement { var tfce = (ThroughFaceContactElement)ce; // ce is renamed and recast as tfce edgesToDelete.Add(tfce.SplitEdge); verticesToAdd.Add(tfce.StartVertex); Vertex positiveVertex, negativeVertex; if (tfce.SplitEdge.To.Position.dotProduct(plane.Normal) > plane.DistanceToOrigin) { positiveVertex = tfce.SplitEdge.To; negativeVertex = tfce.SplitEdge.From; } else { positiveVertex = tfce.SplitEdge.From; negativeVertex = tfce.SplitEdge.To; } positiveFace = new PolygonalFace(new[] { ce.ContactEdge.From, ce.ContactEdge.To, positiveVertex }); facesToAdd.Add(positiveFace); negativeFace = new PolygonalFace(new[] { ce.ContactEdge.To, ce.ContactEdge.From, negativeVertex }); facesToAdd.Add(negativeFace); var positiveEdge = new Edge(positiveVertex, ce.ContactEdge.From, positiveFace, null, true, true); edgesToAdd.Add(positiveEdge); edgesToModify.Add(positiveEdge); var negativeEdge = new Edge(ce.ContactEdge.From, negativeVertex, negativeFace, null, true, true); edgesToAdd.Add(negativeEdge); edgesToModify.Add(negativeEdge); var otherVertex = faceToSplit.Vertices.First(v => v != positiveVertex && v != negativeVertex); PolygonalFace thirdFace; if (otherVertex.Position.dotProduct(plane.Normal) > plane.DistanceToOrigin) { thirdFace = new PolygonalFace(new[] { ce.ContactEdge.To, otherVertex, positiveVertex }); facesToAdd.Add(thirdFace); edgesToAdd.Add(new Edge(ce.ContactEdge.To, positiveVertex, positiveFace, thirdFace, true, true)); } else { thirdFace = new PolygonalFace(new[] { ce.ContactEdge.To, negativeVertex, otherVertex }); facesToAdd.Add(thirdFace); edgesToAdd.Add(new Edge(negativeVertex, ce.ContactEdge.To, negativeFace, thirdFace, true, true)); } // for the new edges in a through face this line accomplishes: +3 add f to e; +4 add e to f; +5 add v to e; // +6 add e to v } loop[i] = new CoincidentEdgeContactElement { ContactEdge = ce.ContactEdge, EndVertex = ce.ContactEdge.To, StartVertex = ce.ContactEdge.From, SplitFaceNegative = negativeFace, SplitFacePositive = positiveFace }; } } // -1 remove v from f - no need to do this as no v's are removed foreach (var face in facesToDelete) { foreach (var vertex in face.Vertices) vertex.Faces.Remove(face); //-2 remove f from v foreach (var edge in face.Edges) { if (edgesToDelete.Contains(edge)) continue; edgesToModify.Add(edge); if (edge.OwnedFace == face) edge.OwnedFace = null; //-3 remove f from e else edge.OtherFace = null; } } //-4 remove e from f - no need to do as the only edges deleted are the ones between deleted faces ts.RemoveFaces(facesToDelete); // -5 remove v from e - not needed as no vertices are deleted (like -1 above) foreach (var edge in edgesToDelete) { edge.From.Edges.Remove(edge); //-6 remove e from v edge.To.Edges.Remove(edge); } ts.RemoveEdges(edgesToDelete); // now to add new faces to modified edges ts.AddVertices(verticesToAdd); ts.AddFaces(facesToAdd); foreach (var edge in edgesToModify) { var facesToAttach = facesToAdd.Where(f => f.Vertices.Contains(edge.To) && f.Vertices.Contains(edge.From) && !f.Edges.Contains(edge)); if (facesToAttach.Count() > 2) throw new Exception(); foreach (var face in facesToAttach) { face.Edges.Add(edge); //+4 add e to f var fromIndex = face.Vertices.IndexOf(edge.From); if ((fromIndex == face.Vertices.Count - 1 && face.Vertices[0] == edge.To) || (fromIndex < face.Vertices.Count - 1 && face.Vertices[fromIndex + 1] == edge.To)) edge.OwnedFace = face; //+3 add f to e else edge.OtherFace = face; } } ts.AddEdges(edgesToAdd); }
private static List<TessellatedSolid> convertFaceListsToSolids(TessellatedSolid ts, List<List<PolygonalFace>> facesLists, List<Loop> loops, Boolean onPositiveSide, Flat plane) { List<TessellatedSolid> solids = new List<TessellatedSolid>(); foreach (var facesList in facesLists) { // get a list of the vertex indices from the original solid var vertIndices = facesList.SelectMany(f => f.Vertices.Select(v => v.IndexInList)) .Distinct().OrderBy(index => index).ToArray(); var numVertices = vertIndices.Count(); // get the set of connected loops for this list of faces. it could be one or it could be all var connectedLoops = loops.Where(loop => (onPositiveSide && loop.Any(ce => facesList.Contains(ce.SplitFacePositive))) || (!onPositiveSide && loop.Any(ce => facesList.Contains(ce.SplitFaceNegative)))) .ToList(); // put the vertices from vertIndices in subSolidVertices, except those that are on the loop. // you'll need to copy those. var subSolidVertices = new Vertex[numVertices]; var indicesToCopy = connectedLoops.SelectMany(loop => loop.Select(ce => ce.StartVertex.IndexInList)) .OrderBy(index => index).ToArray(); var numIndicesToCopy = indicesToCopy.GetLength(0); var newEdgeVertices = new Vertex[connectedLoops.Count][]; for (int i = 0; i < connectedLoops.Count; i++) newEdgeVertices[i] = new Vertex[connectedLoops[i].Count]; var copyIndex = 0; for (int i = 0; i < numVertices; i++) { Vertex vertexCopy; if (copyIndex < numIndicesToCopy && vertIndices[i] == indicesToCopy[copyIndex]) { var oldVertex = ts.Vertices[vertIndices[i]]; vertexCopy = oldVertex.Copy(); for (int j = 0; j < connectedLoops.Count; j++) { var k = connectedLoops[j].FindIndex(ce => ce.StartVertex == oldVertex); newEdgeVertices[j][k] = vertexCopy; } foreach (var face in oldVertex.Faces.Where(face => facesList.Contains(face))) { face.Vertices.Remove(oldVertex); face.Vertices.Add(vertexCopy); vertexCopy.Faces.Add(face); } while (copyIndex < numIndicesToCopy && vertIndices[i] >= indicesToCopy[copyIndex]) copyIndex++; } else vertexCopy = ts.Vertices[vertIndices[i]]; vertexCopy.IndexInList = i; subSolidVertices[i] = vertexCopy; } solids.Add(new TessellatedSolid(facesList, subSolidVertices, newEdgeVertices, onPositiveSide ? plane.Normal.multiply(-1) : plane.Normal, connectedLoops.Select(loop => loop.IsPositive).ToArray())); } return solids; }
/// <summary> /// Performs the slicing operation on the prescribed flat plane. This destructively alters /// the tessellated solid into one or more solids which are returned in the "out" parameter /// lists. /// </summary> /// <param name="oldSolid">The old solid.</param> /// <param name="plane">The plane.</param> /// <param name="positiveSideSolids">The solids that are on the positive side of the plane /// This means that are on the side that the normal faces.</param> /// <param name="negativeSideSolids">The solids on the negative side of the plane.</param> public static void OnFlat(TessellatedSolid ts, Flat plane, out List<TessellatedSolid> positiveSideSolids, out List<TessellatedSolid> negativeSideSolids) { var contactData = DefineContact(plane, ts); DivideUpContact(ts, contactData, plane); var loops = contactData.AllLoops.Where(loop => loop.All( ce => !(ce.ContactEdge.Curvature == CurvatureType.Convex && ce is CoincidentEdgeContactElement))).ToList(); var allNegativeStartingFaces = loops.SelectMany(loop => loop.Select(ce => ce.SplitFaceNegative)).ToList(); var allPositiveStartingFaces = loops.SelectMany(loop => loop.Select(ce => ce.SplitFacePositive)).ToList(); var negativeSideFaceList = FindAllSolidsWithTheseFaces(allNegativeStartingFaces, allPositiveStartingFaces); var positiveSideFaceList = FindAllSolidsWithTheseFaces(allPositiveStartingFaces, allNegativeStartingFaces); negativeSideSolids = convertFaceListsToSolids(ts, negativeSideFaceList, loops, false, plane); positiveSideSolids = convertFaceListsToSolids(ts, positiveSideFaceList, loops, true, plane); }
private static BoundingBox Find_via_MC_ApproachOne(TessellatedSolid ts) { BoundingBox minBox = new BoundingBox(); var minVolume = double.PositiveInfinity; foreach (var convexHullEdge in ts.ConvexHullEdges) { var rotAxis = convexHullEdge.Vector.normalize(); var n = convexHullEdge.OwnedFace.Normal; var numSamples = (int)Math.Ceiling((Math.PI - convexHullEdge.InternalAngle) / MaxDeltaAngle); var deltaAngle = (Math.PI - convexHullEdge.InternalAngle) / numSamples; var edgeBBs = new BoundingBox[numSamples]; for (var i = 0; i < numSamples; i++) { double[] direction; if (i == 0) direction = n; else { var angleChange = i * deltaAngle; var invCrossMatrix = new[,] { {n[0]*n[0], n[0]*n[1], n[0]*n[2]}, {n[1]*n[0], n[1]*n[1], n[1]*n[2]}, {n[2]*n[0], n[2]*n[1], n[2]*n[2]} }; direction = invCrossMatrix.multiply(rotAxis.multiply(Math.Sin(angleChange))); } edgeBBs[i] = FindOBBAlongDirection(ts.ConvexHullVertices, direction); if (edgeBBs[i].Volume < minVolume) { minBox = edgeBBs[i]; minVolume = minBox.Volume; } } } return minBox; }
private static BoundingBox Find_via_PCA_Approach(TessellatedSolid ts) { throw new NotImplementedException(); }
/// <summary> /// Orienteds the bounding box. /// </summary> /// <param name="ts">The ts.</param> /// <returns>BoundingBox.</returns> public static BoundingBox OrientedBoundingBox(TessellatedSolid ts) { return Find_via_MC_ApproachOne(ts); }
/// <summary> /// Copies this instance. /// </summary> /// <returns>TessellatedSolid.</returns> public TessellatedSolid Copy() { var copyOfFaces = new PolygonalFace[NumberOfFaces]; for (var i = 0; i < NumberOfFaces; i++) copyOfFaces[i] = Faces[i].Copy(); var copyOfVertices = new Vertex[NumberOfVertices]; for (var i = 0; i < NumberOfVertices; i++) copyOfVertices[i] = Vertices[i].Copy(); for (var fIndex = 0; fIndex < NumberOfFaces; fIndex++) { var thisFace = copyOfFaces[fIndex]; var oldFace = Faces[fIndex]; var vertexIndices = new List<int>(); foreach (var oldVertex in oldFace.Vertices) { var vIndex = oldVertex.IndexInList; vertexIndices.Add(vIndex); var thisVertex = copyOfVertices[vIndex]; thisFace.Vertices.Add(thisVertex); thisVertex.Faces.Add(thisFace); } } Edge[] copyOfEdges = MakeEdges(copyOfFaces, copyOfVertices); var copy = new TessellatedSolid { SurfaceArea = SurfaceArea, Center = (double[])Center.Clone(), Faces = copyOfFaces, Vertices = copyOfVertices, Edges = copyOfEdges, Name = Name, NumberOfFaces = NumberOfFaces, NumberOfVertices = NumberOfVertices, Volume = Volume, XMax = XMax, XMin = XMin, YMax = YMax, YMin = YMin, ZMax = ZMax, ZMin = ZMin }; copy.CreateConvexHull(); return copy; }
private static void TestSlice(TessellatedSolid ts) { //var a= ContactData.Divide(new Flat { DistanceToOrigin = 40 , Normal = new []{0,1.0,0} }, ts).Area; // Debug.WriteLine(a); //Console.ReadKey(); //return; var now = DateTime.Now; Debug.WriteLine("start..."); var crossAreas = new double[3][,]; List<TessellatedSolid> positiveSideSolids, negativeSideSolids; Slice.OnFlat(ts, new Flat(0, new[] { 1.0, 0, 0 }), out positiveSideSolids, out negativeSideSolids); }
//private static void TestClassification(TessellatedSolid ts) //{ // TesselationToPrimitives.Run(ts); //} private static void TestOBB(TessellatedSolid ts) { var obb = MinimumEnclosure.OrientedBoundingBox(ts); }
private static void TestXSections(TessellatedSolid ts) { var now = DateTime.Now; Debug.WriteLine("start..."); var crossAreas = new double[3][,]; var maxSlices = 100; var delta = Math.Max((ts.Bounds[1][0] - ts.Bounds[0][0]) / maxSlices, Math.Max((ts.Bounds[1][1] - ts.Bounds[0][1]) / maxSlices, (ts.Bounds[1][2] - ts.Bounds[0][2]) / maxSlices)); //Parallel.For(0, 3, i => for (int i = 0; i < 3; i++) { //var max = ts.Bounds[1][i]; //var min = ts.Bounds[0][i]; //var numSteps = (int)Math.Ceiling((max - min) / delta); var coordValues = ts.Vertices.Select(v => v.Position[i]).Distinct().OrderBy(x => x).ToList(); var numSteps = coordValues.Count; var direction = new double[3]; direction[i] = 1.0; crossAreas[i] = new double[numSteps, 2]; for (var j = 0; j < numSteps; j++) { var dist = crossAreas[i][j, 0] = coordValues[j]; Console.WriteLine("slice at Coord " + i + " at " + coordValues[j]); crossAreas[i][j, 1] = Slice.DefineContact(new Flat(dist, direction), ts).Area; } }//); Debug.WriteLine("end...Time Elapsed = " + (DateTime.Now - now)); Console.ReadKey(); for (var i = 0; i < 3; i++) { Debug.WriteLine("\nfor direction " + i); for (var j = 0; j < crossAreas[i].GetLength(0); j++) { Debug.WriteLine(crossAreas[i][j, 0] + ", " + crossAreas[i][j, 1]); } } }