/// <summary> /// Fills solid arrays with data about faces of an object generated whose status is as required /// </summary> /// <param name="obj"></param> /// <param name="vertices"></param> /// <param name="indices"></param> /// <param name="faceStatus1"></param> /// <param name="faceStatus2"></param> private void GroupObjectComponents(CsgObject3D obj, List <Vertex> vertices, List <int> indices, FaceStatus faceStatus1, FaceStatus faceStatus2) { var vertexIndexByHashCode = new Dictionary <Vector3, int>(); //for each face.. foreach (CsgFace face in obj.Faces.All()) { //if the face status fits with the desired status... if (face.Status == faceStatus1 || face.Status == faceStatus2) { //adds the face elements into the arrays Vertex[] faceVerts = { face.v1, face.v2, face.v3 }; for (int j = 0; j < faceVerts.Length; j++) { if (vertexIndexByHashCode.ContainsKey(faceVerts[j].Position)) { indices.Add(vertexIndexByHashCode[faceVerts[j].Position]); } else { vertexIndexByHashCode.Add(faceVerts[j].Position, vertices.Count); indices.Add(vertices.Count); vertices.Add(faceVerts[j]); } } } } }
/// <summary> /// Classify faces as being inside, outside or on boundary of other object /// </summary> /// <param name="otherObject">object 3d used for the comparison</param> public void ClassifyFaces(CsgObject3D otherObject, CancellationToken cancellationToken, Action <CsgFace> classifyFaces = null) { var otherBounds = otherObject.Bounds; foreach (var vertex in vertices) { cancellationToken.ThrowIfCancellationRequested(); if (!otherBounds.Contains(vertex.Position) && vertex.Status != FaceStatus.Outside) { vertex.Status = FaceStatus.Outside; } } //calculate adjacency information Faces.All(); foreach (var face in Faces.QueryResults) { cancellationToken.ThrowIfCancellationRequested(); face.v1.AddAdjacentVertex(face.v2); face.v1.AddAdjacentVertex(face.v3); face.v2.AddAdjacentVertex(face.v1); face.v2.AddAdjacentVertex(face.v3); face.v3.AddAdjacentVertex(face.v1); face.v3.AddAdjacentVertex(face.v2); } // for each face foreach (var face in Faces.QueryResults) { cancellationToken.ThrowIfCancellationRequested(); // If the face vertices can't be classified with the simple classify if (face.SimpleClassify() == false) { //makes the ray trace classification face.RayTraceClassify(otherObject); //mark the vertices if (face.v1.Status == FaceStatus.Unknown) { face.v1.Mark(face.Status); } if (face.v2.Status == FaceStatus.Unknown) { face.v2.Mark(face.Status); } if (face.v3.Status == FaceStatus.Unknown) { face.v3.Mark(face.Status); } } classifyFaces?.Invoke(face); } }
public BooleanModeller(Solid solid1, Solid solid2, Action <string, double> reporter, CancellationToken cancellationToken) { this.reporter = reporter; this.cancellationToken = cancellationToken; //representation to apply boolean operations reporter?.Invoke("Create Csg Object 1", 0.1); object1 = new CsgObject3D(solid1); reporter?.Invoke("Create Csg Object 2", 0.2); object2 = new CsgObject3D(solid2); object1Copy = new CsgObject3D(solid1); }
private void PrepareData() { if (object1Copy != null) { //split the faces so that none of them intercepts each other reporter?.Invoke("Split Faces 1", 0.4); object1.SplitFaces(object2, cancellationToken, Object1SplitFace, Object1SplitResults); reporter?.Invoke("Split Faces 2", 0.6); object2.SplitFaces(object1Copy, cancellationToken, Object2SplitFace, Object2SplitResults); // free the memory object1Copy = null; //classify faces as being inside or outside the other solid reporter?.Invoke("Classify Faces 1", 0.8); object1.ClassifyFaces(object2, Object1ClassifyFace); reporter?.Invoke("Classify Faces 2", 0.9); object2.ClassifyFaces(object1, Object2ClassifyFace); } }
/// <summary> /// Split faces so that no face is intercepted by a face of other object /// </summary> /// <param name="compareObject">the other object 3d used to make the split</param> public void SplitFaces(CsgObject3D compareObject, CancellationToken cancellationToken, Action <CsgFace, CsgFace> splitFaces = null, Action <List <CsgFace> > results = null) { Stack <CsgFace> newFacesFromSplitting = new Stack <CsgFace>(); int numFacesStart = this.Faces.Count; //if the objects bounds overlap... //for each object1 face... var bounds = compareObject.GetBound(); Faces.SearchBounds(bounds); foreach (var thisFaceIn in Faces.QueryResults) // put it in an array as we will be adding new faces to it { newFacesFromSplitting.Push(thisFaceIn); // make sure we process every face that we have added during splitting before moving on to the next face while (newFacesFromSplitting.Count > 0) { var faceToSplit = newFacesFromSplitting.Pop(); // stop processing if operation has been canceled cancellationToken.ThrowIfCancellationRequested(); //if object1 face bound and object2 bound overlap ... //for each object2 face... compareObject.Faces.SearchBounds(faceToSplit.GetBound()); foreach (var cuttingFace in compareObject.Faces.QueryResults) { //if object1 face bound and object2 face bound overlap... //PART I - DO TWO POLIGONS INTERSECT? //POSSIBLE RESULTS: INTERSECT, NOT_INTERSECT, COPLANAR //distance from the face1 vertices to the face2 plane double v1DistToCuttingFace = cuttingFace.DistanceFromPlane(faceToSplit.v1); double v2DistToCuttingFace = cuttingFace.DistanceFromPlane(faceToSplit.v2); double v3DistToCuttingFace = cuttingFace.DistanceFromPlane(faceToSplit.v3); //distances signs from the face1 vertices to the face2 plane PlaneSide sideFace1Vert1 = (v1DistToCuttingFace > EqualityTolerance ? PlaneSide.Front : (v1DistToCuttingFace < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); PlaneSide sideFace1Vert2 = (v2DistToCuttingFace > EqualityTolerance ? PlaneSide.Front : (v2DistToCuttingFace < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); PlaneSide sideFace1Vert3 = (v3DistToCuttingFace > EqualityTolerance ? PlaneSide.Front : (v3DistToCuttingFace < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); //if all the signs are zero, the planes are coplanar //if all the signs are positive or negative, the planes do not intersect //if the signs are not equal... if (!(sideFace1Vert1 == sideFace1Vert2 && sideFace1Vert2 == sideFace1Vert3)) { //distance from the face2 vertices to the face1 plane double faceToSplitTo1 = faceToSplit.DistanceFromPlane(cuttingFace.v1); double faceToSplitTo2 = faceToSplit.DistanceFromPlane(cuttingFace.v2); double faceToSplitTo3 = faceToSplit.DistanceFromPlane(cuttingFace.v3); //distances signs from the face2 vertices to the face1 plane PlaneSide signFace2Vert1 = (faceToSplitTo1 > EqualityTolerance ? PlaneSide.Front : (faceToSplitTo1 < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); PlaneSide signFace2Vert2 = (faceToSplitTo2 > EqualityTolerance ? PlaneSide.Front : (faceToSplitTo2 < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); PlaneSide signFace2Vert3 = (faceToSplitTo3 > EqualityTolerance ? PlaneSide.Front : (faceToSplitTo3 < -EqualityTolerance ? PlaneSide.Back : PlaneSide.On)); //if the signs are not equal... if (!(signFace2Vert1 == signFace2Vert2 && signFace2Vert2 == signFace2Vert3)) { var line = new Line(faceToSplit, cuttingFace); //intersection of the face1 and the plane of face2 var segment1 = new Segment(line, faceToSplit, sideFace1Vert1, sideFace1Vert2, sideFace1Vert3); //intersection of the face2 and the plane of face1 var segment2 = new Segment(line, cuttingFace, signFace2Vert1, signFace2Vert2, signFace2Vert3); //if the two segments intersect... if (segment1.Intersect(segment2)) { //PART II - SUBDIVIDING NON-COPLANAR POLYGONS Stack <CsgFace> facesFromSplit = new Stack <CsgFace>(); if (this.SplitFace(faceToSplit, segment1, segment2, facesFromSplit) && facesFromSplit.Count > 0 && !(facesFromSplit.Count == 1 && facesFromSplit.Peek().Equals(faceToSplit))) { foreach (var face in facesFromSplit) { newFacesFromSplitting.Push(face); } // send debugging information if registered splitFaces?.Invoke(faceToSplit, cuttingFace); results?.Invoke(facesFromSplit.ToList()); break; } } } } } } } }
/// <summary> /// Classifies the face based on the ray trace technique /// </summary> /// <param name="obj">object3d used to compute the face status</param> public void RayTraceClassify(CsgObject3D obj) { var random = new Random(); //creating a ray starting at the face baricenter going to the normal direction Ray ray = new Ray(center, Normal); //Line ray = new Line(GetNormal(), center); ray.PerturbDirection(random); bool success; double distance; Vector3 intersectionPoint; CsgFace closestFace = null; double closestDistance; do { success = true; closestDistance = Double.MaxValue; //for each face from the other solid... //foreach (Face face in obj.Faces.AllObjects()) obj.Faces.AlongRay(ray); foreach (var face in obj.Faces.QueryResults) { double hitDistance; bool front; //if ray intersects the plane... if (face.Plane.RayHitPlane(ray, out hitDistance, out front)) { double dotProduct = Vector3.Dot(face.Normal, ray.directionNormal); distance = hitDistance; intersectionPoint = ray.origin + ray.directionNormal * hitDistance; ray.maxDistanceToConsider = hitDistance; //if ray lies in plane... if (Math.Abs(distance) < EqualityTolerance && Math.Abs(dotProduct) < EqualityTolerance) { //disturb the ray in order to not lie into another plane ray.PerturbDirection(random); success = false; break; } //if ray starts in plane... if (Math.Abs(distance) < EqualityTolerance && Math.Abs(dotProduct) > EqualityTolerance) { //if ray intersects the face... if (face.ContainsPoint(intersectionPoint)) { //faces coincide closestFace = face; closestDistance = 0; break; } } //if ray intersects plane... else if (Math.Abs(dotProduct) > EqualityTolerance && distance > EqualityTolerance) { if (distance < closestDistance) { //if ray intersects the face; if (face.ContainsPoint(intersectionPoint)) { //this face is the closest face until now closestDistance = distance; closestFace = face; } } } } } } while (success == false); if (closestFace == null) { //none face found: outside face Status = FaceStatus.Outside; } else //face found: test dot product { double dotProduct = Vector3.Dot(closestFace.Normal, ray.directionNormal); //distance = 0: coplanar faces if (Math.Abs(closestDistance) < EqualityTolerance) { if (dotProduct > EqualityTolerance) { Status = FaceStatus.Same; } else if (dotProduct < -EqualityTolerance) { Status = FaceStatus.Opposite; } } else if (dotProduct > EqualityTolerance) { //dot product > 0 (same direction): inside face Status = FaceStatus.Inside; } else if (dotProduct < -EqualityTolerance) { //dot product < 0 (opposite direction): outside face Status = FaceStatus.Outside; } } }