public VoxelizationOutput Voxelize(VoxelizationInput input, Action<VoxelizationProgress> progress) { // Setup VBO state GL.EnableClientState(ArrayCap.VertexArray); GL.EnableClientState(ArrayCap.IndexArray); m_input = input; VoxelizationOutput output = new VoxelizationOutput(); output.Octree = input.Octree; VoxelizationProgress vp = new VoxelizationProgress(); vp.Status = "Voxelizing mesh with " + input.Octree.MaxLevels + " subdivision levels"; progress(vp); GL.PushAttrib(AttribMask.AllAttribBits); for (int i = 0; i <= input.Octree.MaxLevels; i++) { vp.Progress = (i / (input.Octree.MaxLevels + 1.0f)); vp.Status = "Voxelizing octree level " + i; progress(vp); RecursiveSolveStatus(input.Octree.Root, i); } GL.PopAttrib(); vp.Progress = 1; vp.Status = "Done voxelizing mesh"; progress(vp); return output; }
public static void Run(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); ProgramOptions options = new ProgramOptions(); string settingPath = Path.Combine(Path.GetDirectoryName(Application.ExecutablePath), "Oxel.Settings.xml"); VoxelizationInput input = VoxelizationInput.Load(settingPath); if (input == null) input = new VoxelizationInput(); if (args.Contains("-c")) { // Make sure user can see console output AttachConsole(PARENT_PROCESS_ID); input.Clone(options); if (!CommandLineParser.Parse<ProgramOptions>(args, ref options)) return; options.Clone(input); } else { CommandLineParser.Parse<VoxelizationInput>(args, ref input); } if (options.UseCommandLine) { Logger.IsCommandLine = true; Operations operations = new Operations(); operations.Initialize(input); operations.Open(options.InputMesh, input.WindingOrder); WaitHandle waitHandle = operations.GenerateOccluder((VoxelizationProgress vp) => { string coverage = String.Format("Volume Coverage : {0,5:0.##}%", (100 * vp.VolumeCoverage)) + " " + String.Format("Silhouette Coverage : {0,5:0.##}%", (100 * vp.SilhouetteCoverage)); if (!String.IsNullOrEmpty(vp.Status)) Console.WriteLine(vp.Status + "\r\n"); Console.WriteLine(coverage); }, new Action(() => { })); waitHandle.WaitOne(); operations.Save(options.OutputMesh); } else { using (MainWindow window = new MainWindow(input)) { window.ShowDialog(); } } }
public static Mesh Filter(VoxelizationInput input, Mesh mesh) { // Remove the top and bottom polygons if (input.RemoveTop || input.RemoveBottom) { Vector3 upAxis, downAxis; if (input.UpAxis == UpAxis.Y) { upAxis = Vector3.UnitY; downAxis = -Vector3.UnitY; } else // if (input.UpAxis == UpAxis.Z) { upAxis = Vector3.UnitZ; downAxis = -Vector3.UnitZ; } List<Triangle> filteredTrianlges = new List<Triangle>(); Triangle[] triangles = Triangle.ToTriangleArray(mesh.Indicies, mesh.Vertices); foreach (Triangle t in triangles) { Plane p = t.Plane; Vector3 normal = t.NormalCounterClockwise(); // Remove all top polygon if (input.RemoveTop) { if (Vector3Ex.AlmostEquals(ref normal, ref upAxis)) continue; } // Remove all bottom polygons that are with-in one voxel of the mesh bounds. if (input.RemoveBottom) { if (Vector3Ex.AlmostEquals(ref normal, ref downAxis)) { Vector3 closestPoint = input.Octree.MeshBounds.ClosestPointOnSurface(t.Center); float distance = (t.Center - closestPoint).Length; if (distance <= input.Octree.SmallestVoxelSideLength) continue; } } filteredTrianlges.Add(t); } Triangle.FromTriangleArray(filteredTrianlges, out mesh.Indicies, out mesh.Vertices); } return mesh; }
public MainWindow(VoxelizationInput input) { InitializeComponent(); LinkLabel label = new LinkLabel(); label.Text = "Bug/Feature?"; label.BackColor = Color.Transparent; label.LinkColor = Color.Blue; label.ActiveLinkColor = Color.Blue; label.DisabledLinkColor = Color.Blue; label.VisitedLinkColor = Color.Blue; label.LinkClicked += (s, e) => { Process.Start("mailto:[email protected]?subject=[Oxel] Bug/Feature"); }; ToolStripControlHost host = new ToolStripControlHost(label); host.Alignment = ToolStripItemAlignment.Right; m_menu.SuspendLayout(); m_menu.Items.Add(host); m_menu.ResumeLayout(true); m_gl = new GLControl(new GraphicsMode(32, 24, 8)); m_gl.BackColor = System.Drawing.Color.Black; m_gl.Dock = System.Windows.Forms.DockStyle.Fill; m_gl.Location = new System.Drawing.Point(0, 0); m_gl.Name = "m_gl"; m_gl.Size = new System.Drawing.Size(716, 516); m_gl.TabIndex = 2; m_gl.VSync = false; m_gl.Load += new System.EventHandler(this.m_gl_Load); m_gl.Paint += new System.Windows.Forms.PaintEventHandler(this.m_gl_Paint); m_gl.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.m_gl_KeyPress); m_gl.MouseDown += new System.Windows.Forms.MouseEventHandler(this.m_gl_MouseDown); m_gl.MouseMove += new System.Windows.Forms.MouseEventHandler(this.m_gl_MouseMove); m_gl.MouseUp += new System.Windows.Forms.MouseEventHandler(this.m_gl_MouseUp); m_gl.MouseWheel += new MouseEventHandler(m_gl_MouseWheel); m_gl.Resize += new System.EventHandler(this.m_gl_Resize); this.splitContainer1.Panel1.Controls.Add(this.m_gl); m_operations = new Operations(); m_operations.Initialize(input); m_propertyGrid.SelectedObject = m_operations.Input; m_operations.Input.PropertyChanged += new PropertyChangedEventHandler(vp_PropertyChanged); }
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; }
long MeasureOccluderOcclusion(SilhouetteOcclusionValidator sov, VoxelizationInput input, List<AABBi> occluderBounds) { Mesh mesh = BuildMeshFromBoxes(input, occluderBounds); RenderableMesh renderable = new RenderableMesh(mesh, true); long sideCoverage, topCoverage; sov.ComputeCoverage(renderable, input.Octree.MeshBounds, out sideCoverage, out topCoverage); renderable.Dispose(); return sideCoverage + topCoverage; }
List<AABBi> GetRelevantOccluders(VoxelizationInput input, List<Occluder> occluders) { var occluderBounds = from occluder in occluders where occluder.DeltaOcclusion > input.MinimumOcclusion orderby occluder.DeltaOcclusion descending select occluder.Bounds; return occluderBounds.ToList(); }
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; }
Mesh BuildMeshFromBoxes(VoxelizationInput input, List<AABBi> boxes) { // Build the mesh, this will also remove all false triangle loops and collinear point triangles. Vector3 deltaP = new Vector3((float)input.Octree.SmallestVoxelSideLength, (float)input.Octree.SmallestVoxelSideLength, (float)input.Octree.SmallestVoxelSideLength); return MeshBuilder.BuildMesh(input.Octree.VoxelBounds, deltaP, boxes); }
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; }
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; }