public VoxelizingOctreeCell(VoxelizingOctree tree, VoxelizingOctreeCell root, Vector3 center, float length, int level) { Tree = tree; Root = root; Center = center; Length = length; Level = level; float half_length = length / 2.0f; Bounds = new AABBf(); Bounds.MinX = center.X - half_length; Bounds.MinY = center.Y - half_length; Bounds.MinZ = center.Z - half_length; Bounds.MaxX = center.X + half_length; Bounds.MaxY = center.Y + half_length; Bounds.MaxZ = center.Z + half_length; VoxelBounds = new AABBi( (int)Math.Round((Bounds.MinX - tree.VoxelBounds.MinX) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero), (int)Math.Round((Bounds.MinY - tree.VoxelBounds.MinY) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero), (int)Math.Round((Bounds.MinZ - tree.VoxelBounds.MinZ) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero), (int)Math.Round((Bounds.MaxX - tree.VoxelBounds.MinX) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero), (int)Math.Round((Bounds.MaxY - tree.VoxelBounds.MinY) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero), (int)Math.Round((Bounds.MaxZ - tree.VoxelBounds.MinZ) / tree.SmallestVoxelSideLength, MidpointRounding.AwayFromZero)); }
public static Mesh BuildMesh(List <AABBi> boxList) { Mesh mesh = new Mesh(); if (boxList.Count == 0) { mesh.Vertices = new Vector4[0]; mesh.Indicies = new int[0]; return(mesh); } List <CSGNode> nodes = new List <CSGNode>(); for (int i = 0; i < boxList.Count; i++) { AABBi box = boxList[i]; CSGNode node = new CSGNode(new Plane[] { new Plane(0, -1, 0, 0), new Plane(-1, 0, 0, 0), new Plane(0, 0, -1, 0), new Plane(0, 0, 1, box.MaxZ - box.MinZ), new Plane(1, 0, 0, box.MaxX - box.MinX), new Plane(0, 1, 0, box.MaxY - box.MinY) }); node.Translation = new Vector3(box.MinX, box.MinY, box.MinZ); nodes.Add(node); } return(BuildMesh(nodes)); }
public CSGMesh(Plane[] planes, List<Polygon> polygons, List<HalfEdge> edges, List<Vector3> vertices, AABBi bounds) { this.Planes = planes; this.Polygons = polygons; this.Edges = edges; this.Vertices = vertices; this.Bounds.Set(bounds); }
public PlaneSideResult OnSide(AABBi 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(SignedDistance(x, y, z))); }
public void Clone(AABBi clone) { clone.MaxX = this.MaxX; clone.MaxY = this.MaxY; clone.MaxZ = this.MaxZ; clone.MinX = this.MinX; clone.MinY = this.MinY; clone.MinZ = this.MinZ; }
public void Set(AABBi from, Vector3 translation) { MinX = (int)Math.Floor(from.MinX + translation.X); MinY = (int)Math.Floor(from.MinY + translation.Y); MinZ = (int)Math.Floor(from.MinZ + translation.Z); MaxX = (int)Math.Ceiling(from.MaxX + translation.X); MaxY = (int)Math.Ceiling(from.MaxY + translation.Y); MaxZ = (int)Math.Ceiling(from.MaxZ + translation.Z); }
public void Set(AABBi 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 void Add(AABBi 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 Intersects(AABBi other) { return ((MaxX > other.MinX && MaxX < other.MaxX) || (MinX > other.MinX && MinX < other.MaxX) || (MaxY > other.MinY && MaxY < other.MaxY) || (MinY > other.MinY && MinY < other.MaxY) || (MaxZ > other.MinZ && MaxZ < other.MaxZ) || (MinZ > other.MinZ && MinZ < other.MaxZ)); }
// Creates a clone of the mesh #region Clone 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) { newVertices.Add(vertex); } var newBounds = new AABBi(Bounds); var newMesh = new CSGMesh( newPlanes, newPolygons, newEdges, newVertices, newBounds); return(newMesh); }
public AABBi Clone() { AABBi clone = new AABBi(); clone.MaxX = this.MaxX; clone.MaxY = this.MaxY; clone.MaxZ = this.MaxZ; clone.MinX = this.MinX; clone.MinY = this.MinY; clone.MinZ = this.MinZ; return(clone); }
void ComputeNext(Random random, AABBi current, AABBi next, VoxelField volume, ref Vector3i densestVoxel, long delta, double temperature) { current.Clone(next); do { double probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MinX = densestVoxel.X + random.Next(0 - densestVoxel.X, 0); } probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MinY = densestVoxel.Y + random.Next(0 - densestVoxel.Y, 0); } probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MinZ = densestVoxel.Z + random.Next(0 - densestVoxel.Z, 0); } probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MaxX = densestVoxel.X + random.Next(0, volume.VoxelSize.X - densestVoxel.X) + 1; } probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MaxY = densestVoxel.Y + random.Next(0, volume.VoxelSize.Y - densestVoxel.Y) + 1; } probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.MaxZ = densestVoxel.Z + random.Next(0, volume.VoxelSize.Z - densestVoxel.Z) + 1; } } while (!TestRangeForFreeSpace(volume, new Vector3i(next.MinX, next.MinY, next.MinZ), new Vector3i(next.MaxX, next.MaxY, next.MaxZ))); }
public PlaneSideResult OnSide(AABBi 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 = SignedDistance(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 = SignedDistance(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); }
protected static bool TestRangeForFreeSpace(VoxelField volume, AABBi box) { return TestRangeForFreeSpace(volume, new Vector3i(box.MinX, box.MinY, box.MinZ), new Vector3i(box.MaxX - 1, box.MaxY - 1, box.MaxZ - 1)); }
protected static AABBi ExpandAndFillBox(VoxelField volume, ref Vector3i originVoxel, Byte fillByte) { int pX, nX, pY, nY, pZ, nZ; pX = nX = pY = nY = pZ = nZ = 0; volume.SetVoxel(originVoxel.X, originVoxel.Y, originVoxel.Z, fillByte); bool boxGrew = false; do { // +Z Axis bool pZGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - nY, originVoxel.Z + (pZ + 1)), new Vector3i(originVoxel.X + pX, originVoxel.Y + pY, originVoxel.Z + (pZ + 1)), fillByte); if (pZGrew) { pZ++; } // -Z Axis bool nZGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - nY, originVoxel.Z - (nZ + 1)), new Vector3i(originVoxel.X + pX, originVoxel.Y + pY, originVoxel.Z - (nZ + 1)), fillByte); if (nZGrew) { nZ++; } // +Y Axis bool pYGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y + (pY + 1), originVoxel.Z - nZ), new Vector3i(originVoxel.X + pX, originVoxel.Y + (pY + 1), originVoxel.Z + pZ), fillByte); if (pYGrew) { pY++; } // -Y Axis bool nYGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - (nY + 1), originVoxel.Z - nZ), new Vector3i(originVoxel.X + pX, originVoxel.Y - (nY + 1), originVoxel.Z + pZ), fillByte); if (nYGrew) { nY++; } // +X Axis bool pXGrew = FillRange(volume, new Vector3i(originVoxel.X + (pX + 1), originVoxel.Y - nY, originVoxel.Z - nZ), new Vector3i(originVoxel.X + (pX + 1), originVoxel.Y + pY, originVoxel.Z + pZ), fillByte); if (pXGrew) { pX++; } // -X Axis bool nXGrew = FillRange(volume, new Vector3i(originVoxel.X - (nX + 1), originVoxel.Y - nY, originVoxel.Z - nZ), new Vector3i(originVoxel.X - (nX + 1), originVoxel.Y + pY, originVoxel.Z + pZ), fillByte); if (nXGrew) { nX++; } boxGrew = (pZGrew || nZGrew || pYGrew || nYGrew || pXGrew || nXGrew); } while (boxGrew); AABBi box = new AABBi(); box.MinX = originVoxel.X - nX; box.MinY = originVoxel.Y - nY; box.MinZ = originVoxel.Z - nZ; box.MaxX = originVoxel.X + pX + 1; box.MaxY = originVoxel.Y + pY + 1; box.MaxZ = originVoxel.Z + pZ + 1; return(box); }
// 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 mesh, 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: { mesh.Intersect(categorizationNode.Bounds, categorizationNode.Planes, 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 (AABBi.IsOutside(processedNode.Bounds, relativeLeftTrans, leftNode.Bounds)) { if (AABBi.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 (AABBi.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { //Categorize(processedNode, left, mesh, // inputPolygons, // inside, aligned, revAligned, outside); categorizationNode = leftNode; goto Restart; } else { LogicalOr(processedNode, mesh, 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 (AABBi.IsOutside(processedNode.Bounds, relativeLeftTrans, leftNode.Bounds) || AABBi.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, mesh, 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 (AABBi.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 (AABBi.IsOutside(processedNode.Bounds, relativeRightTrans, rightNode.Bounds)) { categorizationNode = leftNode; goto Restart; } else { LogicalOr(processedNode, mesh, categorizationNode, inputPolygons, outside, revAligned, aligned, inside, true, false); } break; } } }
void ComputeNext(Random random, AABBi current, AABBi next, VoxelField volume, ref Vector3i densestVoxel, long delta, double temperature) { current.Clone(next); do { double probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MinX = densestVoxel.X + random.Next(0 - densestVoxel.X, 0); probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MinY = densestVoxel.Y + random.Next(0 - densestVoxel.Y, 0); probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MinZ = densestVoxel.Z + random.Next(0 - densestVoxel.Z, 0); probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MaxX = densestVoxel.X + random.Next(0, volume.VoxelSize.X - densestVoxel.X) + 1; probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MaxY = densestVoxel.Y + random.Next(0, volume.VoxelSize.Y - densestVoxel.Y) + 1; probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) next.MaxZ = densestVoxel.Z + random.Next(0, volume.VoxelSize.Z - densestVoxel.Z) + 1; } while (!TestRangeForFreeSpace(volume, new Vector3i(next.MinX, next.MinY, next.MinZ), new Vector3i(next.MaxX, next.MaxY, next.MaxZ))); }
protected static bool FillRange(VoxelField volume, AABBi box, Byte fillByte) { return FillRange(volume, new Vector3i(box.MinX, box.MinY, box.MinZ), new Vector3i(box.MaxX - 1, box.MaxY - 1, box.MaxZ - 1), fillByte); }
public static bool IsOutside(AABBi left, Vector3 translation, AABBi 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 bool IsOutside(AABBi 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 static bool IsOutside(AABBi left, AABBi 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 VoxelizationOutput Generate(VoxelizationInput input, Action<VoxelizationProgress> progress) { this.input = input; VoxelizationOutput output = new VoxelizationOutput(); output.Octree = input.Octree; List<List<VoxelizingOctreeCell>> cellList = new List<List<VoxelizingOctreeCell>>(); input.Octree.AccumulateChildren(out cellList); VolumeAccumulator volume = new VolumeAccumulator(); VolumeAccumulator[] volumeAtLevel = new VolumeAccumulator[input.Octree.MaxLevels]; for (int i = 0; i < input.Octree.MaxLevels; i++) { List<VoxelizingOctreeCell> childernAtDepth = cellList[i]; VolumeAccumulator levelVolumeTotal = new VolumeAccumulator(); Parallel.For(0, childernAtDepth.Count, () => new VolumeAccumulator(), (n, loop, partial) => { VoxelizingOctreeCell cell = childernAtDepth[n]; float sideLength = cell.Length; switch (cell.Status) { case CellStatus.Inside: partial.InsideTotal += (sideLength * sideLength * sideLength); break; case CellStatus.Outside: partial.OutsideTotal += (sideLength * sideLength * sideLength); break; case CellStatus.Intersecting: case CellStatus.IntersectingBounds: if (cell.IsLeaf) partial.IntersectingTotal += (sideLength * sideLength * sideLength); break; } return partial; }, partial => { lock (levelVolumeTotal) { levelVolumeTotal.InsideTotal += partial.InsideTotal; levelVolumeTotal.OutsideTotal += partial.OutsideTotal; levelVolumeTotal.IntersectingTotal += partial.IntersectingTotal; } }); volume.InsideTotal += levelVolumeTotal.InsideTotal; volume.OutsideTotal += levelVolumeTotal.OutsideTotal; volume.IntersectingTotal += levelVolumeTotal.IntersectingTotal; volumeAtLevel[i] = levelVolumeTotal; } Debug.WriteLine("Percentage of inner volume at each octree level"); for (int i = 0; i < input.Octree.MaxLevels; i++) { Debug.WriteLine("Level {0}: Inner Volume {1}%", i, (volumeAtLevel[i].InsideTotal / volume.InsideTotal) * 100); } // A good check to perform is to compare the ratio of intersecting volume leaf nodes to the total volume // we've determined is inside. A tool could use this ratio to automatically determine a good octree level // by iterative optimization. If a mesh for example fails to get at least a 1 : 0.5 ratio of intersecting:inner // volume ratio it's a good bet that the octree does not subdivide enough levels in order to find enough inner volume // to meet our occlusion needs. If further subdivision up to some maximum, lets say 8 fails to ever meet this ratio // one could say the mesh is not a good candidate for automating occluder generation. Debug.WriteLine(""); float intersecting_inside_ratio = volume.InsideTotal / volume.IntersectingTotal; Debug.WriteLine("Intersecting : Inner = 1:{0}", intersecting_inside_ratio); Debug.WriteLine("Inner / (Inner + Intersecting) = {0}", volume.InsideTotal / (volume.InsideTotal + volume.IntersectingTotal)); const float MINIMUM_INTERSECTING_TO_INSIDE_RATIO = 0.25f; AABBf meshBounds = input.Octree.MeshBounds; double dX = meshBounds.MaxX - meshBounds.MinX; double dY = meshBounds.MaxY - meshBounds.MinY; double dZ = meshBounds.MaxZ - meshBounds.MinZ; double reduction = 0.5; for (int i = 0; i <= input.Octree.MaxLevels * 2; i++) reduction *= 0.5; dX = dX * reduction; dY = dY * reduction; dZ = dZ * reduction; if (intersecting_inside_ratio > MINIMUM_INTERSECTING_TO_INSIDE_RATIO) { List<AABBi> innerBounds = new List<AABBi>(); float innerVolumeGathered = 0.0f; for (int i = 0; i < input.Octree.MaxLevels; i++) { for (int n = 0; n < cellList[i].Count; n++) { if (cellList[i][n].Status == CellStatus.Inside) { AABBf bound = cellList[i][n].Bounds; AABBi bi = new AABBi(); bi.MaxX = (int)Math.Round(((double)bound.MaxX - (double)meshBounds.MinX) / dX, MidpointRounding.AwayFromZero); bi.MaxY = (int)Math.Round(((double)bound.MaxY - (double)meshBounds.MinY) / dY, MidpointRounding.AwayFromZero); bi.MaxZ = (int)Math.Round(((double)bound.MaxZ - (double)meshBounds.MinZ) / dZ, MidpointRounding.AwayFromZero); bi.MinX = (int)Math.Round(((double)bound.MinX - (double)meshBounds.MinX) / dX, MidpointRounding.AwayFromZero); bi.MinY = (int)Math.Round(((double)bound.MinY - (double)meshBounds.MinY) / dY, MidpointRounding.AwayFromZero); bi.MinZ = (int)Math.Round(((double)bound.MinZ - (double)meshBounds.MinZ) / dZ, MidpointRounding.AwayFromZero); innerBounds.Add(bi); } } innerVolumeGathered += volumeAtLevel[i].InsideTotal / volume.InsideTotal; if (innerVolumeGathered > input.MinimumVolume) { break; } } Debug.WriteLine("Enough inner volume found {0}%", innerVolumeGathered * 100.0f); Mesh mesh = MeshBuilder.BuildMesh(innerBounds); for (int i = 0; i < mesh.Vertices.Length; i++) { mesh.Vertices[i].X = (float)(((double)meshBounds.MinX) + (mesh.Vertices[i].X * dX)); mesh.Vertices[i].Y = (float)(((double)meshBounds.MinY) + (mesh.Vertices[i].Y * dY)); mesh.Vertices[i].Z = (float)(((double)meshBounds.MinZ) + (mesh.Vertices[i].Z * dZ)); } if (input.Retriangulate) { Mesh triangulatedMesh = MeshOptimizer.Retriangulate(input, mesh, out output.DebugLines); if (triangulatedMesh != null) mesh = triangulatedMesh; } mesh = PolygonFilter.Filter(input, mesh); output.OccluderMesh = new RenderableMesh(mesh, true); } else { Debug.WriteLine("Not enough inner volume found to continue."); } return output; }
// Intersects a mesh with a brush (set of planes) #region Intersect public void Intersect(AABBi 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 = 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 (!AABBi.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); 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 VoxelizationOutput Generate(VoxelizationInput input, Action <VoxelizationProgress> progress) { this.input = input; VoxelizationOutput output = new VoxelizationOutput(); output.Octree = input.Octree; List <List <VoxelizingOctreeCell> > cellList = new List <List <VoxelizingOctreeCell> >(); input.Octree.AccumulateChildren(out cellList); VolumeAccumulator volume = new VolumeAccumulator(); VolumeAccumulator[] volumeAtLevel = new VolumeAccumulator[input.Octree.MaxLevels]; for (int i = 0; i < input.Octree.MaxLevels; i++) { List <VoxelizingOctreeCell> childernAtDepth = cellList[i]; VolumeAccumulator levelVolumeTotal = new VolumeAccumulator(); Parallel.For(0, childernAtDepth.Count, () => new VolumeAccumulator(), (n, loop, partial) => { VoxelizingOctreeCell cell = childernAtDepth[n]; float sideLength = cell.Length; switch (cell.Status) { case CellStatus.Inside: partial.InsideTotal += (sideLength * sideLength * sideLength); break; case CellStatus.Outside: partial.OutsideTotal += (sideLength * sideLength * sideLength); break; case CellStatus.Intersecting: case CellStatus.IntersectingBounds: if (cell.IsLeaf) { partial.IntersectingTotal += (sideLength * sideLength * sideLength); } break; } return(partial); }, partial => { lock (levelVolumeTotal) { levelVolumeTotal.InsideTotal += partial.InsideTotal; levelVolumeTotal.OutsideTotal += partial.OutsideTotal; levelVolumeTotal.IntersectingTotal += partial.IntersectingTotal; } }); volume.InsideTotal += levelVolumeTotal.InsideTotal; volume.OutsideTotal += levelVolumeTotal.OutsideTotal; volume.IntersectingTotal += levelVolumeTotal.IntersectingTotal; volumeAtLevel[i] = levelVolumeTotal; } Debug.WriteLine("Percentage of inner volume at each octree level"); for (int i = 0; i < input.Octree.MaxLevels; i++) { Debug.WriteLine("Level {0}: Inner Volume {1}%", i, (volumeAtLevel[i].InsideTotal / volume.InsideTotal) * 100); } // A good check to perform is to compare the ratio of intersecting volume leaf nodes to the total volume // we've determined is inside. A tool could use this ratio to automatically determine a good octree level // by iterative optimization. If a mesh for example fails to get at least a 1 : 0.5 ratio of intersecting:inner // volume ratio it's a good bet that the octree does not subdivide enough levels in order to find enough inner volume // to meet our occlusion needs. If further subdivision up to some maximum, lets say 8 fails to ever meet this ratio // one could say the mesh is not a good candidate for automating occluder generation. Debug.WriteLine(""); float intersecting_inside_ratio = volume.InsideTotal / volume.IntersectingTotal; Debug.WriteLine("Intersecting : Inner = 1:{0}", intersecting_inside_ratio); Debug.WriteLine("Inner / (Inner + Intersecting) = {0}", volume.InsideTotal / (volume.InsideTotal + volume.IntersectingTotal)); const float MINIMUM_INTERSECTING_TO_INSIDE_RATIO = 0.25f; AABBf meshBounds = input.Octree.MeshBounds; double dX = meshBounds.MaxX - meshBounds.MinX; double dY = meshBounds.MaxY - meshBounds.MinY; double dZ = meshBounds.MaxZ - meshBounds.MinZ; double reduction = 0.5; for (int i = 0; i <= input.Octree.MaxLevels * 2; i++) { reduction *= 0.5; } dX = dX * reduction; dY = dY * reduction; dZ = dZ * reduction; if (intersecting_inside_ratio > MINIMUM_INTERSECTING_TO_INSIDE_RATIO) { List <AABBi> innerBounds = new List <AABBi>(); float innerVolumeGathered = 0.0f; for (int i = 0; i < input.Octree.MaxLevels; i++) { for (int n = 0; n < cellList[i].Count; n++) { if (cellList[i][n].Status == CellStatus.Inside) { AABBf bound = cellList[i][n].Bounds; AABBi bi = new AABBi(); bi.MaxX = (int)Math.Round(((double)bound.MaxX - (double)meshBounds.MinX) / dX, MidpointRounding.AwayFromZero); bi.MaxY = (int)Math.Round(((double)bound.MaxY - (double)meshBounds.MinY) / dY, MidpointRounding.AwayFromZero); bi.MaxZ = (int)Math.Round(((double)bound.MaxZ - (double)meshBounds.MinZ) / dZ, MidpointRounding.AwayFromZero); bi.MinX = (int)Math.Round(((double)bound.MinX - (double)meshBounds.MinX) / dX, MidpointRounding.AwayFromZero); bi.MinY = (int)Math.Round(((double)bound.MinY - (double)meshBounds.MinY) / dY, MidpointRounding.AwayFromZero); bi.MinZ = (int)Math.Round(((double)bound.MinZ - (double)meshBounds.MinZ) / dZ, MidpointRounding.AwayFromZero); innerBounds.Add(bi); } } innerVolumeGathered += volumeAtLevel[i].InsideTotal / volume.InsideTotal; if (innerVolumeGathered > input.MinimumVolume) { break; } } Debug.WriteLine("Enough inner volume found {0}%", innerVolumeGathered * 100.0f); Mesh mesh = MeshBuilder.BuildMesh(innerBounds); for (int i = 0; i < mesh.Vertices.Length; i++) { mesh.Vertices[i].X = (float)(((double)meshBounds.MinX) + (mesh.Vertices[i].X * dX)); mesh.Vertices[i].Y = (float)(((double)meshBounds.MinY) + (mesh.Vertices[i].Y * dY)); mesh.Vertices[i].Z = (float)(((double)meshBounds.MinZ) + (mesh.Vertices[i].Z * dZ)); } if (input.Retriangulate) { Mesh triangulatedMesh = MeshOptimizer.Retriangulate(input, mesh, out output.DebugLines); if (triangulatedMesh != null) { mesh = triangulatedMesh; } } mesh = PolygonFilter.Filter(input, mesh); output.OccluderMesh = new RenderableMesh(mesh, true); } else { Debug.WriteLine("Not enough inner volume found to continue."); } return(output); }
public AABBi(AABBi other) { Clear(); Set(other); }
public CSGMesh(Plane[] planes, List <Polygon> polygons, List <HalfEdge> edges, List <Vector3> vertices, AABBi bounds) { this.Planes = planes; this.Polygons = polygons; this.Edges = edges; this.Vertices = vertices; this.Bounds.Set(bounds); }
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) { newVertices.Add(vertex); } var newBounds = new AABBi(Bounds); var newMesh = new CSGMesh( newPlanes, newPolygons, newEdges, newVertices, newBounds); return newMesh; }
// Combines multiple meshes into one #region Combine 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 AABBi(); bounds.Clear(); int edgeIndex = 0; int polygonIndex = 0; foreach (var item in brushMeshes) { var node = item.Key; var translation = node.Translation - offset; var mesh = item.Value; foreach (var edge in mesh.Edges) { short vertexIndex; var vertex = 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 AABBi Clone() { AABBi clone = new AABBi(); clone.MaxX = this.MaxX; clone.MaxY = this.MaxY; clone.MaxZ = this.MaxZ; clone.MinX = this.MinX; clone.MinY = this.MinY; clone.MinZ = this.MinZ; return clone; }
AABBi BruteForceFill(VoxelizationInput input, SilhouetteOcclusionValidator sov, VoxelField voxelField, Vector3i densestVoxel, byte fillByte, List <Occluder> currentOccluders) { Object syncroot = new Object(); Int64 largestVolume = 1; AABBi largestOccluder = new AABBi(densestVoxel.X, densestVoxel.Y, densestVoxel.Z, densestVoxel.X + 1, densestVoxel.Y + 1, densestVoxel.Z + 1); int MaxTopOccluders = 2000; List <AABBi> bestOccluders = new List <AABBi>(MaxTopOccluders); Parallel.For(densestVoxel.Z + 1, voxelField.VoxelSize.Z, max_z => { for (Int32 min_z = densestVoxel.Z; min_z >= 0; --min_z) { for (Int32 max_y = densestVoxel.Y + 1; max_y < voxelField.VoxelSize.Y; ++max_y) { for (Int32 min_y = densestVoxel.Y; min_y >= 0; --min_y) { for (Int32 max_x = densestVoxel.X + 1; max_x < voxelField.VoxelSize.X; ++max_x) { for (Int32 min_x = densestVoxel.X; min_x >= 0; --min_x) { Int32 dx = max_x - min_x; Int32 dy = max_y - min_y; Int32 dz = max_z - min_z; Int64 volume = dx * dy * dz; if (TestRangeForFreeSpace(voxelField, new AABBi(min_x, min_y, min_z, max_x, max_y, max_z))) { lock (syncroot) { if (volume > largestVolume) { largestVolume = volume; largestOccluder = new AABBi(min_x, min_y, min_z, max_x, max_y, max_z); if (bestOccluders.Count >= MaxTopOccluders) { bestOccluders.RemoveAt(MaxTopOccluders - 1); } bestOccluders.Insert(0, largestOccluder); } } } else { // if we can't expand outward any further there's no point in checking more. break; } } } } } } Debug.WriteLine("Checked " + max_z); }); List <AABBi> relevantOccluders = GetRelevantOccluders(input, currentOccluders); long bestCoverage = 0; AABBi bestCoverageVolume = largestOccluder; foreach (AABBi occluder in bestOccluders) { List <AABBi> tempOccluders = relevantOccluders.ToList(); tempOccluders.Add(occluder); long coverage = MeasureOccluderOcclusion(sov, input, tempOccluders); if (coverage > bestCoverage) { bestCoverage = coverage; bestCoverageVolume = occluder; } } FillRange(voxelField, bestCoverageVolume, fillByte); return(bestCoverageVolume); }
AABBi SimulatedAnnealingFill(VoxelizationInput input, SilhouetteOcclusionValidator sov, VoxelField volume, ref Vector3i densestVoxel, byte fillByte, List <Occluder> currentOccluders) { AABBi current = new AABBi(densestVoxel.X, densestVoxel.Y, densestVoxel.Z, densestVoxel.X + 1, densestVoxel.Y + 1, densestVoxel.Z + 1); AABBi next = new AABBi(0, 0, 0, 0, 0, 0); int iteration = -1; List <AABBi> relevantOccluders = GetRelevantOccluders(input, currentOccluders); List <AABBi> occluders = relevantOccluders.ToList(); occluders.Add(current); long coverage = MeasureOccluderOcclusion(sov, input, occluders); double coolignAlpha = 0.999; double temperature = 400.0; double epsilon = 0.001; Random random = new Random(1337); int maxItterations = 1000; long delta = 0; while (temperature > epsilon && iteration < maxItterations) { iteration++; ComputeNext(random, current, next, volume, ref densestVoxel, delta, temperature); occluders = relevantOccluders.ToList(); occluders.Add(next); delta = MeasureOccluderOcclusion(sov, input, occluders) - coverage; if (delta < 0) { next.Clone(current); coverage = delta + coverage; } else { double probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.Clone(current); coverage = delta + coverage; } } temperature *= coolignAlpha; if (iteration % 400 == 0) { Console.WriteLine(coverage); } } FillRange(volume, new Vector3i(current.MinX, current.MinY, current.MinZ), new Vector3i(current.MaxX, current.MaxY, current.MaxZ), fillByte); return(current); }
public bool Intersects(AABBi other) { return (MaxX > other.MinX && MaxX < other.MaxX) || (MinX > other.MinX && MinX < other.MaxX) || (MaxY > other.MinY && MaxY < other.MaxY) || (MinY > other.MinY && MinY < other.MaxY) || (MaxZ > other.MinZ && MaxZ < other.MaxZ) || (MinZ > other.MinZ && MinZ < other.MaxZ); }
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 AABBi(); 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.Cross(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.Dot(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 bool IsOutside(AABBi 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 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 AABBi(); bounds.Clear(); int edgeIndex = 0; int polygonIndex = 0; foreach (var item in brushMeshes) { var node = item.Key; var translation = node.Translation - offset; var mesh = item.Value; foreach (var edge in mesh.Edges) { short vertexIndex; var vertex = 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(AABBi 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 = 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 (!AABBi.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); 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; } } }
AABBi BruteForceFill(VoxelizationInput input, SilhouetteOcclusionValidator sov, VoxelField voxelField, Vector3i densestVoxel, byte fillByte, List<Occluder> currentOccluders) { Object syncroot = new Object(); Int64 largestVolume = 1; AABBi largestOccluder = new AABBi(densestVoxel.X, densestVoxel.Y, densestVoxel.Z, densestVoxel.X + 1, densestVoxel.Y + 1, densestVoxel.Z + 1); int MaxTopOccluders = 2000; List<AABBi> bestOccluders = new List<AABBi>(MaxTopOccluders); Parallel.For(densestVoxel.Z + 1, voxelField.VoxelSize.Z, max_z => { for (Int32 min_z = densestVoxel.Z; min_z >= 0; --min_z) { for (Int32 max_y = densestVoxel.Y + 1; max_y < voxelField.VoxelSize.Y; ++max_y) { for (Int32 min_y = densestVoxel.Y; min_y >= 0; --min_y) { for (Int32 max_x = densestVoxel.X + 1; max_x < voxelField.VoxelSize.X; ++max_x) { for (Int32 min_x = densestVoxel.X; min_x >= 0; --min_x) { Int32 dx = max_x - min_x; Int32 dy = max_y - min_y; Int32 dz = max_z - min_z; Int64 volume = dx * dy * dz; if (TestRangeForFreeSpace(voxelField, new AABBi(min_x, min_y, min_z, max_x, max_y, max_z))) { lock (syncroot) { if (volume > largestVolume) { largestVolume = volume; largestOccluder = new AABBi(min_x, min_y, min_z, max_x, max_y, max_z); if (bestOccluders.Count >= MaxTopOccluders) bestOccluders.RemoveAt(MaxTopOccluders - 1); bestOccluders.Insert(0, largestOccluder); } } } else { // if we can't expand outward any further there's no point in checking more. break; } } } } } } Debug.WriteLine("Checked " + max_z); }); List<AABBi> relevantOccluders = GetRelevantOccluders(input, currentOccluders); long bestCoverage = 0; AABBi bestCoverageVolume = largestOccluder; foreach (AABBi occluder in bestOccluders) { List<AABBi> tempOccluders = relevantOccluders.ToList(); tempOccluders.Add(occluder); long coverage = MeasureOccluderOcclusion(sov, input, tempOccluders); if (coverage > bestCoverage) { bestCoverage = coverage; bestCoverageVolume = occluder; } } FillRange(voxelField, bestCoverageVolume, fillByte); return bestCoverageVolume; }
AABBi SimulatedAnnealingFill(VoxelizationInput input, SilhouetteOcclusionValidator sov, VoxelField volume, ref Vector3i densestVoxel, byte fillByte, List<Occluder> currentOccluders) { AABBi current = new AABBi(densestVoxel.X, densestVoxel.Y, densestVoxel.Z, densestVoxel.X + 1, densestVoxel.Y + 1, densestVoxel.Z + 1); AABBi next = new AABBi(0, 0, 0, 0, 0, 0); int iteration = -1; List<AABBi> relevantOccluders = GetRelevantOccluders(input, currentOccluders); List<AABBi> occluders = relevantOccluders.ToList(); occluders.Add(current); long coverage = MeasureOccluderOcclusion(sov, input, occluders); double coolignAlpha = 0.999; double temperature = 400.0; double epsilon = 0.001; Random random = new Random(1337); int maxItterations = 1000; long delta = 0; while (temperature > epsilon && iteration < maxItterations) { iteration++; ComputeNext(random, current, next, volume, ref densestVoxel, delta, temperature); occluders = relevantOccluders.ToList(); occluders.Add(next); delta = MeasureOccluderOcclusion(sov, input, occluders) - coverage; if (delta < 0) { next.Clone(current); coverage = delta + coverage; } else { double probability = random.NextDouble(); if (probability < Math.Exp(-delta / temperature)) { next.Clone(current); coverage = delta + coverage; } } temperature *= coolignAlpha; if (iteration % 400 == 0) Console.WriteLine(coverage); } FillRange(volume, new Vector3i(current.MinX, current.MinY, current.MinZ), new Vector3i(current.MaxX, current.MaxY, current.MaxZ), fillByte); return current; }
public static bool IsOutside(AABBi left, AABBi 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 static bool IsOutside(AABBi left, Vector3 translation, AABBi 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; }
protected static bool FillRange(VoxelField volume, AABBi box, Byte fillByte) { return(FillRange(volume, new Vector3i(box.MinX, box.MinY, box.MinZ), new Vector3i(box.MaxX - 1, box.MaxY - 1, box.MaxZ - 1), fillByte)); }
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 AABBi(); 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.Cross(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.Dot(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)); }
protected static bool TestRangeForFreeSpace(VoxelField volume, AABBi box) { return(TestRangeForFreeSpace(volume, new Vector3i(box.MinX, box.MinY, box.MinZ), new Vector3i(box.MaxX - 1, box.MaxY - 1, box.MaxZ - 1))); }
protected static AABBi ExpandAndFillBox(VoxelField volume, ref Vector3i originVoxel, Byte fillByte) { int pX, nX, pY, nY, pZ, nZ; pX = nX = pY = nY = pZ = nZ = 0; volume.SetVoxel(originVoxel.X, originVoxel.Y, originVoxel.Z, fillByte); bool boxGrew = false; do { // +Z Axis bool pZGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - nY, originVoxel.Z + (pZ + 1)), new Vector3i(originVoxel.X + pX, originVoxel.Y + pY, originVoxel.Z + (pZ + 1)), fillByte); if (pZGrew) pZ++; // -Z Axis bool nZGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - nY, originVoxel.Z - (nZ + 1)), new Vector3i(originVoxel.X + pX, originVoxel.Y + pY, originVoxel.Z - (nZ + 1)), fillByte); if (nZGrew) nZ++; // +Y Axis bool pYGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y + (pY + 1), originVoxel.Z - nZ), new Vector3i(originVoxel.X + pX, originVoxel.Y + (pY + 1), originVoxel.Z + pZ), fillByte); if (pYGrew) pY++; // -Y Axis bool nYGrew = FillRange(volume, new Vector3i(originVoxel.X - nX, originVoxel.Y - (nY + 1), originVoxel.Z - nZ), new Vector3i(originVoxel.X + pX, originVoxel.Y - (nY + 1), originVoxel.Z + pZ), fillByte); if (nYGrew) nY++; // +X Axis bool pXGrew = FillRange(volume, new Vector3i(originVoxel.X + (pX + 1), originVoxel.Y - nY, originVoxel.Z - nZ), new Vector3i(originVoxel.X + (pX + 1), originVoxel.Y + pY, originVoxel.Z + pZ), fillByte); if (pXGrew) pX++; // -X Axis bool nXGrew = FillRange(volume, new Vector3i(originVoxel.X - (nX + 1), originVoxel.Y - nY, originVoxel.Z - nZ), new Vector3i(originVoxel.X - (nX + 1), originVoxel.Y + pY, originVoxel.Z + pZ), fillByte); if (nXGrew) nX++; boxGrew = (pZGrew || nZGrew || pYGrew || nYGrew || pXGrew || nXGrew); } while (boxGrew); AABBi box = new AABBi(); box.MinX = originVoxel.X - nX; box.MinY = originVoxel.Y - nY; box.MinZ = originVoxel.Z - nZ; box.MaxX = originVoxel.X + pX + 1; box.MaxY = originVoxel.Y + pY + 1; box.MaxZ = originVoxel.Z + pZ + 1; return box; }