protected static float FindDistanceToAbnormalVoxelInRange( VoxelField volume, Vector3i voxel, int startX, int startY, int startZ, int endX, int endY, int endZ) { // 1. Ignore directions that take us outside the bounds of the voxel volume. if (startX < 0 || startY < 0 || startZ < 0) return float.MaxValue; float closestValueDist = float.MaxValue; // 2. Find the value along the axis for (Int32 z = startZ; z <= endZ; ++z) { for (Int32 y = startY; y <= endY; ++y) { for (Int32 x = startX; x <= endX; ++x) { byte value = volume.GetVoxel(x, y, z); if (value != 1) { float dist = (float)(voxel - new Vector3i(x, y, z)).LengthSquared; if (dist < closestValueDist) { closestValueDist = dist; } } } } } return closestValueDist; }
// initialization void Start() { m_leapController = handController.GetLeapController(); addDebugText("Leap connected:\t" + m_leapController.IsConnected); updateRadiusText(); updateStrengthText(); toolMaterial.SetFloat("_Radius", voxelObjectGPU.modManager.getToolRadius()); voxel = new VoxelField(voxelFieldSize); voxel.createNoisyGround(); initMesh(false); timeRemaining = timeMax; }
protected static bool FillRange(VoxelField volume, Vector3i start, Vector3i end, Byte fillByte) { // 1. Ensure that we can safely expand into the area. if (!TestRangeForFreeSpace(volume, start, end)) return false; // 2. Fill area for (Int32 z = start.Z; z <= end.Z; ++z) { for (Int32 y = start.Y; y <= end.Y; ++y) { for (Int32 x = start.X; x <= end.X; ++x) { volume.SetVoxel(x, y, z, fillByte); } } } return true; }
protected static Vector3i FindHighestDensityVoxel(VoxelField volume) { float denestDistance = float.MinValue; Vector3i densestVoxel = new Vector3i(0, 0, 0); Object syncroot = new Object(); Parallel.For(0, volume.VoxelSize.Z, z => { for (Int32 y = 0; y < volume.VoxelSize.Y; ++y) { for (Int32 x = 0; x < volume.VoxelSize.X; ++x) { byte value = volume.GetVoxel(x, y, z); // Ignore empty voxels and already boxed voxels if (value != 1) continue; float closestExtDist = FindShortestDistanceToAbnormalVoxel(volume, x, y, z); if (closestExtDist > denestDistance) { lock (syncroot) { if (closestExtDist > denestDistance) { denestDistance = closestExtDist; densestVoxel = new Vector3i(x, y, z); } } } } } }); return densestVoxel; }
/// <summary> /// writes the initial mesh to the GPU /// </summary> /// <param name="voxel">voxel field that is written to the GPU</param> /// <param name="rotation">rotation of the object</param> /// <param name="withSmooth">boolean, if the object should be smoothed</param> public void initMesh(VoxelField voxel, Vector3 rotation, bool withSmooth) { //Debug.Log("Update Voxel Buffer"); //before creating a new vertexBuffer the old one must be disposed vertexBuffer.Dispose(); vertexBuffer = new ComputeBuffer(maxVerticesSize, sizeof(float) * 6); //send the current voxel field to the gpu voxelBuffer.Dispose(); voxelBuffer = new ComputeBuffer(voxelFieldSize * voxelFieldSize * voxelFieldSize, sizeof(float)); voxelBuffer.SetData(voxel.getField()); modManager.setDensityBuffer(voxelBuffer); if (withSmooth) { modManager.InitialSmooth(10); // 10 shader passes with smooth } rotation.x = rotation.x / 180 * (float)Math.PI; rotation.y = rotation.y / 180 * (float)Math.PI; //calculate new vertices in vertexBuffer voxelComputeShader.SetFloat("rotationXaxis", rotation.x); voxelComputeShader.SetFloat("rotationYaxis", rotation.y); voxelComputeShader.SetFloat("scale", scaling); voxelComputeShader.SetFloats("positionOffset", 0f, 0f, 0f); voxelComputeShader.SetInt("cubeDimension", voxelCubeSize); voxelComputeShader.SetInt("dimension", voxelCubeSize + 1); voxelComputeShader.SetFloat("isolevel", 0.0f); voxelComputeShader.SetBuffer(0, "cubeEdgeFlags", edgeTable); voxelComputeShader.SetBuffer(0, "triangleConnectionTable", triTable); voxelComputeShader.SetBuffer(0, "voxel", voxelBuffer); //voxelComputeShader.SetBuffer(0, "normals", normalBuffer); voxelComputeShader.SetBuffer(0, "vertexBuffer", vertexBuffer); voxelComputeShader.Dispatch(0, voxelCubeSize / 8, voxelCubeSize / 8, voxelCubeSize / 8); }
private void resetAll(bool resetByServer) { if (resetByServer) { //todo check if current object should be exported export(); // transmit current id? //todo answer to server "all okay"? } //Debug.Log("Reseting environment"); updateRadiusText(); updateStrengthText(); toolMaterial.SetFloat("_Radius", voxelObjectGPU.modManager.getToolRadius()); objectScaling = 1.0f; scaling = objectSize * objectScaling / (float)voxelCubeSize; rotation = Vector3.zero; posOffset = Vector3.zero; resetBoundingBoxPosition(); voxel = new VoxelField(voxelFieldSize); voxel.createNoisyGround(); initMesh(true); voxelObjectGPU.modManager.ResetToolRange(); }
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 virtual VoxelizationOutput Generate(VoxelizationInput input, Action<VoxelizationProgress> progress) { VoxelizationProgress vp = new VoxelizationProgress(); DateTime start = DateTime.Now; vp.Status = "Building voxel field from octree"; progress(vp); VoxelField voxelField = new VoxelField(input.Octree); Byte fillByte = 2; float oldPercent = 1.0f; float newPercent = 1.0f; List<Occluder> occluders = new List<Occluder>(); vp.Status = "Calculating original mesh silhouette coverage"; progress(vp); SilhouetteOcclusionValidator sov = new SilhouetteOcclusionValidator(1024, 1024); long groundSideCoverage, groundTopCoverage; sov.ComputeCoverage(input.OriginalMesh, input.Octree.MeshBounds, out groundSideCoverage, out groundTopCoverage); long totalCoverage = groundSideCoverage + groundTopCoverage; if (totalCoverage == 0) totalCoverage = 1; vp.Status = "Fitting boxes into mesh..."; progress(vp); long oldOcclusion = 0; do { Vector3i densestVoxel = FindHighestDensityVoxel(voxelField); AABBi occluderBounds; if (input.Type == OcclusionType.BoxExpansion) { occluderBounds = ExpandAndFillBox(voxelField, ref densestVoxel, fillByte); } //else if (input.Type == OcclusionType.SimulatedAnnealing) //{ // occluderBounds = SimulatedAnnealingFill(input, sov, voxelField, ref densestVoxel, fillByte, occluders); //} else if (input.Type == OcclusionType.BruteForce) { occluderBounds = BruteForceFill(input, sov, voxelField, densestVoxel, fillByte, occluders); } else { throw new Exception("Unknown occluder generation type!"); } List<AABBi> relevantOccluders = GetRelevantOccluders(input, occluders); relevantOccluders.Add(occluderBounds); long newOcclusion = MeasureOccluderOcclusion(sov, input, relevantOccluders); Occluder occluder = new Occluder(); occluder.Bounds = occluderBounds; occluder.DeltaOcclusion = (newOcclusion - oldOcclusion) / (double)totalCoverage; occluders.Add(occluder); if (occluder.DeltaOcclusion > input.MinimumOcclusion) oldOcclusion = newOcclusion; Debug.WriteLine("Coverage " + occluder.DeltaOcclusion); Debug.WriteLine("Bounds (" + occluder.Bounds.MinX + "x" + occluder.Bounds.MaxX + " " + occluder.Bounds.MinY + "x" + occluder.Bounds.MaxY + " " + occluder.Bounds.MinZ + "x" + occluder.Bounds.MaxZ + ")"); oldPercent = newPercent; newPercent = MeasureUnboxedVoxels(voxelField); Debug.WriteLine("(" + densestVoxel.X + "," + densestVoxel.Y + "," + densestVoxel.Z + ")\tCoverage=" + ((1 - newPercent) * 100) + "%\tDelta=" + ((oldPercent - newPercent) * 100) + "%"); vp.Progress = Math.Min(((1 - newPercent) / input.MinimumVolume), 1.0f); vp.SilhouetteCoverage = oldOcclusion / (double)totalCoverage; vp.VolumeCoverage = 1 - newPercent; vp.Status = String.Format("Occlusion Progress : {0:0.##}%", (100 * vp.Progress)); progress(vp); } while (newPercent > (1 - input.MinimumVolume)); Mesh mesh = BuildMeshFromBoxes(input, GetRelevantOccluders(input, occluders)); VoxelizationOutput output = new VoxelizationOutput(); if (input.Retriangulate) { vp.Status = "Retriangulating occluder mesh"; progress(vp); Mesh triangulatedMesh = MeshOptimizer.Retriangulate(input, mesh, out output.DebugLines); if (triangulatedMesh != null) mesh = triangulatedMesh; } vp.Status = "Filtering polygons"; progress(vp); mesh = PolygonFilter.Filter(input, mesh); vp.Status = "Generating final occlusion mesh"; progress(vp); // Prepare the output output.Octree = input.Octree; output.TimeTaken = DateTime.Now - start; output.VolumeCoverage = 1 - newPercent; output.SilhouetteCoverage = oldOcclusion / (double)totalCoverage; output.OccluderMesh = new RenderableMesh(mesh, true); vp.Status = "Cleanup..."; progress(vp); sov.Dispose(); return output; }
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))); }
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; }
protected static float FindShortestDistanceToAbnormalVoxel(VoxelField volume, int x, int y, int z) { int pX, nX, pY, nY, pZ, nZ; pX = nX = pY = nY = pZ = nZ = 0; Vector3i voxel = new Vector3i(x, y, z); do { // +Z Axis float distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x - nX, y - nY, z + (pZ + 1), x + pX, y + pY, z + (pZ + 1)); if (distance != float.MaxValue) return distance; pZ++; // -Z Axis distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x - nX, y - nY, z - (nZ + 1), x + pX, y + pY, z - (nZ + 1)); if (distance != float.MaxValue) return distance; nZ++; // +Y Axis distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x - nX, y + (pY + 1), z - nZ, x + pX, y + (pY + 1), z + pZ); if (distance != float.MaxValue) return distance; pY++; // -Y Axis distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x - nX, y - (nY + 1), z - nZ, x + pX, y - (nY + 1), z + pZ); if (distance != float.MaxValue) return distance; nY++; // +X Axis distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x + (pX + 1), y - nY, z - nZ, x + (pX + 1), y + pY, z + pZ); if (distance != float.MaxValue) return distance; pX++; // -X Axis distance = FindDistanceToAbnormalVoxelInRange(volume, voxel, x - (nX + 1), y - nY, z - nZ, x - (nX + 1), y + pY, z + pZ); if (distance != float.MaxValue) return distance; nX++; } while (true); }
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); }
protected static bool TestRangeForFreeSpace(VoxelField volume, Vector3i start, Vector3i end) { // 1. Ignore directions that take us outside the bounds of the voxel volume. if (start.X < 0 || start.Y < 0 || start.Z < 0) return false; // 2. Ensure that we can safely expand into the area, expand as long as the voxel isn't empty. // Note: it is ok to expand into a space already occupied. for (Int32 z = start.Z; z <= end.Z; ++z) { for (Int32 y = start.Y; y <= end.Y; ++y) { for (Int32 x = start.X; x <= end.X; ++x) { byte value = volume.GetVoxel(x, y, z); // If a voxel has a '1' then it's unused volume space, if it is '0' it is empty, // and if anything else, it has a box in it already. if (value != 1) return false; } } } return true; }
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; }
protected static float MeasureUnboxedVoxels(VoxelField volume) { int unboxedVoxels = 0; int totalvoxels = 0; for (Int32 x = 0; x < volume.VoxelSize.X; ++x) { for (Int32 y = 0; y < volume.VoxelSize.Y; ++y) { for (Int32 z = 0; z < volume.VoxelSize.Z; ++z) { byte value = volume.GetVoxel(x, y, z); if (value == 1) unboxedVoxels++; if (value != 0) totalvoxels++; } } } return unboxedVoxels / (float)totalvoxels; }