protected TreeNodeProcessor(string dataPath, TreeImportSettings settings, TreeImportData importData) { Settings = settings; this.dataPath = dataPath; tmpFolderPath = Path.Combine(dataPath, "tmp"); stride = UnsafeUtility.SizeOf <PointCloudPoint>(); ChildNodeRecords = new List <NodeRecord>(); inputBuffer = new PointCloudPoint[settings.chunkSize]; if (settings.sampling == TreeImportSettings.SamplingMethod.CellCenter) { PointCollection = new CellCenterPointCollection(); VolatilePointCollection = new CellCenterPointCollection(); } else { PointCollection = new PoissonDiskPointCollection(); VolatilePointCollection = new CellCenterPointCollection(); } PointCollection.Initialize(settings, importData); VolatilePointCollection.Initialize(settings, importData); }
public OctreeNodeProcessor(string dataPath, TreeImportSettings settings, TreeImportData importData) : base(dataPath, settings, importData) { ChildBuffers = new PointCloudPoint[ChildCount][]; for (var i = 0; i < ChildCount; ++i) { ChildBuffers[i] = new PointCloudPoint[settings.chunkSize]; } ChildCounts = new int[ChildCount]; ChildFileCounts = new int[ChildCount]; }
public MeshBuilder(string dataPath, TreeImportSettings settings, TreeImportData importData) { this.dataPath = dataPath; this.importData = importData; this.settings = settings; tmpDataPath = Path.Combine(dataPath, "tmp"); voxelData = new MeshGenerationData(); for (var i = 1; i < LutOffsets.Length; ++i) { neighborData.Add(LutOffsets[i], new MeshGenerationData()); } }
/// <summary> /// Dispatches mesh generation process on all available worker threads. /// </summary> /// <param name="importData">Import data generated during preprocessing.</param> /// <returns>True if mesh build succeeded, false otherwise.</returns> private bool GenerateMeshes(TreeImportData importData) { var cancelled = false; EditorUtility.DisplayProgressBar("Mesh generation", "Preparing for mesh generation...", 0f); // meshBuilders = new MeshBuilder[Settings.threadCount]; for (var i = 0; i < Settings.threadCount; ++i) { processors[i] = new MeshBuilder(outputPath, Settings, importData); threads[i] = new Thread(processors[i].StartWork); threads[i].Start(); } var meshesToDo = Directory.GetFiles(outputTmpPath, "*.meshdata"); var meshesCount = meshesToDo.Length; foreach (var fileName in meshesToDo) { var id = Path.GetFileNameWithoutExtension(fileName); queue.Enqueue(nodeRecords[id]); } while (GenerateMeshLoop(out var busyThreadCount, out var voxelsDone)) { var title = $"Generating meshes ({busyThreadCount.ToString()}/{Settings.threadCount.ToString()} threads in use)"; // var message = $"{doneCount.ToString()}/{meshesCount.ToString()} meshes"; var message = $"{voxelsDone.ToString()} voxels processed"; // var progress = (float) doneCount / meshesCount; if (EditorUtility.DisplayCancelableProgressBar(title, message, 0f)) { cancelled = true; break; } Thread.Sleep(20); } return(!cancelled); }
/// <summary> /// Starts tree building process with given settings. /// </summary> public static bool BuildNodeTree(TreeImportSettings settings) { var processors = new List <PointProcessor>(); foreach (var inputFile in settings.inputFiles) { var processor = CreateProcessor(Utility.GetFullPath(inputFile)); if (processor != null) { processors.Add(processor); } } if (processors.Count == 0) { Debug.LogError("All of given point cloud files are invalid or unsupported."); return(false); } var bounds = CalculateBounds(processors); var transformationData = new TransformationData(bounds, settings); var unityBounds = bounds.GetUnityBounds(settings); var transform = transformationData.TransformationMatrix; unityBounds.center = transform.MultiplyPoint3x4(unityBounds.center); unityBounds.extents = transform.MultiplyVector(unityBounds.extents); TreeImportData importData = null; if (settings.generateMesh && settings.roadOnlyMesh) { var histogram = GenerateHistogram(processors, bounds); importData = new TreeImportData(unityBounds, histogram); } else { importData = new TreeImportData(unityBounds); } NodeProcessorDispatcher dispatcher; var fullOutputPath = Utility.GetFullPath(settings.outputPath); try { EditorUtility.DisplayProgressBar("Creating dispatcher", "Preparing target directory...", 0f); dispatcher = new NodeProcessorDispatcher(fullOutputPath, settings); } finally { EditorUtility.ClearProgressBar(); } foreach (var processor in processors) { if (!processor.ConvertPoints(dispatcher, transformationData)) { Debug.Log("Import cancelled."); return(false); } } if (dispatcher.ProcessPoints(importData)) { dispatcher.GetPointCountResults(out var total, out var used, out var discarded); Debug.Log($"Octree build finished successfully.\n" + $"Used points: {used}/{total} ({discarded} discarded on low tree levels)"); return(true); } else { Debug.Log("Octree build failed."); return(false); } }
///<inheritdoc/> public void Initialize(TreeImportSettings treeSettings, TreeImportData importData) { rootBounds = importData.Bounds; settings = treeSettings; cellsPerAxis = new int[3]; }
/// <summary> /// Builds tree of all points previously registered through <see cref="AddChunk"/> and <see cref="AddPoint"/> methods. Stores results on disk. /// </summary> /// <param name="importData">Import data generated during preprocessing.</param> /// <returns>True if tree build succeeded, false otherwise.</returns> public bool ProcessPoints(TreeImportData importData) { // Make sure all points are flushed to disk - buffers are ignored during build if (pointCount > 0) { FlushTmpFile(points, 0, pointCount); pointCount = 0; } processors = new ParallelProcessor[Settings.threadCount]; threads = new Thread[Settings.threadCount]; var rootNode = Settings.treeType == TreeType.Octree ? new OctreeNodeRecord(TreeUtility.RootNodeIdentifier, importData.Bounds, 0) : new QuadtreeNodeRecord(TreeUtility.RootNodeIdentifier, importData.Bounds, 0) as NodeRecord; nodeRecords.Add(rootNode.Identifier, rootNode); var cancelled = false; void StopProcessorsIfRunning() { if (processors != null) { foreach (var processor in processors) { processor.StopWork(); } foreach (var thread in threads) { thread.Join(); } } } try { EditorUtility.DisplayProgressBar("Starting threads", "Allocating memory...", 0f); // This buffer is no loner needed - clear reference and let GC free the memory PublicMaxSizeBuffer = null; GC.Collect(); // Start one processor on each of the requested threads for (var i = 0; i < Settings.threadCount; ++i) { if (Settings.treeType == TreeType.Octree) { processors[i] = new OctreeNodeProcessor(outputPath, Settings, importData); } else { processors[i] = new QuadtreeNodeProcessor(outputPath, Settings, importData); } threads[i] = new Thread(processors[i].StartWork); threads[i].Start(); } // Assign root processing to first worker thread processors[0].AssignWork(rootNode); // Lock main thread here until either all work is finished or user cancels the process while (BuildLoop(out var busyThreadCount, out var finishedPointCount)) { var title = $"Building tree ({busyThreadCount.ToString()}/{Settings.threadCount.ToString()} threads in use)"; var message = $"{finishedPointCount.ToString()}/{totalPointsCount.ToString()} points"; var progress = (float)finishedPointCount / totalPointsCount; if (EditorUtility.DisplayCancelableProgressBar(title, message, progress)) { cancelled = true; break; } Thread.Sleep(20); } if (!cancelled) { foreach (var processor in processors) { discardedPointsCount += ((TreeNodeProcessor)processor).DiscardedPoints; } // Generate meshes - stop current work on threads, then restart with mesh builders if (Settings.generateMesh) { StopProcessorsIfRunning(); if (!GenerateMeshes(importData)) { cancelled = true; } } } // Tree build succeeded - finalize if (!cancelled) { FinalizeBuild(); } } finally { // Whether process finishes, crashes, or is cancelled, stop worker threads and clear progress bar EditorUtility.ClearProgressBar(); StopProcessorsIfRunning(); processors = null; threads = null; Directory.Delete(outputTmpPath, true); GC.Collect(); } return(!cancelled); }