// Intersects a mesh with a brush (set of planes) #region Intersect public void Intersect(AABB cuttingNodeBounds, Plane[] cuttingNodePlanes, Vector3 cuttingNodeTranslation, Vector3 inputPolygonTranslation, List <Polygon> inputPolygons, List <Polygon> inside, List <Polygon> aligned, List <Polygon> revAligned, List <Polygon> outside) { var categories = new PolygonSplitResult[cuttingNodePlanes.Length]; var translatedPlanes = new Plane[cuttingNodePlanes.Length]; var translation = Vector3.Subtract(cuttingNodeTranslation, inputPolygonTranslation); // translate the planes we cut our polygons with so that they're located at the same // relative distance from the polygons as the brushes are from each other. for (int i = 0; i < cuttingNodePlanes.Length; i++) { translatedPlanes[i] = Plane.Translated(cuttingNodePlanes[i], translation); } var vertices = this.Vertices; var edges = this.Edges; var planes = this.Planes; for (int i = inputPolygons.Count - 1; i >= 0; i--) { var inputPolygon = inputPolygons[i]; if (inputPolygon.FirstIndex == -1) { continue; } var bounds = inputPolygon.Bounds; var finalResult = PolygonSplitResult.CompletelyInside; // A quick check if the polygon lies outside the planes we're cutting our polygons with. if (!AABB.IsOutside(cuttingNodeBounds, translation, bounds)) { PolygonSplitResult intermediateResult; Polygon outsidePolygon = null; for (int otherIndex = 0; otherIndex < translatedPlanes.Length; otherIndex++) { var translatedCuttingPlane = translatedPlanes[otherIndex]; var side = cuttingNodePlanes[otherIndex].OnSide(bounds, translation.Negated()); if (side == PlaneSideResult.Outside) { finalResult = PolygonSplitResult.CompletelyOutside; break; // nothing left to process, so we exit } else if (side == PlaneSideResult.Inside) { continue; } var polygon = inputPolygon; intermediateResult = PolygonSplit(translatedCuttingPlane, inputPolygonTranslation, ref polygon, out outsidePolygon); inputPolygon = polygon; if (intermediateResult == PolygonSplitResult.CompletelyOutside) { finalResult = PolygonSplitResult.CompletelyOutside; break; // nothing left to process, so we exit } else if (intermediateResult == PolygonSplitResult.Split) { if (outside != null) { outside.Add(outsidePolygon); } // Note: left over is still completely inside, // or plane (opposite) aligned } else if (intermediateResult != PolygonSplitResult.CompletelyInside) { finalResult = intermediateResult; } } } else { finalResult = PolygonSplitResult.CompletelyOutside; } switch (finalResult) { case PolygonSplitResult.CompletelyInside: inside.Add(inputPolygon); break; case PolygonSplitResult.CompletelyOutside: outside.Add(inputPolygon); break; // The polygon can only be visible if it's part of the last brush that shares it's surface area, // otherwise we'd get overlapping polygons if two brushes overlap. // When the (final) polygon is aligned with one of the cutting planes, we know it lies on the surface of // the CSG node we're cutting the polygons with. We also know that this node is not the node this polygon belongs to // because we've done that check earlier on. So we flag this polygon as being invisible. case PolygonSplitResult.PlaneAligned: inputPolygon.Visible = false; aligned.Add(inputPolygon); break; case PolygonSplitResult.PlaneOppositeAligned: inputPolygon.Visible = false; revAligned.Add(inputPolygon); break; } } }
// Categorize the given inputPolygons as being inside/outside or (reverse-)aligned // with the shape that is defined by the current brush or csg-branch. // When an inputPolygon crosses the node, it is split into pieces and every individual // piece is then categorized. #region Categorize public static void Categorize(CSGNode processedNode, CSGMesh processedMesh, CSGNode categorizationNode, List <Polygon> inputPolygons, List <Polygon> inside, List <Polygon> aligned, List <Polygon> revAligned, List <Polygon> outside) { // When you go deep enough in the tree it's possible that all categories point to the same // destination. So we detect that and potentially avoid a lot of wasted work. if (inside == revAligned && inside == aligned && inside == outside) { inside.AddRange(inputPolygons); return; } Restart: if (processedNode == categorizationNode) { // When the currently processed node is the same node as we categorize against, then // we know that all our polygons are visible and we set their default category // (usually aligned, unless it's an instancing node in which case it's precalculated) foreach (var polygon in inputPolygons) { switch (polygon.Category) { case PolygonCategory.Aligned: aligned.Add(polygon); break; case PolygonCategory.ReverseAligned: revAligned.Add(polygon); break; case PolygonCategory.Inside: inside.Add(polygon); break; case PolygonCategory.Outside: outside.Add(polygon); break; } // When brushes overlap and they share the same surface area we only want to keep // the polygons of the last brush in the tree, and skip all others. // At this point in the tree we know that this polygon belongs to this brush, so // we set it to visible. If the polygon is found to share the surface area with another // brush further on in the tree it'll be set to invisible again in mesh.Intersect. polygon.Visible = true; } return; } var leftNode = categorizationNode.Left; var rightNode = categorizationNode.Right; switch (categorizationNode.NodeType) { case CSGNodeType.Brush: { processedMesh.Intersect(categorizationNode.Bounds, categorizationNode.Generator.GetPlanes(categorizationNode.WorldTransformation), categorizationNode.Translation, processedNode.Translation, inputPolygons, inside, aligned, revAligned, outside); break; } case CSGNodeType.Addition: { // ( A || B) var relativeLeftTrans = Vector3.Subtract(processedNode.Translation, leftNode.Translation); var relativeRightTrans = Vector3.Subtract(processedNode.Translation, rightNode.Translation); if (AABB.IsOutside(processedNode.Bounds, relativeLeftTrans, leftNode.Bounds)) { if (AABB.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { // When our polygons lie outside the bounds of both the left and the right node, then // all the polygons can be categorized as being 'outside' outside.AddRange(inputPolygons); } else { //Categorize(processedNode, mesh, right, // inputPolygons, // inside, aligned, revAligned, outside); categorizationNode = rightNode; goto Restart; } } else if (AABB.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { //Categorize(processedNode, left, mesh, // inputPolygons, // inside, aligned, revAligned, outside); categorizationNode = leftNode; goto Restart; } else { LogicalOr(processedNode, processedMesh, categorizationNode, inputPolygons, inside, aligned, revAligned, outside, false, false); } break; } case CSGNodeType.Common: { // !(!A || !B) var relativeLeftTrans = Vector3.Subtract(processedNode.Translation, leftNode.Translation); var relativeRightTrans = Vector3.Subtract(processedNode.Translation, rightNode.Translation); if (AABB.IsOutside(processedNode.Bounds, relativeLeftTrans, leftNode.Bounds) || AABB.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { // When our polygons lie outside the bounds of both the left and the right node, then // all the polygons can be categorized as being 'outside' outside.AddRange(inputPolygons); } else { LogicalOr(processedNode, processedMesh, categorizationNode, inputPolygons, outside, revAligned, aligned, inside, true, true); } break; } case CSGNodeType.Subtraction: { // !(!A || B) var relativeLeftTrans = Vector3.Subtract(processedNode.Translation, leftNode.Translation); var relativeRightTrans = Vector3.Subtract(processedNode.Translation, rightNode.Translation); if (AABB.IsOutside(processedNode.Bounds, relativeLeftTrans, leftNode.Bounds)) { // When our polygons lie outside the bounds of both the left node, then // all the polygons can be categorized as being 'outside' outside.AddRange(inputPolygons); } else if (AABB.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { categorizationNode = leftNode; goto Restart; } else { LogicalOr(processedNode, processedMesh, categorizationNode, inputPolygons, outside, revAligned, aligned, inside, true, false); } break; } } }