public void RecursivePlaneSplit(int treeDepth, int maxTreeDepth, int maxGeometryPerNode, int parentSplitAxis) { Contract.Requires(treeDepth > 0); Contract.Requires(maxTreeDepth > 0); Contract.Requires(maxGeometryPerNode > 0); Contract.Requires(0 <= parentSplitAxis && parentSplitAxis <= 2); Contract.Requires(splittingPlane == null, "splitting-plane is null"); Assert.IsTrue(normalSide == null, "normal-side is null"); Assert.IsTrue(backSide == null, "back-side is null"); Assert.IsTrue(geometry != null, "geometry collection is null"); Assert.IsTrue(geometry.Count > 0, "geometry collection is empty"); leafColor = (uint)random.Next(); totalTreeDepth = Math.Max(totalTreeDepth, treeDepth); if (treeDepth >= maxTreeDepth || // Have we made the tree deep enough? geometry.Count <= maxGeometryPerNode) // Stop splitting nodes when current node contains little enough geometry. { leafNodes++; ProcessLeafNode(); return; } // Pick an axis-aligned plane to split the current sector. // TODO: balance tree better by choosing splitting plane direction based on // longest bounding box length, or most even split of geometry? int axis; #if SPLIT_LONGEST_AXIS Vector boxExtent = boundingBox.Max - boundingBox.Min; boxExtent = new Vector(Math.Abs(boxExtent.x), Math.Abs(boxExtent.y), Math.Abs(boxExtent.z)); if (boxExtent.x > boxExtent.y) { if (boxExtent.x > boxExtent.z) { axis = 0; // X-axis } else { axis = 2; // Z-axis } } else { if (boxExtent.y > boxExtent.z) { axis = 1; // Y-axis } else { axis = 2; // Z-axis } } #else // Split on the next axis after the parent's split axis (i.e. X -> Y -> Z -> X ...) axis = (parentSplitAxis + 1) % 3; #endif Vector splitPt = boundingBox.Centre; #if ADAPTIVE_SPLIT // Compute centroid of all triangle vertices in this node. Vector centroid = new Vector(0, 0, 0); int vertexCount = 0; foreach (Triangle tri in geometry) { //if (boundingBox.ContainsPoint(tri.Vertex1)) //{ // centroid += tri.Vertex1; // vertexCount++; //} //if (boundingBox.ContainsPoint(tri.Vertex2)) //{ // centroid += tri.Vertex2; // vertexCount++; //} //if (boundingBox.ContainsPoint(tri.Vertex3)) //{ // centroid += tri.Vertex3; // vertexCount++; //} centroid += tri.Vertex1; centroid += tri.Vertex2; centroid += tri.Vertex3; vertexCount += 3; } splitPt = centroid / vertexCount; #endif // Create splitting plane. switch (axis) { case 0: splittingPlane = new Plane(splitPt, new Vector(1, 0, 0)); break; // X-axis case 1: splittingPlane = new Plane(splitPt, new Vector(0, 1, 0)); break; // Y-axis case 2: splittingPlane = new Plane(splitPt, new Vector(0, 0, 1)); break; // Z-axis default: Assert.Fail("Unexpected axis index"); break; } // Copy all geometry from current node to the appropriate child nodes. var normalSideGeom = new List <Triangle>(); var backSideGeom = new List <Triangle>(); foreach (Triangle geom in geometry) { PlaneHalfSpace planeHalfSpace = geom.IntersectPlane(splittingPlane); Assert.IsTrue((planeHalfSpace & ~(PlaneHalfSpace.NormalSide | PlaneHalfSpace.BackSide)) == 0, "Unexpected PlaneHalfSpace enumeration bit value"); if ((planeHalfSpace & PlaneHalfSpace.NormalSide) != 0) { normalSideGeom.Add(geom); } if ((planeHalfSpace & PlaneHalfSpace.BackSide) != 0) { backSideGeom.Add(geom); } } // If either child node ends up with no geometry or all geometry, keep the geometry in the current node. // TODO: if we cannot split along this axis, attempt to split along the other two axes? bool rejectSplit = (normalSideGeom.Count == geometry.Count || backSideGeom.Count == geometry.Count); #if LOG_SPLITS Logger.Log("Tree node: axis {0}, depth: {1}, {2} tri => {3},{4} {5}", axis, treeDepth, geometry.Count, normalSideGeom.Count, backSideGeom.Count, rejectSplit ? "(rejected split)" : ""); #endif if (rejectSplit) { // Throw away the child nodes, i.e. reverse the splitting of the current node. splittingPlane = null; leafNodes++; ProcessLeafNode(); return; } // Delete all geometry from current node, so that geometry only exists in child nodes. geometry.Clear(); // Create bounding boxes for child nodes. Vector backSideBoxMax = boundingBox.Max; Vector normalSideBoxMin = boundingBox.Min; switch (axis) { case 0: backSideBoxMax.x = normalSideBoxMin.x = splitPt.x; break; // X-axis case 1: backSideBoxMax.y = normalSideBoxMin.y = splitPt.y; break; // Y-axis case 2: backSideBoxMax.z = normalSideBoxMin.z = splitPt.z; break; // Z-axis default: Assert.Fail("Unexpected axis index"); break; } AxisAlignedBox backSideBox = new AxisAlignedBox(boundingBox.Min, backSideBoxMax); AxisAlignedBox normalSideBox = new AxisAlignedBox(normalSideBoxMin, boundingBox.Max); // Create child nodes and recursively split them. //axis = (axis + 1) % 3; treeDepth++; if (normalSideGeom.Count > 0) { normalSide = new Node(normalSideGeom, normalSideBox); normalSide.RecursivePlaneSplit(treeDepth, maxTreeDepth, maxGeometryPerNode, axis); } if (backSideGeom.Count > 0) { backSide = new Node(backSideGeom, backSideBox); backSide.RecursivePlaneSplit(treeDepth, maxTreeDepth, maxGeometryPerNode, axis); } // Is current node a leaf node? if (normalSide == null && backSide == null) { // Yes, so increment the leaf node count. // TODO: we never seem to reach this code! leafNodes++; splittingPlane = null; // TODO: Contracts analyser complains that this assert is always false, due to "initialisation of this.backSide" !?! Contract.Assert(geometry.Count > 0, "Leaf node must contain some geometry"); Contract.Assert(splittingPlane == null, "Leaf node must not have a splitting plane"); } else { // No, current node is an internal node. Check that no internal node has any geometry. Contract.Assert(geometry.Count == 0, "Internal node must not contain any geometry"); Contract.Assert(splittingPlane != null, "Internal node must have a splitting plane"); } }
/// <summary> /// Convert a list of triangles into a grid of voxels. /// </summary> /// <param name="triangles">Triangles bounded by the unit cube centred at the origin</param> /// <param name="voxelGridSize">The resolution of the voxel grid</param> /// <returns>Number of voxels cells that are 'filled in', i.e. non-empty</returns> static public int Convert(List <Raytrace.Triangle> triangles, int voxelGridSize, VoxelGrid voxelGrid) { Contract.Requires(Contract.ForAll(triangles, (t => t.Vertex1.x >= -0.5 && t.Vertex1.x <= 0.5 && t.Vertex2.y >= -0.5 && t.Vertex2.y <= 0.5 && t.Vertex3.z >= -0.5 && t.Vertex3.z <= 0.5))); var voxelColors = new uint[voxelGridSize, voxelGridSize, voxelGridSize]; var voxelNormals = new Vector[voxelGridSize, voxelGridSize, voxelGridSize]; int totalTriInCellCount = 0; int numFilledVoxels = 0; for (var x = 0; x < voxelGridSize; x++) { var x0 = ((double)x / voxelGridSize) - 0.5; var x1 = ((double)(x + 1) / voxelGridSize) - 0.5; Plane leftPlane = new Plane(new Vector(x0, 0, 0), new Vector(1, 0, 0)); Plane rightPlane = new Plane(new Vector(x1, 0, 0), new Vector(-1, 0, 0)); var trisInSlabX = FindTrianglesInsidePlanes(triangles, new List <Plane> { leftPlane, rightPlane }); for (var y = 0; y < voxelGridSize; y++) { var y0 = ((double)y / voxelGridSize) - 0.5; var y1 = ((double)(y + 1) / voxelGridSize) - 0.5; Plane topPlane = new Plane(new Vector(0, y0, 0), new Vector(0, 1, 0)); Plane bottomPlane = new Plane(new Vector(0, y1, 0), new Vector(0, -1, 0)); var trisInRowXY = FindTrianglesInsidePlanes(trisInSlabX, new List <Plane> { topPlane, bottomPlane /*, leftPlane, rightPlane*/ }); for (var z = 0; z < voxelGridSize; z++) { var z0 = ((double)z / voxelGridSize) - 0.5; var z1 = ((double)(z + 1) / voxelGridSize) - 0.5; Plane nearPlane = new Plane(new Vector(0, 0, z0), new Vector(0, 0, 1)); Plane farPlane = new Plane(new Vector(0, 0, z1), new Vector(0, 0, -1)); var trisInCell = FindTrianglesInsidePlanes(trisInRowXY, new List <Plane> { nearPlane, farPlane /*, topPlane, bottomPlane, leftPlane, rightPlane*/ }); // Color based on number of triangles in cell // uint color = (trisInCell.Count == 0 ? 0 : (uint)(trisInCell.Count * 123456789) | 0xff000000); // Fixed color // uint color = (trisInCell.Count == 0 ? 0 : 0xff00ff00); // Pick color of first triangle in cell // uint color = (trisInCell.Count == 0 ? 0 : trisInCell[0].Color); // Average colors of all triangles in cell uint cellColor = 0; if (trisInCell.Count > 0) { var voxelBox = new AxisAlignedBox(new Vector(x0, y0, z0), new Vector(x1, y1, z1)); Color color = Color.Black; int count = 0; foreach (var tri in trisInCell) { //if (voxelBox.IntersectsTriangle(tri)) { color += new Color(tri.Color); count++; } } color /= count; // trisInCell.Count; cellColor = color.ToARGB(); numFilledVoxels++; totalTriInCellCount += count; // trisInCell.Count; } voxelColors[x, y, z] = cellColor; // Pick normal of first triangle in cell Vector normal = (trisInCell.Count == 0 ? Vector.Zero : trisInCell[0].Plane.Normal); voxelNormals[x, y, z] = normal; } } } Contract.Assert(totalTriInCellCount >= triangles.Count); /* * // Random voxel grid * var random = new Random(1234567890); * for (var x = 0; x < voxelGridSize; x++) * { * for (var y = 0; y < voxelGridSize; y++) * { * for (var z = 0; z < voxelGridSize; z++) * { * // pick a random opaque color * var color = (random.Next(2) == 0 ? 0 : (uint)random.Next() | 0xff000000); * voxels[x, y, z] = color; * } * } * } */ voxelGrid.SetData(voxelColors, voxelNormals); return(numFilledVoxels); }