Cogs.Core
SparseBuildOctreeCommand.cpp
1#include "SparseBuildOctreeCommand.h"
2
3#include "Context.h"
4
5#include "Resources/ModelManager.h"
6#include "Resources/MeshManager.h"
7#include "Resources/Buffer.h"
8#include "Resources/MaterialManager.h"
9
10#include "Scene/GetBounds.h"
11
12#include "Services/Variables.h"
13
14#include "Editor.h"
15#include "Batch.h"
16
17#include "Serialization/JsonParser.h"
18#include "Serialization/ModelWriter.h"
19
20#include "SparseBuildOctree.h"
21#include "AssetStatsCommand.h"
22#include "Utilities/Parallel.h"
23
24#include "Foundation/Geometry/BoundingBox.hpp"
25#include "Foundation/Logging/Logger.h"
26#include "Foundation/Memory/MemoryBuffer.h"
27#include "Foundation/Platform/IO.h"
28#include "Foundation/Platform/Timer.h"
29
30#include "zstd.h"
31
32#include "glm/gtx/compatibility.hpp"
33#include "glm/gtx/component_wise.hpp"
34#include "glm/gtx/transform.hpp"
35
36#define _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
37#include "rapidjson/stringbuffer.h"
38#include "rapidjson/prettywriter.h"
39#undef _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING
40
41#include <fstream>
42#include <regex>
43#include <span>
44
45using namespace Cogs;
46using namespace Cogs::Core;
47using namespace Cogs::Geometry;
48
49namespace
50{
51 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("SparseBuildOctree");
52
55 template<typename T>
56 void setIfNotSet(Cogs::Core::ModelLoadInfo& loadInfo, const Cogs::StringView& key, const T& value)
57 {
58 if (!loadInfo.properties->hasProperty(key)) {
59 loadInfo.properties->addProperty(key, value);
60 }
61 }
62 template<>
63 void setIfNotSet(Cogs::Core::ModelLoadInfo& loadInfo, const Cogs::StringView& key, const Cogs::StringView& value)
64 {
65 if (!value.empty() && !loadInfo.properties->hasProperty(key)) {
66 loadInfo.properties->addProperty(key, value);
67 }
68 }
69 template<>
70 void setIfNotSet(Cogs::Core::ModelLoadInfo& loadInfo, const Cogs::StringView& key, const std::string& value)
71 {
72 setIfNotSet(loadInfo, key, Cogs::StringView(value));
73 }
74
75
76 void parameterizationTriggerload(Context* context, SparseOctree::Parameterization& p, const SparseOctree::OctreeInfo& info)
77 {
78 ModelLoadInfo& loadInfo = *context->modelManager->createLoadInfo();
79 loadInfo.resourceId = (uint32_t)-1;
80 loadInfo.resourcePath = info.source;
81 loadInfo.loadFlags = ResourceLoadFlags::ForceUnique;
82 loadInfo.modelFlags = ModelLoadFlags::ForceUnique;
83
84 if (context->variables->get("resources.models.autoReload", false)) {
85 loadInfo.loadFlags |= ResourceLoadFlags::AutoReload;
86 }
87
88 // Store loadInfo properties.
89 // 1. Copy all properties from from the Batch-file "properties" section
90 // "properties": { "keepList": "Path.tags.json", etc.
91 // 2. Set renamed properties parsed from the Batch-file "properties" section,
92 // unless the output property has been set in the Batch property list.
93 // Allows migrating to new batch format setting output properties directly,
94 // e.g. Batch properties: { "rvm.tagSimplify.keepList": "Path.tags.json" ,,)
95 // Batch property "keepList" saved as: "rvm.tagSimplify.keepList"
96 // 3. Later if "rvm.tasks" is defined, (defining list of RVMParser tasks with properties),
97 // Overwrite any properties defined in RVMParser tasks defintion
98 // e.g. "rvm.tasks": { "rvm.tagSimplify.keepList": "My/Custom/Path.tag.json",
99 // This allows a user creating a Kognitwin import definition where only most of the import
100 // properties are pre-defined to pass a new "rvm.tasks" def, with modified params,
101 // like: "rvm.tessellate.origin": [Custom X/Y/Z]
102 //
103 loadInfo.properties = std::make_unique<PropertyStore>();
104 *loadInfo.properties = info.properties;
105
106 ::setIfNotSet(loadInfo, "rvm.tessellate.tolerance", p.tolerance);
107 ::setIfNotSet(loadInfo, "rvm.tessellate.remesh", p.remesh);
108 ::setIfNotSet(loadInfo, "rvm.tessellate.remeshGridSpacing", p.remeshGridSpacing);
109 ::setIfNotSet(loadInfo, "rvm.tessellate.remeshVolumeBlur", p.remeshVolumeBlur);
110 ::setIfNotSet(loadInfo, "rvm.tessellate.remeshTriSoupBlur", p.remeshTriSoupBlur);
111 ::setIfNotSet(loadInfo, "rvm.tessellate.remeshSimplification", p.remeshSimplification);
112 ::setIfNotSet(loadInfo, "rvm.tessellate.featureAngle", p.featureAngle);
113 ::setIfNotSet(loadInfo, "rvm.tessellate.cullDiameter", p.cullDiameter);
114 ::setIfNotSet(loadInfo, "rvm.tessellate.cullGeometryThreshold", p.cullGeometry);
115 ::setIfNotSet(loadInfo, "rvm.tessellate.cullLeafThreshold", p.cullLeaf);
116 ::setIfNotSet(loadInfo, "rvm.tessellate.simplificationFactor", p.simplificationFactor);
117 ::setIfNotSet(loadInfo, "rvm.tessellate.simplificationMode", p.simplificationMode);
118
119 if (info.keepList.size()) {
120 ::setIfNotSet(loadInfo, "rvm.tagSimplify.keepList", info.keepList);
121 };
122
123 // All loaded triangle meshes are merged together, so no point in setting up
124 // hierarchy etc.
125 ::setIfNotSet(loadInfo, "rvm.export.noHierarchy", true);
126 ::setIfNotSet(loadInfo, "rvm.export.discardLines", true);
127 ::setIfNotSet(loadInfo, "rvm.export.doNotMergeGeometries", false);
128
129 ::setIfNotSet(loadInfo, "rvm.export.idInTexCoord", true);
130
131
132 ::setIfNotSet(loadInfo, "rvm.filter.rootEndsWith", info.filter_rootEndsWith);
133 ::setIfNotSet(loadInfo, "rvm.filter.rootIncludes", info.filter_rootIncludes);
134 ::setIfNotSet(loadInfo, "rvm.filter.rootPattern", info.filter_rootPattern);
135 ::setIfNotSet(loadInfo, "rvm.filter.attributeName", info.filter_attributeName);
136 ::setIfNotSet(loadInfo, "rvm.filter.attributeValue", info.filter_attributeValue);
137
138 if (!info.filter_excludeNodes.empty()) {
139 ::setIfNotSet(loadInfo, "rvm.filter.numExcludeNodes", std::to_string(info.filter_excludeNodes.size()));
140 for (size_t i = 1; i <= info.filter_excludeNodes.size(); ++i)
141 ::setIfNotSet(loadInfo, "rvm.filter.excludeNode" + std::to_string(i), info.filter_excludeNodes[i - 1]);
142 }
143
144 // Save RVM Tasks defintions. Runs in this order.
145 ::setIfNotSet(loadInfo, "rvm.filter.filter", info.taskFilter);
146
147 p.model = context->modelManager->loadResource(&loadInfo);
148
149 LOG_DEBUG(logger, "Approximate command line: %s"
150 " --rvm.tessellate.tolerance=%.2f"
151 " --rvm.tessellate.remesh=%.2f"
152 " --rvm.tessellate.remeshGridSpacing=%.4f"
153 " --rvm.tessellate.remeshVolumeBlur=%d"
154 " --rvm.tessellate.remeshTriSoupBlur=%d"
155 " --rvm.tessellate.remeshSimplification=%.2f"
156 " --rvm.tessellate.featureAngle=%.2f"
157 " --rvm.tessellate.cullDiameter=%.2f"
158 " --rvm.tessellate.cullGeometryThreshold=%.2f"
159 " --rvm.tessellate.cullLeafThreshold=%.2f"
160 " --rvm.tessellate.simplificationFactor=%.2f"
161 " --rvm.tessellate.simplificationMode=%d"
162 ,
163 info.source.c_str(),
164 p.tolerance,
165 p.remesh,
166 p.remeshGridSpacing,
167 p.remeshVolumeBlur,
168 p.remeshTriSoupBlur,
169 p.remeshSimplification,
170 p.featureAngle,
171 p.cullDiameter,
172 p.cullGeometry,
173 p.cullLeaf,
174 p.simplificationFactor,
175 p.simplificationMode);
176 }
177
178 void parameterizationWaitForLoad(Context* context, SparseOctree::Parameterization& p)
179 {
180 Timer timer = Timer::startNew();
181 do {
182 Cogs::Core::runFrames(context, 2);
183 p.isLoaded = ((p.model->getFlags() & (ResourceFlags::Loaded | ResourceFlags::FailedLoad)) != 0);
184 if (10.0 * 60.0 * 60.0 < timer.elapsedSeconds()) {
185 LOG_FATAL(logger, "Failed to load model in 10 hours, breaking out.");
186 break;
187 }
188 }
189 while (p.isLoaded == false);
190 }
191
192 void levelInitCells(Context* /*context*/, SparseOctree::Grid& grid, const SparseOctree::Octree& octree, const size_t level)
193 {
194 const SparseOctree::SceneInfo& scene = octree.scenes[level];
195 const Geometry::BoundingBox& bounds = scene.bounds;
196 size_t xSize = octree.grids[level].dimensions.x;
197 size_t ySize = octree.grids[level].dimensions.y;
198 size_t zSize = octree.grids[level].dimensions.z;
199
200 LOG_DEBUG(logger, " +- dimensions: [%zu %zu %zu]", xSize, ySize, zSize);
201
202 if (isEmpty(bounds)) {
203 LOG_DEBUG(logger, " +- bounding box: <empty>");
204 return;
205 }
206 LOG_DEBUG(logger, " +- bounding box: [%.2f %.2f %.2f] x [%.2f %.2f %.2f]",
207 bounds.min.x, bounds.min.y, bounds.min.z,
208 bounds.max.x, bounds.max.y, bounds.max.z);
209 glm::vec3 size = bounds.max - bounds.min;
210 grid.origin = bounds.min;
211 grid.cellSize = size / glm::vec3(xSize, ySize, zSize);
212 }
213
214 SparseOctree::CellIndex createCell(SparseOctree::Grid& grid, SparseOctree::CellMapKey key, size_t i, size_t j, size_t k)
215 {
216 SparseOctree::CellIndex cellIndex = grid.cellStore.size();
217
218 grid.cellMap.emplace(key, cellIndex);
219
220 SparseOctree::Cell& cell = grid.cellStore.emplace_back();
221 cell.x = static_cast<uint32_t>(i);
222 cell.y = static_cast<uint32_t>(j);
223 cell.z = static_cast<uint32_t>(k);
224 return cellIndex;
225 }
226
227 void levelFilterEntities(Context* /*context*/, SparseOctree::Grid& grid, SparseOctree::Octree& octree, size_t level) {
228
229 const SparseOctree::SceneInfo& scene = octree.scenes[level];
230 const SparseOctree::LevelInput& input = octree.inputs[level];
231
232 const float cullingThreshold = std::min(scene.maxGeometrySize, input.cullingThreshold);
233
234 float maxDensity = 0.0f;
235 float minDensity = 1000000000.0f;
236 size_t outliers = 0;
237
238 // Filter out entitites that we should
239 std::vector<const SparseOctree::EntityRef*> filteredEntities;
240 filteredEntities.reserve(scene.entities.size());
241
242 struct {
243 size_t explicitlySkipped = 0;
244 size_t missingMesh = 0;
245 size_t smallerThanCullingThreshold = 0;
246 size_t biggerThanMaxSize = 0;
247 } skipStats;
248
249 for (auto& e : scene.entities) {
250 if (e.skip) { skipStats.explicitlySkipped++; continue; }
251 if (!e.mesh) { skipStats.missingMesh++; continue; }
252 if (e.maxDim < cullingThreshold) { skipStats.smallerThanCullingThreshold++; continue; }
253 if (e.maxDim > 10000000) { skipStats.biggerThanMaxSize++; continue; }
254
255 assert(!glm::any(glm::isnan(e.box.min)) && !glm::any(glm::isnan(e.box.max)) && "NaN in our boxes.");
256
257 if (octree.info.discardOutliers) {
258 const size_t count = e.mesh->getCount();
259 const glm::vec3 diag = e.box.max - e.box.min;
260 const float volume = diag.x * diag.y * diag.z;
261 const float density = (float)count / volume;
262
263 if (count > size_t(octree.info.outlierMinVertexCount) && density > octree.info.outlierMinVertexDensity && volume < octree.info.outlierMaxVolume) {
264 LOG_DEBUG(logger, "Count: %zu", count);
265 LOG_DEBUG(logger, "Volume: %f", volume);
266 LOG_DEBUG(logger, "Density: %f", density);
267
268 ++outliers;
269
270 maxDensity = std::max(maxDensity, density);
271 minDensity = std::min(minDensity, density);
272
273 if (octree.info.outputOutliers) {
274 filteredEntities.push_back(&e);
275 }
276 }
277 else if (!octree.info.outputOutliers) {
278 filteredEntities.push_back(&e);
279 }
280 }
281 else {
282 filteredEntities.push_back(&e);
283 }
284 }
285
286 if (outliers) {
287 LOG_INFO(logger, "Outliers: %zu", outliers);
288 LOG_INFO(logger, "Max density: %f", maxDensity);
289 LOG_INFO(logger, "Min density: %f", minDensity);
290 }
291
292
293
294 size_t meshCellOverlap = 0;
295 const Geometry::BoundingBox bounds = scene.bounds;
296 for (const SparseOctree::EntityRef* e : filteredEntities) {
297
298 const Geometry::BoundingBox& bbox = e->box;
299 assert(e->box.min.x <= e->box.max.x && e->box.min.y <= e->box.max.y && e->box.min.z <= e->box.max.z);
300
301 glm::vec3 flo = glm::max(glm::vec3(0.f), glm::floor((bbox.min - bounds.min) / grid.cellSize));
302 glm::vec3 fhi = glm::max(glm::vec3(0.f), glm::floor((bbox.max - bounds.min) / grid.cellSize));
303
304 glm::uvec3 lo = glm::uvec3(flo);
305 glm::uvec3 hi = glm::min(glm::uvec3(fhi) + glm::uvec3(1), grid.dimensions);
306
307 for (size_t k = lo.z; k < hi.z; k++) {
308 for (size_t j = lo.y; j < hi.y; j++) {
309 for (size_t i = lo.x; i < hi.x; i++) {
310
311 assert(i < grid.dimensions.x && j < grid.dimensions.y && k < grid.dimensions.z);
312
313 SparseOctree::CellIndex cellIndex;
314 SparseOctree::CellMapKey key = grid.cellMapKey(i, j, k);
315 if (auto it = grid.cellMap.find(key); it != grid.cellMap.end()) {
316 cellIndex = it->second;
317 SparseOctree::Cell& cell = grid.cellStore[cellIndex];
318 assert(cell.x == i && cell.y == j && cell.z == k);
319 }
320 else {
321 // Create new cell
322 cellIndex = createCell(grid, key, i, j, k);
323
324 // Make sure that all ancestors exist so cells are always connected upwards
325 size_t kk = k;
326 size_t jj = j;
327 size_t ii = i;
328 for (size_t l = level + 1; l < octree.numLevels; l++) {
329 kk /= 2;
330 jj /= 2;
331 ii /= 2;
332 SparseOctree::Grid& ancestorGrid = octree.grids[l];
333 SparseOctree::CellMapKey ancestorKey = ancestorGrid.cellMapKey(ii, jj, kk);
334 if (auto at = ancestorGrid.cellMap.find(ancestorKey); at != ancestorGrid.cellMap.end()) {
335 break;
336 }
337 else {
338 SparseOctree::CellIndex ancestorIndex = createCell(ancestorGrid, ancestorKey, ii, jj, kk);
339 SparseOctree::Cell& ancestorCell = ancestorGrid.cellStore[ancestorIndex];
340 assert(ancestorCell.x == ii && ancestorCell.y == jj && ancestorCell.z == kk);
341 }
342 }
343
344 }
345
346 SparseOctree::Cell& cell = grid.cellStore[cellIndex];
347 assert(cell.x == i && cell.y == j && cell.z == k);
348
349 cell.touches.push_back(e->index);
350 meshCellOverlap++;
351 }
352 }
353 }
354 }
355
356 for (SparseOctree::Cell& cell : grid.cellStore) {
357 cell.bbox.min = grid.origin + grid.cellSize * glm::vec3(cell.x, cell.y, cell.z);
358 cell.bbox.max = grid.origin + grid.cellSize * glm::vec3(cell.x + 1, cell.y + 1, cell.z + 1);
359 }
360
361 LOG_DEBUG(logger, "FilterEntities: initial=%zu, survived=%zu, explicitlySkipped=%zu, missingMesh=%zu smallerThanCullingThreshold=%zu, biggerThanMaxSize=%zu",
362 scene.entities.size(), filteredEntities.size(),
363 skipStats.explicitlySkipped, skipStats.missingMesh, skipStats.smallerThanCullingThreshold, skipStats.biggerThanMaxSize);
364
365 LOG_DEBUG(logger, "FilterEntities: meshCellOverlap=%zu", meshCellOverlap);
366 }
367
368 void levelGetModelAndBounds(SparseOctree::Octree& octree, SparseOctree::SceneInfo& scene, float maxTolerance)
369 {
370 Model& model = *scene.model.resolve();
371
372 scene.entities.resize(model.parts.size());
373
374 scene.bounds = Geometry::BoundingBox();
375 for (size_t i = 0; i < model.parts.size(); ++i) {
376 const ModelPart& part = model.parts[i];
377 SparseOctree::EntityRef& e = scene.entities[i];
378
379 e.index = (uint32_t)i;
380 e.parent = part.parentIndex;
381
382 if (part.nameIndex != (uint32_t)-1) {
383 auto name = model.getPartName(part);
384
385 if (octree.info.skipPattern.size() && name.find(octree.info.skipPattern) != StringView::NoPosition) {
386 LOG_INFO(logger, "Skipped object %.*s", StringViewFormat(name));
387 e.skip = true;
388 }
389 }
390
391 if (part.meshIndex != (uint32_t)-1) {
392 e.mesh = model.meshes[part.meshIndex];
393
394 if (e.mesh->primitiveType != PrimitiveType::TriangleList) {
395 continue;
396 }
397
398 // Skip degenerate meshes.
399 if (e.mesh->getCount() < 3) {
400 LOG_ERROR(logger, "Skipped part %zu with degenerate mesh; %u count.", i, e.mesh->getCount());
401 e.mesh = {};
402 continue;
403 }
404
405 if (part.materialIndex != NoIndex) {
406 e.material = model.materials[part.materialIndex];
407 }
408 e.startIndex = part.startIndex;
409 e.vertexCount = part.vertexCount;
410
411 if (part.boundsIndex != (uint32_t)-1) {
412 e.box = model.bounds[part.boundsIndex];
413 }
414 else {
415 if (isEmpty(e.mesh->boundingBox)) {
416 e.mesh->setBounds(calculateBounds(e.mesh.resolve()));
417 }
418 e.box = e.mesh->boundingBox;
419
420 if (octree.info.useMinBounds && glm::any(glm::lessThan(e.box.min, octree.info.minBounds))) {
421 LOG_ERROR(logger, "Skipped part %zu with invalid min bounds: [ %.3f, %.3f, %.3f ].", i, e.box.min.x, e.box.min.y, e.box.min.z);
422 e.mesh = {};
423 continue;
424 }
425
426 if (octree.info.useMaxBounds && glm::any(glm::greaterThan(e.box.max, octree.info.maxBounds))) {
427 LOG_ERROR(logger, "Skipped part %zu with invalid max bounds: [ %.3f, %.3f, %.3f ].", i, e.box.max.x, e.box.max.y, e.box.max.z);
428 e.mesh = {};
429 continue;
430 }
431 }
432 scene.bounds += e.box;
433 e.span = e.box.max - e.box.min;
434 e.maxDim = glm::compMax(e.span);
435
436 scene.geometries++;
437 scene.maxGeometrySize = std::max(scene.maxGeometrySize, e.maxDim);
438 }
439 }
440
441 // Remeshing slightly grows tiny shapes, so a coarse level can be bigger than a detailed level.
442 // We adust bounds a bit to accommodate this.
443 if (!isEmpty(scene.bounds)) {
444 scene.bounds.min -= glm::vec3(maxTolerance);
445 scene.bounds.max += glm::vec3(maxTolerance);
446 }
447 }
448
449 bool calculateLevelSizes(SparseOctree::Octree & octree)
450 {
451 if (!octree.scenes[0].geometries) return false;
452
453 const uint32_t maxCells = (uint32_t)std::pow(2, octree.numLevels);
454
455 glm::vec3 size0 = octree.scenes[0].bounds.max - octree.scenes[0].bounds.min;
456
457 glm::uvec3 cells(maxCells); // number of cells in X, Y, Z
458 glm::uvec3 levels(octree.numLevels); // number of lod levels along X, Y, Z
459
460 // Cell size at most detailed level
461 glm::vec3 cellSize(size0.x / cells.x, size0.y / cells.y, size0.z / cells.z);
462
463 // Reduce number of lod-levels in direction if cells are smaller than minCellSize
464 for (uint32_t l = 0; l < octree.numLevels; ++l) {
465 for (uint32_t i = 0; i < 3; ++i) {
466 if (cellSize[i] > octree.minCellSize) continue;
467 --levels[i];
468 cells[i] = (uint32_t)std::pow(2u, levels[i]);
469 cellSize[i] = size0[i] / cells[i];
470 }
471 }
472
473
474 for (uint32_t l = 0; l < octree.numLevels; ++l) {
475 for (uint32_t i = 0; i < 3; ++i) {
476 if (l < (levels[i] - 1) && levels[i] != 0) {
477 assert(l + 1 <= levels[i]);
478 octree.grids[l].dimensions[i] = 1u << (levels[i] - l - 1);
479 } else {
480 octree.grids[l].dimensions[i] = 1;
481 }
482 }
483 }
484
485 assert(octree.inputs.size() >= octree.numLevels && "Invalid inputs.");
486
487
488 return true;
489 }
490
491 Value jsonEncodeMeshReference(MemoryPoolAllocator<CrtAllocator>& alloc, const SparseOctree::OctreeInfo& info, const SparseOctree::MeshRef& meshRef, bool includeBounds)
492 {
493 Value value(kObjectType);
494
495 if (includeBounds && info.embedMeshBounds && !isEmpty(meshRef.bbox)) {
496 Value bbox(kArrayType);
497 bbox.PushBack(meshRef.bbox.min.x, alloc);
498 bbox.PushBack(meshRef.bbox.min.y, alloc);
499 bbox.PushBack(meshRef.bbox.min.z, alloc);
500 bbox.PushBack(meshRef.bbox.max.x, alloc);
501 bbox.PushBack(meshRef.bbox.max.y, alloc);
502 bbox.PushBack(meshRef.bbox.max.z, alloc);
503
504 Value properties(kObjectType);
505 properties.AddMember("bbox", Value(bbox, alloc), alloc);
506
507 value.AddMember("properties", properties, alloc);
508 }
509
510 if (meshRef.part == (uint32_t)-1) {
511 value.AddMember("model", Value(meshRef.fileName.c_str(), alloc), alloc);
512 }
513 else {
514 Value model(kObjectType);
515 model.AddMember("source", Value(meshRef.fileName.c_str(), alloc), alloc);
516 model.AddMember("part", Value(meshRef.part), alloc);
517 value.AddMember("model", model, alloc);
518 }
519 return value;
520 }
521
522 Value jsonEncodeMeshReferences(MemoryPoolAllocator<CrtAllocator>& alloc, const SparseOctree::OctreeInfo& info, const SparseOctree::Cell& cell, bool forceBounds)
523 {
524 // Note: We only try to include the bounding box if there are multiple mesh references. If there is a single
525 // mesh reference, that corresponds to the whole cell (=lod-node), and the asset system already culls for that.
526
527 if (cell.meshRefs.empty()) return Value();
528
529 if (cell.meshRefs.size() == 1) return jsonEncodeMeshReference(alloc, info, cell.meshRefs[0], forceBounds);
530
531 Value value(kArrayType);
532 for (const SparseOctree::MeshRef& meshRef : cell.meshRefs) {
533 value.PushBack(jsonEncodeMeshReference(alloc, info, meshRef, true), alloc);
534 }
535 return value;
536 }
537
538 void jsonWriteAsset(MemoryPoolAllocator<CrtAllocator>& alloc,
539 const SparseOctree::OctreeInfo& info,
540 const std::string& filename,
541 Value root)
542 {
543 Document hDoc(&alloc);
544 hDoc.SetObject();
545 Value entities(kArrayType);
546 entities.PushBack(root, alloc);
547 hDoc.AddMember("entities", entities, alloc);
548
549 StringBuffer buffer;
550
551 if (info.prettyPrint) {
552 PrettyWriter<StringBuffer> writer(buffer);
553 writer.SetIndent(' ', 2);
554 writer.SetMaxDecimalPlaces(4);
555 hDoc.Accept(writer);
556 }
557 else {
558 Writer<StringBuffer> writer(buffer);
559 writer.SetMaxDecimalPlaces(4);
560 hDoc.Accept(writer);
561 }
562
563 if (0 < info.compressionLevel) {
564
565 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
566
567 size_t uncompressedSize = buffer.GetSize();
568 size_t maxSize = ZSTD_compressBound(uncompressedSize);
569 Memory::MemoryBuffer compressed(maxSize);
570
571 size_t result = ZSTD_compressCCtx(zstd_ctx, compressed.data(), maxSize, buffer.GetString(), uncompressedSize, info.compressionLevel);
572 if (ZSTD_isError(result)) {
573 LOG_ERROR(logger, "%s: zstd compression failed: %s", filename.c_str(), ZSTD_getErrorName(result));
574 return;
575 }
576 else if (uncompressedSize <= result) {
577 LOG_WARNING(logger, "%s: compressed size greater or equal to uncompressed size", filename.c_str());
578 }
579 ZSTD_freeCCtx(zstd_ctx);
580
581 std::ofstream file(filename, std::ios::binary);
582 file.write((const char*)compressed.data(), result);
583 file.close();
584 }
585 else {
586 std::ofstream file(filename);
587 file.write(buffer.GetString(), buffer.GetSize());
588 file.close();
589 }
590
591 LOG_INFO(logger, "Wrote asset %s.", filename.c_str());
592 }
593
594}
595
596void Cogs::Core::SparseBuildOctreeCommand::exportAsset(Context * context, SparseOctree::Octree & octree)
597{
598 MemoryPoolAllocator alloc;
599
600 std::vector<std::vector<uint32_t>> weightsPerLevel(octree.numLevels);
601 std::vector<std::vector<Value>> valuesPerLevel(octree.numLevels);
602
603 const SparseOctree::OctreeInfo& info = octree.info;
604
605 std::vector<Geometry::BoundingBox> bounds, childBounds;
606
607 for (size_t level = 0; level < octree.numLevels; ++level) {
608 size_t xSize = octree.grids[level].dimensions.x;
609 size_t ySize = octree.grids[level].dimensions.y;
610 size_t zSize = octree.grids[level].dimensions.z;
611
612 const SparseOctree::Grid& grid = octree.grids[level];
613
614 childBounds.swap(bounds);
615 bounds.resize(grid.cellStore.size());
616
617 if (level == 0) {
618
619 // FIXME: This should really be the mesh bounds
620 for (size_t i = 0, n = bounds.size(); i < n; i++) {
621 bounds[i] = grid.cellStore[i].bbox;
622 }
623 }
624
625 else {
626 const glm::uvec3 childLayerDim = octree.grids[level - 1].dimensions;
627
628 const SparseOctree::Grid& childGrid = octree.grids[level - 1];
629 assert(childBounds.size() == childGrid.cellStore.size());
630
631 for (size_t i = 0, n = bounds.size(); i < n; i++) {
632
633 const SparseOctree::Cell& cell = grid.cellStore[i];
634
635 // Build aggregate bounds that fully enclose child nodes, which
636 // might be bigger than the bounds of the current cell
637 Geometry::BoundingBox box = cell.bbox;
638
639 size_t x0 = 2 * cell.x;
640 size_t x1 = std::min(x0 + 2, size_t(childGrid.dimensions.x));
641
642 size_t y0 = 2 * cell.y;
643 size_t y1 = std::min(y0 + 2, size_t(childGrid.dimensions.y));
644
645 size_t z0 = 2 * cell.z;
646 size_t z1 = std::min(z0 + 2, size_t(childGrid.dimensions.z));
647
648 for (size_t z = z0; z < z1; z++) {
649 for (size_t y = y0; y < y1; y++) {
650 for (size_t x = x0; x < x1; x++) {
651
652 SparseOctree::CellMapKey childKey = childGrid.cellMapKey(x, y, z);
653 if (auto it = childGrid.cellMap.find(childKey); it != childGrid.cellMap.end()) {
654 SparseOctree::CellIndex childIx = it->second;
655 assert(childIx < childBounds.size());
656 box += childBounds[childIx];
657 }
658 }
659 }
660 }
661 bounds[i] = box;
662 }
663 }
664
665
666 std::vector<Value>& values = valuesPerLevel[level];
667 std::vector<uint32_t>& weights = weightsPerLevel[level];
668
669 std::vector<Value>& prevValues = valuesPerLevel[level ? level - 1 : 0];
670 std::vector<uint32_t>& prevWeights = weightsPerLevel[level ? level - 1 : 0];
671
672 glm::uvec3 prevDims = octree.grids[level ? level - 1: level].dimensions;
673
674 const SparseOctree::Grid& prevGrid = octree.grids[level ? level - 1 : 0];
675
676 values.resize(grid.cellStore.size());
677 weights.resize(grid.cellStore.size());
678
679 std::string directory = IO::combine(info.filesDirectory, std::to_string(level));
680 IO::createDirectories(IO::combine(directory, ".cogsbin"));
681
682 for (size_t z = 0; z < zSize; ++z) {
683 for (size_t y = 0; y < ySize; ++y) {
684 for (size_t x = 0; x < xSize; ++x) {
685
686 SparseOctree::CellMapKey key = grid.cellMapKey(x, y, z);
687 auto it = grid.cellMap.find(key);
688 if (it == grid.cellMap.end()) {
689 continue;
690 }
691
692 SparseOctree::CellIndex cellIndex = it->second;
693 const SparseOctree::Cell& cell = grid.cellStore[cellIndex];
694 assert(cell.x == x && cell.y == y && cell.z == z);
695
696 Value nodeValue(kNullType);
697 uint32_t nodeWeight = static_cast<uint32_t>(cell.meshRefs.size());
698
699 if (level == 0 && !cell.meshRefs.empty()) {
700 // Force bounds on level 0 since they are otherwise only culled by the lod node at level 1, covering 2x2x2 of these.
701 nodeValue = jsonEncodeMeshReferences(alloc, info, cell, true);
702 }
703 else if (level != 0) {
704 Value lods(kArrayType);
705 Value lvl0(kArrayType);
706
707 uint32_t xFac = prevDims.x > xSize ? 2 : 1;
708 uint32_t yFac = prevDims.y > ySize ? 2 : 1;
709 uint32_t zFac = prevDims.z > zSize ? 2 : 1;
710
711 for (uint32_t zz = 0; zz < zFac; ++zz) {
712 for (uint32_t yy = 0; yy < yFac; ++yy) {
713 for (uint32_t xx = 0; xx < xFac; ++xx) {
714
715 size_t i = x * xFac + xx;
716 size_t j = y * yFac + yy;
717 size_t k = z * zFac + zz;
718
719 SparseOctree::CellMapKey childKey = prevGrid.cellMapKey(i, j, k);
720 if (auto jt = prevGrid.cellMap.find(childKey); jt != prevGrid.cellMap.end()) {
721
722 Value& v = prevValues[jt->second];
723 if (v.IsArray()) {
724 for (rapidjson::SizeType r = 0; r < v.Size(); r++) {
725 lvl0.PushBack(v[r], alloc);
726 }
727 }
728 else if (!v.IsNull()) {
729 lvl0.PushBack(v, alloc);
730 }
731 nodeWeight += prevWeights[jt->second];
732 }
733 }
734 }
735 }
736 if (cell.meshRefs.empty() && lvl0.Empty()) continue;
737
738 Value lvl1 = jsonEncodeMeshReferences(alloc, info, cell, false);
739
740 glm::vec3 min = bounds[cellIndex].min;
741 glm::vec3 max = bounds[cellIndex].max;
742 Value bbox(kArrayType);
743 bbox.PushBack(Value(min.x), alloc);
744 bbox.PushBack(Value(min.y), alloc);
745 bbox.PushBack(Value(min.z), alloc);
746 bbox.PushBack(Value(max.x), alloc);
747 bbox.PushBack(Value(max.y), alloc);
748 bbox.PushBack(Value(max.z), alloc);
749
750 Value errors(kArrayType);
751 if (octree.inputs[level].threshold < 0) {
752 errors.PushBack(Value(level * 10.0f - 5), alloc);
753 errors.PushBack(Value(level * 10.0f + 5), alloc);
754 }
755 else {
756 errors.PushBack(Value(octree.inputs[level - 1].threshold), alloc);
757 errors.PushBack(Value(octree.inputs[level].threshold), alloc);
758 }
759
760 Value properties(kObjectType);
761 properties.AddMember("bbox", bbox, alloc);
762 properties.AddMember("errors", errors, alloc);
763
764 if (lvl0.Size() == 0) {
765 // Empty array, just encode as empty object
766 lods.PushBack(Value(kObjectType), alloc);
767 }
768 else if (lvl0.Size() == 1) {
769 // Single element array, encode as single object
770 lods.PushBack(lvl0[0], alloc);
771 }
772 else {
773 // Encode as array
774 lods.PushBack(lvl0, alloc);
775 }
776
777 if (lvl1.IsNull()) {
778 // If the cell is empty, encode as empty object
779 lods.PushBack(Value(kObjectType), alloc);
780 }
781 else {
782 lods.PushBack(lvl1, alloc);
783 }
784
785 auto name = std::string("lod_") + std::to_string(level) + ":" + std::to_string(x) + ":" + std::to_string(y);
786
787 nodeValue = Value(kObjectType);
788 nodeValue.AddMember("name", Value(name.c_str(), alloc), alloc);
789 nodeValue.AddMember("type", Value("LodGroup", alloc), alloc);
790 nodeValue.AddMember("properties", properties, alloc);
791 nodeValue.AddMember("lods", lods, alloc);
792 }
793
794 if ((level + 2 < octree.numLevels) && info.maxSubtreeSize && info.maxSubtreeSize < nodeWeight && nodeValue.IsObject()) {
795 const std::string relative = info.name +
796 "_L" + std::to_string(level) +
797 "_" + std::to_string(x) +
798 "_" + std::to_string(y) +
799 "_" + std::to_string(z) +
800 ".asset";
801
802 const std::string filename = IO::combine(info.layerDirectory, relative);
803 jsonWriteAsset(alloc, info, filename, std::move(nodeValue));
804
805 Value asset(kObjectType);
806 asset.AddMember("source", Value(relative.c_str(), alloc), alloc);
807 asset.AddMember("flags", Value("InstantiateOnDemand | RelativePaths", alloc), alloc);
808 nodeValue = Value(kObjectType);
809 nodeValue.AddMember("asset", asset, alloc);
810 nodeWeight = 1;
811 }
812
813 values[cellIndex] = nodeValue;
814 weights[cellIndex] = nodeWeight;
815 }
816 }
817 }
818 }
819
820 const std::string filename = info.assetFileNameStem + ".asset";
821 jsonWriteAsset(alloc, info, filename, std::move(valuesPerLevel[octree.numLevels - 1][0]));
822 if (info.statistics) {
823 AssetStatsCommand::calculateStats(context, properties, filename, info.assetFileNameStem + "_stats.json");
824 }
825}
826
827namespace
828{
830 bool matchFilename2(const std::string name, const std::string pattern)
831 {
832 const auto extension = Cogs::IO::extension(pattern);
833 const auto stem = Cogs::IO::stem(pattern);
834
835 bool extensionMatch = false;
836 auto entryExtension = Cogs::IO::extension(name);
837 auto entryStem = Cogs::IO::stem(name);
838
839 if (extension == "*") {
840 extensionMatch = true;
841 }
842 else if (extension == entryExtension) {
843 extensionMatch = true;
844 }
845
846 bool patternMatch = false;
847
848 if (stem == "*") {
849 patternMatch = true;
850 }
851 else if (stem.find('*') != std::string::npos) {
852 auto stemEx = stem;
853 stemEx.replace(stem.find('*'), 1, ".*");
854
855 std::regex stemRegex(stemEx);
856
857 std::smatch match;
858 std::regex_search(entryStem, match, stemRegex);
859 if (match.size()) patternMatch = true;
860 } else if (entryStem == stem) {
861 patternMatch = true;
862 }
863
864 return extensionMatch && patternMatch;
865 }
866}
867
869{
870
871}
872
873void Cogs::Core::SparseBuildOctreeCommand::undo()
874{
875
876}
877
878namespace
879{
880 std::span<const float> getArrayProp(Cogs::Core::PropertyStore & properties, Cogs::Core::StringRef key)
881 {
882 auto index = properties.findProperty(0, properties.size(), key);
883 return index != (unsigned)-1 ? properties.getFloatArray(index) : std::span<float>{};
884 }
885}
886
887void Cogs::Core::SparseBuildOctreeCommand::applyPost()
888{
889 context->variables->get("resources.globalFrameUpdateQuota")->setInt(0);
890
891 auto source = properties.getProperty("source", StringView());
892 auto pattern = properties.getProperty("pattern", StringView()).to_string();
893 auto prePattern = properties.getProperty("prePattern", StringView()).to_string();
894 if (!prePattern.empty()) {
895 pattern = prePattern + pattern;
896 }
897 auto postPattern = properties.getProperty("postPattern", StringView()).to_string();
898 if (!postPattern.empty()) {
899 pattern = pattern + postPattern;
900 }
901 auto directory = IO::absolute(source.to_string());
902 auto outDirectory = properties.getProperty("destination", StringView());
903 const bool forceUpdate = properties.getProperty("forceUpdate", false);
904 auto assetFileName = properties.getProperty("fileName", StringView("Out.asset")).to_string();
905
906 std::vector<std::string> sources;
907
908 LOG_DEBUG(logger, "Matching files for pattern %s in %.*s:", pattern.c_str(), StringViewFormat(source));
909
910 if(IO::isFile(directory)) {
911 sources.emplace_back(directory);
912 }
913 else {
914 for (auto& entry : IO::directoryIterator(directory)) {
915 std::string path;
916 try {
917 path = IO::getPath(entry);
918 }
919 catch (std::system_error& e) {
920 LOG_FATAL(logger, "Failed to handle path, code=%d: %s", e.code().value(), e.what());
921 abort();
922 }
923 if (IO::isFile(path)) {
924 auto dirname = IO::parentPath(path);
925 auto filename = IO::fileName(path);
926 if (matchFilename2(filename, pattern)) {
927 LOG_DEBUG(logger, " Matched source %s", filename.c_str());
928 sources.emplace_back(path);
929 }
930 }
931 }
932 }
933
934 if (sources.empty()) {
935 LOG_WARNING(logger, "Matching sources empty for pattern %s in %.*s.", pattern.c_str(), StringViewFormat(source));
936 return;
937 }
938
939 std::vector<SparseOctree::OctreeInfo> infos;
940
941 for (auto & source_ : sources) {
942 SparseOctree::OctreeInfo& info = infos.emplace_back();
943 info.source = source_;
944 info.name = IO::stem(info.source);
945 info.absolute = IO::absolute(info.source);
946 info.directory = IO::absolute(outDirectory.to_string());
947 info.layer = IO::stem(assetFileName);
948 info.layerDirectory = IO::combine(outDirectory.to_string(), info.layer);
949 info.filesDirectory = IO::combine(info.layerDirectory, info.name);
950 info.assetFileNameStem = IO::combine(info.layerDirectory, info.name);
951 info.layerAndName = IO::combine(info.layer, info.name);
952 info.keepList = IO::absolute(properties.getProperty("keepList", StringView()).to_string());
953 info.discardOutliers = properties.getProperty("discardOutliers", info.discardOutliers);
954 info.outlierMinVertexCount = properties.getProperty("outlierMinVertexCount", info.outlierMinVertexCount);
955 info.outlierMinVertexDensity = properties.getProperty("outlierMinVertexDensity", info.outlierMinVertexDensity);
956 info.outlierMaxVolume = properties.getProperty("outlierMaxVolume", info.outlierMaxVolume);
957 info.outputOutliers = properties.getProperty("outputOutliers", info.outputOutliers);
958 info.maxSubtreeSize = static_cast<uint32_t>(std::max(0, static_cast<int>(properties.getProperty("maxSubtreeSize", info.maxSubtreeSize))));
959 info.maxMeshVertexCount = properties.getProperty("maxMeshVertexCount", info.maxMeshVertexCount);
960 info.maxModelVertexCount = properties.getProperty("maxModelVertexCount", info.maxModelVertexCount);
961 info.maxModelMeshCount = properties.getProperty("maxModelMeshCount", info.maxModelMeshCount);
962 info.dropReductionLevelThreshold = properties.getProperty("dropReductionLevelThreshold", info.dropReductionLevelThreshold);
963 info.dropReductionThreshold = properties.getProperty("dropReductionThreshold", info.dropReductionThreshold);
964 info.minReductionThreshold = properties.getProperty("minReductionThreshold", info.minReductionThreshold);
965 info.edgeLengthWeight = properties.getProperty("edgeLengthWeight", info.edgeLengthWeight);
966 info.skipPattern = properties.getProperty("skipPattern", info.skipPattern).to_string();
967 info.useCompression = properties.getProperty("useCompression", info.useCompression);
968 info.prettyPrint = properties.getProperty("prettyPrint", info.prettyPrint);
969 info.compressionLevel = properties.getProperty("compressionLevel", info.compressionLevel);
970 info.embedMeshBounds = properties.getProperty("embedMeshBounds", info.embedMeshBounds);
971 info.statistics = properties.getProperty("statistics", info.statistics);
972 info.colorizeLevels = properties.getProperty("colorizeLevels", info.colorizeLevels);
973
974 info.filter_rootEndsWith = properties.getProperty("filter_rootEndsWith", info.filter_rootEndsWith).to_string();
975 info.filter_rootIncludes = properties.getProperty("filter_rootIncludes", info.filter_rootIncludes).to_string();
976 info.filter_rootPattern = properties.getProperty("filter_rootPattern", info.filter_rootPattern).to_string();
977 info.filter_attributeName = properties.getProperty("filter_attributeName", info.filter_attributeName).to_string();
978 info.filter_attributeValue = properties.getProperty("filter_attributeValue", info.filter_attributeValue).to_string();
979
980 info.filter_excludeNodes.clear();
981
982 Cogs::StringView levelReducer = properties.getProperty("levelReducer", "rationalreducer-vertexcount");
983 switch (levelReducer.hashLowercase()) {
984 case Cogs::hash("rationalreducer-vertexcount"):
985 LOG_DEBUG(logger, "levelReducer=rationalreducer-vertexcount");
986 info.levelReducer = SparseOctree::LevelReducer::RationalReducerVertexCount;
987 break;
988 case Cogs::hash("meshoptimizer-vertexcount"):
989 LOG_DEBUG(logger, "levelReducer=meshoptimizer-vertexcount");
990 info.levelReducer = SparseOctree::LevelReducer::MeshOptimizerVertexCount;
991 break;
992 case Cogs::hash("meshoptimizer-sloppy-vertexcount"):
993 LOG_DEBUG(logger, "levelReducer=meshoptimizer-sloppy-vertexcount");
994 info.levelReducer = SparseOctree::LevelReducer::MeshOptimizerSloppyVertexCount;
995 break;
996 default:
997 LOG_ERROR(logger, "Unknown levelReducer \"%.*s\"", StringViewFormat(levelReducer));
998 break;
999 }
1000
1001 // Allow filtering one node: "filter_excludeNode"
1002 // Real solution for clients is to use the "filter" with Full filter desc in JSON.
1003 auto prop = properties.getProperty("filter_excludeNode", "").to_string();
1004 if (!prop.empty())
1005 info.filter_excludeNodes.emplace_back(std::move(prop));
1006
1007 // Extract RVM Tasks defintions. Runs in this order. Custom compatibility read for "filter".
1008 // info.tasks = properties.getProperty("rvm.tasks", info.tasks).to_string();
1009 info.taskFilter = properties.getProperty("filter", info.taskFilter).to_string();
1010 info.taskFilter = properties.getProperty("rvm.filter.filter", info.taskFilter).to_string();
1011
1012 // Copy all properties from input:
1013 info.properties = properties;
1014
1015 auto minBoundsId = properties.findProperty("minBounds");
1016 if (minBoundsId != (unsigned)-1) {
1017 auto vals = getArrayProp(properties, Strings::add("minBounds"));
1018 info.minBounds = glm::vec3(vals[0], vals[1], vals[2]);
1019 info.useMinBounds = true;
1020 }
1021
1022 auto maxBoundsId = properties.findProperty("maxBounds");
1023 if (maxBoundsId != (unsigned)-1) {
1024 auto vals = getArrayProp(properties, Strings::add("maxBounds"));
1025 info.maxBounds = glm::vec3(vals[0], vals[1], vals[2]);
1026 info.useMaxBounds = true;
1027 }
1028
1029
1030 LOG_DEBUG(logger, "maxSubtreeSize=%u maxMeshVertexCount=%d maxModelVertexCount=%d maxModelMeshCount=%d",
1032
1033 const std::string assetFileNam = info.assetFileNameStem + ".asset";
1034 if (!forceUpdate && IO::exists(assetFileNam)) {
1035 LOG_DEBUG(logger, "Skipping writing %s, as the output already exists.", assetFileNam.c_str());
1036 continue;
1037 }
1038
1039 buildOctree(info);
1040
1041 Cogs::Core::runFrames(context, 3);
1042 }
1043
1044 Document hDoc;
1045 hDoc.SetObject();
1046 auto & hAlloc = hDoc.GetAllocator();
1047 Value entities(kArrayType);
1048
1049 for (auto & info : infos) {
1050 if (info.isEmpty) {
1051 LOG_WARNING(logger, "Skipped linking empty %s.", info.assetFileNameStem.c_str());
1052 continue;
1053 }
1054
1055 Value e(kObjectType);
1056
1057 Value asset(kObjectType);
1058 asset.AddMember("source", Value(std::string(info.layerAndName + ".asset").c_str(), hAlloc), hAlloc);
1059 asset.AddMember("flags", Value("InstantiateOnDemand | RelativePaths", hAlloc), hAlloc);
1060
1061 e.AddMember("asset", asset, hAlloc);
1062
1063 entities.PushBack(e, hAlloc);
1064 }
1065
1066 hDoc.AddMember("entities", entities, hAlloc);
1067
1068 if (properties.getProperty("skipAsset", false)) return;
1069
1070 {
1071 StringBuffer buffer;
1072 PrettyWriter<StringBuffer> writer(buffer);
1073 writer.SetIndent(' ', 2);
1074 writer.SetMaxDecimalPlaces(4);
1075 hDoc.Accept(writer);
1076
1077 auto path = IO::combine(outDirectory.to_string(), assetFileName);
1078 std::ofstream file(path);
1079 file.write(buffer.GetString(), buffer.GetSize());
1080 file.close();
1081
1082 LOG_DEBUG(logger, "Wrote layer asset %s.", assetFileName.c_str());
1083 }
1084}
1085
1086void Cogs::Core::SparseBuildOctreeCommand::buildOctree(SparseOctree::OctreeInfo & info)
1087{
1088 const std::span<const float> params = getArrayProp(properties, Strings::add("params"));
1089 const std::span<const float> paramRemesh = getArrayProp(properties, Strings::add("paramRemesh"));
1090 const std::span<const float> paramRemeshGridSpacing = getArrayProp(properties, Strings::add("paramRemeshGridSpacing"));
1091 const std::span<const float> paramRemeshVolumeBlur = getArrayProp(properties, Strings::add("paramRemeshVolumeBlur"));
1092 const std::span<const float> paramRemeshTriSoupBlur = getArrayProp(properties, Strings::add("paramRemeshTriSoupBlur"));
1093 const std::span<const float> paramRemeshSimplification = getArrayProp(properties, Strings::add("paramRemeshSimplification"));
1094 const std::span<const float> paramFeatureAngle = getArrayProp(properties, Strings::add("paramFeatureAngle"));
1095 const std::span<const float> paramCullDiameter = getArrayProp(properties, Strings::add("paramCullDiameter"));
1096 const std::span<const float> paramCullGeometry = getArrayProp(properties, Strings::add("paramCullGeometry"));
1097 const std::span<const float> paramCullLeaf = getArrayProp(properties, Strings::add("paramCullLeaf"));
1098 const std::span<const float> paramSimplificationFactor = getArrayProp(properties, Strings::add("paramSimplificationFactor"));
1099 const std::span<const float> paramSimplificationMode = getArrayProp(properties, Strings::add("paramSimplificationMode")); // use float array as int array doesn't exist in property store
1100
1101 // If any parametric values provided, use last value as default.
1102 // Otherwise try to fetch default from non-parametric property.
1104 defaults.remesh = paramRemesh.empty() ? properties.getProperty("remesh", defaults.remesh) : paramRemesh.back();
1105 defaults.remeshGridSpacing = paramRemeshGridSpacing.empty() ? properties.getProperty("remeshGridSpacing", defaults.remeshGridSpacing) : paramRemeshGridSpacing.back();
1106 defaults.remeshVolumeBlur = paramRemeshVolumeBlur.empty() ? properties.getProperty("remeshVolumeBlur", defaults.remeshVolumeBlur) : static_cast<int>(paramRemeshVolumeBlur.back());
1107 defaults.remeshTriSoupBlur = paramRemeshTriSoupBlur.empty() ? properties.getProperty("remeshTriSoupBlur", defaults.remeshVolumeBlur) : static_cast<int>(paramRemeshTriSoupBlur.back());
1108 defaults.remeshSimplification = paramRemeshSimplification.empty() ? properties.getProperty("remeshSimplification", defaults.remeshSimplification) : paramRemeshSimplification.back();
1109 defaults.featureAngle = paramFeatureAngle.empty() ? properties.getProperty("featureAngle", defaults.featureAngle) : paramFeatureAngle.back();
1110 defaults.cullDiameter = paramCullDiameter.empty() ? properties.getProperty("cullDiameter", defaults.cullDiameter) : paramCullDiameter.back();
1111 defaults.cullGeometry = paramCullGeometry.empty() ? properties.getProperty("cullGeometry", defaults.cullGeometry) : paramCullGeometry.back();
1112 defaults.simplificationMode = paramSimplificationMode.empty() ? properties.getProperty("simplificationMode", defaults.simplificationMode) : static_cast<int>(paramSimplificationMode.back());
1113
1114 float maxTolerance = 0.f;
1115 std::vector<SparseOctree::Parameterization> parameterizations(params.size());
1116 for (size_t i = 0; i < params.size(); ++i) {
1117 parameterizations[i].tolerance = params[i];
1118 parameterizations[i].remesh = i < paramRemesh.size() ? paramRemesh[i] : defaults.remesh;
1119 parameterizations[i].remeshGridSpacing = i < paramRemeshGridSpacing.size() ? paramRemeshGridSpacing[i] : defaults.remeshGridSpacing;
1120 parameterizations[i].remeshVolumeBlur = i < paramRemeshVolumeBlur.size() ? static_cast<int>(paramRemeshVolumeBlur[i]) : defaults.remeshVolumeBlur;
1121 parameterizations[i].remeshTriSoupBlur = i < paramRemeshTriSoupBlur.size() ? static_cast<int>(paramRemeshTriSoupBlur[i]) : defaults.remeshTriSoupBlur;
1122 parameterizations[i].remeshSimplification = i < paramRemeshSimplification.size() ? paramRemeshSimplification[i] : defaults.remeshSimplification;
1123 parameterizations[i].featureAngle = i < paramFeatureAngle.size() ? paramFeatureAngle[i] : defaults.featureAngle;
1124 parameterizations[i].cullDiameter = i < paramCullDiameter.size() ? paramCullDiameter[i] : defaults.cullDiameter;
1125 parameterizations[i].cullGeometry = i < paramCullGeometry.size() ? paramCullGeometry[i] : defaults.cullGeometry;
1126 parameterizations[i].cullLeaf = i < paramCullLeaf.size() ? paramCullLeaf[i] : defaults.cullLeaf;
1127 parameterizations[i].simplificationFactor = i < paramSimplificationFactor.size() ? paramSimplificationFactor[i] : defaults.simplificationFactor;
1128 parameterizations[i].simplificationMode = i < paramSimplificationMode.size() ? static_cast<int>(paramSimplificationMode[i]) : defaults.simplificationMode;
1129 maxTolerance = std::max(maxTolerance, parameterizations[i].tolerance);
1130 }
1131
1132 std::span<const float> lParams = getArrayProp(properties, Strings::add("levelParams"));
1133 std::span<const float> lReduction = getArrayProp(properties, Strings::add("levelReduction"));
1134 std::span<const float> lCulling = getArrayProp(properties, Strings::add("levelCulling"));
1135 std::span<const float> lThresholds = getArrayProp(properties, Strings::add("levelThresholds"));
1136
1137 // Sanity checks:
1138 // - lParams defines # levels, lThresholds is padded if it is too small
1139 // - The rest should match.
1140 assert(!lParams.empty() && "Need at least one level");
1141 assert(lReduction.size() == lParams.size() && "levelReduction must be the same size as levelParams");
1142 assert(lCulling.size() == lParams.size() && "levelCulling must be the same size as levelParams");
1143 assert(lThresholds.size() <= lParams.size() && "more thresholds than levels");
1144
1145 SparseOctree::Octree octree;
1146 octree.context = state->context;
1147 octree.info = info;
1148 octree.sources.assign(std::begin(parameterizations), std::end(parameterizations));
1149 octree.minCellSize = properties.getProperty("minCellSize", octree.minCellSize);
1150 octree.numLevels = static_cast<uint32_t>(lParams.size());
1151
1152 octree.inputs.resize(octree.numLevels);
1153 for (size_t i = 0; i < lParams.size(); ++i) {
1154 const float threshold = lThresholds.size() > i ? lThresholds[i] : -1;
1155 octree.inputs[i] = SparseOctree::LevelInput{ (uint32_t)lParams[i], lReduction[i], lCulling[i], threshold };
1156 }
1157 octree.grids.resize(octree.numLevels);
1158 octree.scenes.resize(octree.numLevels);
1159
1160 Cogs::Timer timerTotal = Timer::startNew();
1161 {
1162
1163 // Load the first (and most detailed) parameterization, it is assumed that this
1164 // will have the largest boundingbox, and this will be used as bounds for the
1165 // entire pyramid.
1166 size_t parameterization = octree.inputs[0].parameterization;
1167 {
1168 SparseOctree::Parameterization& p = octree.sources[parameterization];
1169 if (p.isLoaded == false) {
1170 parameterizationTriggerload(context, p, octree.info);
1171 parameterizationWaitForLoad(context, p);
1172 if (!p.isLoaded) {
1173 LOG_ERROR(logger, "Model load failure");
1174 return;
1175 }
1176 }
1177 SparseOctree::SceneInfo& scene = octree.scenes[0];
1178 scene.model = octree.sources[octree.inputs[0].parameterization].model;
1179 scene.bounds = Geometry::makeEmptyBoundingBox<Geometry::BoundingBox>();
1180 levelGetModelAndBounds(octree, scene, maxTolerance);
1181 if (!calculateLevelSizes(octree)) {
1182 LOG_ERROR(logger, "No geometries found in level 0.");
1183 info.isEmpty = true;
1184 return;
1185 }
1186 }
1187 Geometry::BoundingBox bounds = octree.scenes[0].bounds;
1188 LOG_DEBUG(logger, "Overall bounding box: [%.2f %.2f %.2f]x[%.2f %.2f %.2f]).",
1189 bounds.min.x, bounds.min.y, bounds.min.z,
1190 bounds.max.x, bounds.max.y, bounds.max.z);
1191
1192 for (size_t level = 0; level < octree.numLevels; level++) {
1193
1194 LOG_DEBUG(logger, "Processing level %zu", level);
1195
1196
1197 // If we have a different parameterization for this level, unload the previous one
1198 if (parameterization != octree.inputs[level].parameterization) {
1199 if (parameterization != ~0u) {
1200 octree.sources[parameterization].model = ModelHandle();
1201 octree.sources[parameterization].isLoaded = false;
1202 }
1203 parameterization = octree.inputs[level].parameterization;
1204 }
1205 SparseOctree::Parameterization& p = octree.sources[parameterization];
1206 if (p.isLoaded == false) {
1207 parameterizationTriggerload(context, p, octree.info);
1208 parameterizationWaitForLoad(context, p);
1209 if (!p.isLoaded) {
1210 LOG_ERROR(logger, "Model load failure");
1211 return;
1212 }
1213 }
1214
1215 SparseOctree::SceneInfo& scene = octree.scenes[level];
1216 if (level != 0) {
1217
1218 // Load and check bounds, but we will use the bounds of level 0 for the grid
1219 // to make sure the levels fit together.
1220 scene.model = octree.sources[octree.inputs[level].parameterization].model;
1221 scene.bounds = Geometry::makeEmptyBoundingBox<Geometry::BoundingBox>();
1222 levelGetModelAndBounds(octree, scene, maxTolerance);
1223 if (!((bounds.min.x <= scene.bounds.min.x) && (scene.bounds.max.x <= bounds.max.x) &&
1224 (bounds.min.y <= scene.bounds.min.y) && (scene.bounds.max.y <= bounds.max.y) &&
1225 (bounds.min.z <= scene.bounds.min.z) && (scene.bounds.max.z <= bounds.max.z)))
1226 {
1227 LOG_WARNING(logger, "Bounding box of level %zu (=[%.2f %.2f %.2f]x[%.2f %.2f %.2f]) extends outside bounding box of level 0 (=[%.2f %.2f %.2f]x[%.2f %.2f %.2f]).",
1228 level,
1229 scene.bounds.min.x, scene.bounds.min.y, scene.bounds.min.z,
1230 scene.bounds.max.x, scene.bounds.max.y, scene.bounds.max.z,
1231 bounds.min.x, bounds.min.y, bounds.min.z,
1232 bounds.max.x, bounds.max.y, bounds.max.z);
1233 }
1234 scene.bounds = bounds; // Use bounds of level 0.
1235 }
1236
1237 SparseOctree::Grid& grid = octree.grids[level];
1238 Timer passTimer = Timer::startNew();
1239 levelInitCells(context, grid, octree, level);
1240 levelFilterEntities(context, grid, octree, level);
1241 LOG_DEBUG(logger, "Level %zu: Determining possibly non-empty cells: %.2fs ", level, passTimer.elapsedSeconds());
1242
1243 SparseOctree::distributeGeometryWithIds(octree, properties, level);
1244 octree.scenes[level].model = ModelHandle();
1245 octree.scenes[level].entities.clear();
1246 Cogs::Core::runFrames(context, 2);
1247
1248 //levelSimplifyCells(context, state, grid, info, properties, octree, level);
1249 }
1250 }
1251 exportAsset(state->context, octree);
1252 LOG_DEBUG(logger, "Processed hierarchy in %.3fs.", timerTotal.elapsedSeconds());
1253
1254}
1255
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
constexpr bool empty() const noexcept
Check if the string is empty.
Definition: StringView.h:122
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
Definition: StringView.cpp:13
static constexpr size_t NoPosition
No position.
Definition: StringView.h:43
Old timer class.
Definition: Timer.h:37
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
Cogs::Geometry::BoundingBox COGSCORE_DLL_API calculateBounds(Mesh *mesh)
Calculate a bounding box for the given mesh.
Definition: MeshHelper.cpp:283
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
Contains all Cogs related functionality.
Definition: FieldSetter.h:23
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
void setBounds(Geometry::BoundingBox box)
Set custom bounds for the mesh.
Definition: Mesh.h:298
uint32_t getCount() const
Get the vertex count of the mesh.
Definition: Mesh.h:1012
Model resources define a template for a set of connected entities, with resources such as meshes,...
Definition: Model.h:56
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
std::string resourcePath
Resource path. Used to locate resource.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.
std::vector< uint32_t > touches
Entity index of parts that touches the cell.
std::vector< MeshRef > meshRefs
Meshes written to disc.
Geometry::BoundingBox bbox
Initially the full bounds of the cell, but shrinked to bounds of geometry after cell has been populat...
std::string fileName
File name of cogsbin file with processed geometry.
uint32_t part
Part index of this cell's geometry in cogsbin file with processed geometry, -1 means that the entire ...
std::string filter_rootEndsWith
Individual Task: filter settings - for compatibility.
bool embedMeshBounds
Add mesh bounds where appropriate.
bool colorizeLevels
Add material to output colorized dependent on lod level, intended as a debug tool.
std::string keepList
("rvm.tagSimplify.keepList")
bool statistics
Calculate statistics for resulting hierarchy.
std::vector< Parameterization > sources
float remesh
("rvm.tessellate.remesh") Shapes with bbox cross-section diameter less than this value gets remeshed....
uint32_t geometries
The number of non-culled geometries in the scene.
float maxGeometrySize
Largest bounding box side length of an object in the scene.
@ TriangleList
List of triangles.
Definition: Common.h:116