public static bool IsOutside(AABB left, AABB right) { return (left.MaxX - right.MinX) < 0 || (left.MinX - right.MaxX) > 0 || (left.MaxY - right.MinY) < 0 || (left.MinY - right.MaxY) > 0 || (left.MaxZ - right.MinZ) < 0 || (left.MinZ - right.MaxZ) > 0 ; }
public CSGMesh(Plane[] planes, List<Polygon> polygons, List<HalfEdge> edges, List<Vector3> vertices, AABB bounds) { this.Planes = planes; this.Polygons = polygons; this.Edges = edges; this.Vertices = vertices; this.Bounds.Set(bounds); }
public static CSGMesh Combine(Vector3 offset, IDictionary<CSGNode, CSGMesh> brushMeshes) { var planeLookup = new Dictionary<Plane, short>(); var vertexLookup = new Dictionary<Vector3, short>(); var planes = new List<Plane>(); var polygons = new List<Polygon>(); var edges = new List<HalfEdge>(); var vertices = new List<Vector3>(); var bounds = new AABB(); bounds.Clear(); int edgeIndex = 0; int polygonIndex = 0; foreach (var item in brushMeshes) { var node = item.Key; var translation = Vector3.Subtract(node.Translation, offset); var mesh = item.Value; foreach (var edge in mesh.Edges) { short vertexIndex; var vertex = Vector3.Add(mesh.Vertices[edge.VertexIndex], translation); if (!vertexLookup.TryGetValue(vertex, out vertexIndex)) { vertexIndex = (short)vertices.Count; vertices.Add(vertex); vertexLookup.Add(vertex, vertexIndex); } var newEdge = new HalfEdge(); newEdge.VertexIndex = vertexIndex; newEdge.NextIndex = (short)(edge.NextIndex + edgeIndex); newEdge.TwinIndex = (short)(edge.TwinIndex + edgeIndex); newEdge.PolygonIndex = (short)(edge.PolygonIndex + polygonIndex); edges.Add(newEdge); } foreach (var polygon in mesh.Polygons) { if (polygon.FirstIndex == -1) continue; short planeIndex; var plane = mesh.Planes[polygon.PlaneIndex]; if (!planeLookup.TryGetValue(plane, out planeIndex)) { planeIndex = (short)planes.Count; planes.Add(plane); planeLookup.Add(plane, planeIndex); } var newPolygon = new Polygon(); newPolygon.PlaneIndex = planeIndex; newPolygon.FirstIndex = (short)(polygon.FirstIndex + edgeIndex); newPolygon.Category = polygon.Category; newPolygon.Visible = polygon.Visible; newPolygon.Bounds.Set(polygon.Bounds, translation); polygons.Add(newPolygon); if (newPolygon.Visible) { var first = edges[newPolygon.FirstIndex]; var iterator = first; do { bounds.Add(vertices[iterator.VertexIndex]); iterator = edges[iterator.NextIndex]; } while (iterator != first); } } edgeIndex = edges.Count; polygonIndex = polygons.Count; } return new CSGMesh(planes.ToArray(), polygons, edges, vertices, bounds); }
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; } } }
public CSGMesh Clone() { var newPlanes = new Plane[Planes.Length]; for (int i = 0; i < Planes.Length; i++) { var plane = Planes[i]; newPlanes[i] = new Plane(plane.A, plane.B, plane.C, plane.D); } var newPolygons = new List<Polygon>(Polygons.Count); foreach (var polygon in Polygons) { var newPolygon = new Polygon(); newPolygon.FirstIndex = polygon.FirstIndex; newPolygon.Visible = polygon.Visible; newPolygon.Category = polygon.Category; newPolygon.PlaneIndex = polygon.PlaneIndex; newPolygon.Bounds.Set(polygon.Bounds); newPolygons.Add(newPolygon); } var newEdges = new List<HalfEdge>(Edges.Count); foreach (var edge in Edges) { var newEdge = new HalfEdge(); newEdge.NextIndex = edge.NextIndex; newEdge.PolygonIndex = edge.PolygonIndex; newEdge.TwinIndex = edge.TwinIndex; newEdge.VertexIndex = edge.VertexIndex; newEdges.Add(newEdge); } var newVertices = new List<Vector3>(Vertices.Count); foreach (var vertex in Vertices) { var newVertex = new Vector3(vertex.X, vertex.Y, vertex.Z); newVertices.Add(newVertex); } var newBounds = new AABB(Bounds); var newMesh = new CSGMesh( newPlanes, newPolygons, newEdges, newVertices, newBounds); return newMesh; }
public static CSGMesh CreateFromPlanes(Plane[] brushPlanes) { var planes = new Plane[brushPlanes.Length]; for (int i = 0; i < brushPlanes.Length; i++) { var plane = brushPlanes[i]; planes[i] = new Plane(plane.A, plane.B, plane.C, plane.D); } var pointIntersections = new List<PointIntersection>(planes.Length * planes.Length); var intersectingPlanes = new List<short>(); var vertices = new List<Vector3>(); var edges = new List<HalfEdge>(); // Find all point intersections where 3 (or more planes) intersect for (short planeIndex1 = 0; planeIndex1 < planes.Length - 2; planeIndex1++) { var plane1 = planes[planeIndex1]; for (short planeIndex2 = (short)(planeIndex1 + 1); planeIndex2 < planes.Length - 1; planeIndex2++) { var plane2 = planes[planeIndex2]; for (short planeIndex3 = (short)(planeIndex2 + 1); planeIndex3 < planes.Length; planeIndex3++) { var plane3 = planes[planeIndex3]; // Calculate the intersection var vertex = Plane.Intersection(plane1, plane2, plane3); // Check if the intersection is valid if (float.IsNaN(vertex.X) || float.IsNaN(vertex.Y) || float.IsNaN(vertex.Z) || float.IsInfinity(vertex.X) || float.IsInfinity(vertex.Y) || float.IsInfinity(vertex.Z)) continue; intersectingPlanes.Clear(); intersectingPlanes.Add(planeIndex1); intersectingPlanes.Add(planeIndex2); intersectingPlanes.Add(planeIndex3); for (short planeIndex4 = 0; planeIndex4 < planes.Length; planeIndex4++) { if (planeIndex4 == planeIndex1 || planeIndex4 == planeIndex2 || planeIndex4 == planeIndex3) continue; var plane4 = planes[planeIndex4]; var side = plane4.OnSide(vertex); if (side == PlaneSideResult.Intersects) { if (planeIndex4 < planeIndex3) // Already found this vertex goto SkipIntersection; // We've found another plane which goes trough our found intersection point intersectingPlanes.Add(planeIndex4); } else if (side == PlaneSideResult.Outside) // Intersection is outside of brush goto SkipIntersection; } var vertexIndex = (short)vertices.Count; vertices.Add(vertex); // Add intersection point to our list pointIntersections.Add(new PointIntersection(vertexIndex, intersectingPlanes)); SkipIntersection: ; } } } var foundPlanes = new short[2]; // Find all our intersection edges which are formed by a pair of planes // (this could probably be done inside the previous loop) for (int i = 0; i < pointIntersections.Count; i++) { var pointIntersectionA = pointIntersections[i]; for (int j = i + 1; j < pointIntersections.Count; j++) { var pointIntersectionB = pointIntersections[j]; var planesIndicesA = pointIntersectionA.PlaneIndices; var planesIndicesB = pointIntersectionB.PlaneIndices; short foundPlaneIndex = 0; foreach (var currentPlaneIndex in planesIndicesA) { if (!planesIndicesB.Contains(currentPlaneIndex)) continue; foundPlanes[foundPlaneIndex] = currentPlaneIndex; foundPlaneIndex++; if (foundPlaneIndex == 2) break; } // If foundPlaneIndex is 0 or 1 then either this combination does not exist, // or only goes trough one point if (foundPlaneIndex < 2) continue; // Create our found intersection edge var halfEdgeA = new HalfEdge(); var halfEdgeAIndex = (short)edges.Count; edges.Add(halfEdgeA); var halfEdgeB = new HalfEdge(); var halfEdgeBIndex = (short)edges.Count; edges.Add(halfEdgeB); halfEdgeA.TwinIndex = halfEdgeBIndex; halfEdgeB.TwinIndex = halfEdgeAIndex; halfEdgeA.VertexIndex = pointIntersectionA.VertexIndex; halfEdgeB.VertexIndex = pointIntersectionB.VertexIndex; // Add it to our points pointIntersectionA.Edges.Add(new EdgeIntersection( halfEdgeA, foundPlanes[0], foundPlanes[1])); pointIntersectionB.Edges.Add(new EdgeIntersection( halfEdgeB, foundPlanes[0], foundPlanes[1])); } } var polygons = new List<Polygon>(); for (short i = 0; i < (short)planes.Length; i++) { var polygon = new Polygon(); polygon.PlaneIndex = i; polygons.Add(polygon); } var bounds = new AABB(); var direction = new Vector3(); for (int i = pointIntersections.Count - 1; i >= 0; i--) { var pointIntersection = pointIntersections[i]; var pointEdges = pointIntersection.Edges; // Make sure that we have at least 2 edges ... // This may happen when a plane only intersects at a single edge. if (pointEdges.Count <= 2) { pointIntersections.RemoveAt(i); continue; } var vertexIndex = pointIntersection.VertexIndex; var vertex = vertices[vertexIndex]; for (int j = 0; j < pointEdges.Count - 1; j++) { var edge1 = pointEdges[j]; for (int k = j + 1; k < pointEdges.Count; k++) { var edge2 = pointEdges[k]; int planeIndex1 = -1; int planeIndex2 = -1; // Determine if and which of our 2 planes are identical if (edge1.PlaneIndices[0] == edge2.PlaneIndices[0]) { planeIndex1 = 0; planeIndex2 = 0; } else if (edge1.PlaneIndices[0] == edge2.PlaneIndices[1]) { planeIndex1 = 0; planeIndex2 = 1; } else if (edge1.PlaneIndices[1] == edge2.PlaneIndices[0]) { planeIndex1 = 1; planeIndex2 = 0; } else if (edge1.PlaneIndices[1] == edge2.PlaneIndices[1]) { planeIndex1 = 1; planeIndex2 = 1; } else continue; HalfEdge ingoing; HalfEdge outgoing; short outgoingIndex; var shared_plane = planes[edge1.PlaneIndices[planeIndex1]]; var edge1_plane = planes[edge1.PlaneIndices[1 - planeIndex1]]; var edge2_plane = planes[edge2.PlaneIndices[1 - planeIndex2]]; direction = Vector3.CrossProduct(shared_plane.Normal, edge1_plane.Normal); // Determine the orientation of our two edges to determine // which edge is in-going, and which one is out-going if (Vector3.DotProduct(direction, edge2_plane.Normal) < 0) { ingoing = edge2.Edge; outgoingIndex = edge1.Edge.TwinIndex; outgoing = edges[outgoingIndex]; } else { ingoing = edge1.Edge; outgoingIndex = edge2.Edge.TwinIndex; outgoing = edges[outgoingIndex]; } // Link the out-going half-edge to the in-going half-edge ingoing.NextIndex = outgoingIndex; // Add reference to polygon to half-edge, and make sure our // polygon has a reference to a half-edge // Since a half-edge, in this case, serves as a circular // linked list this just works. var polygonIndex = edge1.PlaneIndices[planeIndex1]; ingoing.PolygonIndex = polygonIndex; outgoing.PolygonIndex = polygonIndex; var polygon = polygons[polygonIndex]; polygon.FirstIndex = outgoingIndex; polygon.Bounds.Add(vertex); } } // Add the intersection point to the area of our bounding box bounds.Add(vertex); } return new CSGMesh(planes, polygons, edges, vertices, bounds); }
public PlaneSideResult OnSide(AABB bounds, Vector3 translation) { var backward_x = A <= 0 ? bounds.MinX : bounds.MaxX; var backward_y = B <= 0 ? bounds.MinY : bounds.MaxY; var backward_z = C <= 0 ? bounds.MinZ : bounds.MaxZ; var distance = Distance(backward_x + translation.X, backward_y + translation.Y, backward_z + translation.Z); var side = OnSide(distance); if (side == PlaneSideResult.Inside) return PlaneSideResult.Inside; var forward_x = A >= 0 ? bounds.MinX : bounds.MaxX; var forward_y = B >= 0 ? bounds.MinY : bounds.MaxY; var forward_z = C >= 0 ? bounds.MinZ : bounds.MaxZ; distance = Distance(forward_x + translation.X, forward_y + translation.Y, forward_z + translation.Z); side = OnSide(distance); if (side == PlaneSideResult.Outside) return PlaneSideResult.Outside; return PlaneSideResult.Intersects; }
public PlaneSideResult OnSide(AABB bounds) { var x = A >= 0 ? bounds.MinX : bounds.MaxX; var y = B >= 0 ? bounds.MinY : bounds.MaxY; var z = C >= 0 ? bounds.MinZ : bounds.MaxZ; return OnSide(Distance(x, y, z)); }
public static bool IsOutside(AABB left, Vector3 translation, AABB right) { return ((left.MaxX + translation.X) - right.MinX) < 0 || ((left.MinX + translation.X) - right.MaxX) > 0 || ((left.MaxY + translation.Y) - right.MinY) < 0 || ((left.MinY + translation.Y) - right.MaxY) > 0 || ((left.MaxZ + translation.Z) - right.MinZ) < 0 || ((left.MinZ + translation.Z) - right.MaxZ) > 0 ; }
public void Set(AABB other, Vector3 translation) { this.MinX = (int)Math.Floor(other.MinX + translation.X); this.MinY = (int)Math.Floor(other.MinY + translation.Y); this.MinZ = (int)Math.Floor(other.MinZ + translation.Z); this.MaxX = (int)Math.Ceiling(other.MaxX + translation.X); this.MaxY = (int)Math.Ceiling(other.MaxY + translation.Y); this.MaxZ = (int)Math.Ceiling(other.MaxZ + translation.Z); }
public AABB(AABB other) { Clear(); Set(other); }
public void Set(AABB bounds) { this.MinX = bounds.MinX; this.MinY = bounds.MinY; this.MinZ = bounds.MinZ; this.MaxX = bounds.MaxX; this.MaxY = bounds.MaxY; this.MaxZ = bounds.MaxZ; }
public bool IsOutside(AABB other) { return (this.MaxX - other.MinX) < 0 || (this.MinX - other.MaxX) > 0 || (this.MaxY - other.MinY) < 0 || (this.MinY - other.MaxY) > 0 || (this.MaxZ - other.MinZ) < 0 || (this.MinZ - other.MaxZ) > 0 ; }
public void Add(AABB bounds) { MinX = Math.Min(MinX, bounds.MinX); MinY = Math.Min(MinY, bounds.MinY); MinZ = Math.Min(MinZ, bounds.MinZ); MaxX = Math.Max(MaxX, bounds.MaxX); MaxY = Math.Max(MaxY, bounds.MaxY); MaxZ = Math.Max(MaxZ, bounds.MaxZ); }
public bool ValidateDrop(bool inSceneView) { if (!inSceneView) { return(false); } Reset(); if (DragAndDrop.objectReferences == null || DragAndDrop.objectReferences.Length == 0) { dragGameObjects = null; return(false); } dragGameObjects = new List <GameObject>(); containsModel = false; foreach (var obj in DragAndDrop.objectReferences) { var gameObject = obj as GameObject; if (gameObject == null) { continue; } if (gameObject.GetComponentInChildren <CSGBrush>() == null) { continue; } if (PrefabUtility.GetPrefabType(gameObject) == PrefabType.None) { continue; } if (PrefabUtility.GetPrefabParent(gameObject) == null && PrefabUtility.GetPrefabObject(gameObject) != null) { dragGameObjects.Add(gameObject); } containsModel = containsModel || (gameObject.GetComponent <CSGModel>() != null); } if (dragGameObjects.Count != 1) { dragGameObjects = null; return(false); } var dragGameObjectBounds = new AABB(); dragGameObjectBounds.Reset(); foreach (var gameObject in dragGameObjects) { var brushes = gameObject.GetComponentsInChildren <CSGBrush>(); if (brushes.Length == 0) { continue; } dragGameObjectBounds.Add(BoundsUtilities.GetLocalBounds(brushes, gameObject.transform.worldToLocalMatrix)); } if (!dragGameObjectBounds.Valid) { dragGameObjectBounds.Extend(MathConstants.zeroVector3); } projectedBounds = new Vector3[8]; BoundsUtilities.GetBoundsCornerPoints(dragGameObjectBounds, projectedBounds); haveNoParent = false; return(true); }
public void SetBounds(AABB newBounds, bool autoAdjust = true) { var boundsSize = newBounds.Size; stairsWidth = GeometryUtility.CleanLength(boundsSize.x); stairsHeight = GeometryUtility.CleanLength(boundsSize.y); stairsDepth = GeometryUtility.CleanLength(boundsSize.z); if (float.IsInfinity(stairsWidth) || float.IsNaN(stairsWidth)) { stairsWidth = 1.0f; } if (float.IsInfinity(stairsDepth) || float.IsNaN(stairsDepth)) { stairsDepth = stepDepth; } if (float.IsInfinity(stairsHeight) || float.IsNaN(stairsHeight)) { stairsHeight = stepHeight; } stairsWidth = Mathf.Max(0, stairsWidth); stairsDepth = Mathf.Max(0, stairsDepth); stairsHeight = Mathf.Max(0, stairsHeight); if (autoAdjust) { if (!bounds.IsEmpty()) { float offsetY = (newBounds.MaxY - bounds.MaxY) + (bounds.MinY - newBounds.MinY); float offsetZ = (newBounds.MaxZ - bounds.MaxZ) + (bounds.MinZ - newBounds.MinZ); if (offsetY != 0) // scaling in height direction { if (offsetY > 0) // growing { if (extraDepth > 0) { extraDepth = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth); } else { extraDepth -= offsetY; extraHeight += offsetY; } } else // shrinking { extraHeight += offsetY; extraDepth = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth); } } if (offsetZ != 0) // scaling in depth direction { if (offsetZ > 0) // growing { if (extraHeight > 0) { extraHeight = stairsHeight - ((Mathf.Max(0, stairsDepth - extraDepth) / stepDepth) * stepHeight); } else { extraDepth += offsetZ; if (extraDepth < 0) { extraDepth = 0; } extraHeight -= offsetZ; } } else // shrinking { if (extraDepth > 0) { extraDepth = Mathf.Max(0, extraDepth + offsetZ); } extraHeight = stairsHeight - ((Mathf.Max(0, stairsDepth - extraDepth) / stepDepth) * stepHeight); } extraDepth = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth); } if (extraDepth < 0) { extraDepth = 0; } if (extraHeight < 0) { extraHeight = 0; } } else { extraDepth = Mathf.Max(0, stairsDepth - (totalSteps * stepDepth)); extraHeight = 0; } CalcTotalSteps(); if ((totalSteps * stepDepth) > stairsDepth) { var newTotalSteps = Mathf.Max(1, Mathf.FloorToInt((stairsDepth - extraDepth) / stepDepth)); extraHeight += (totalSteps - newTotalSteps) * stepHeight; totalSteps = newTotalSteps; } } bounds = newBounds; }