1#include "SparseBuildOctreeCommand.h"
5#include "Resources/ModelManager.h"
6#include "Resources/MeshManager.h"
7#include "Resources/Buffer.h"
8#include "Resources/MaterialManager.h"
10#include "Scene/GetBounds.h"
12#include "Services/Variables.h"
17#include "Serialization/JsonParser.h"
18#include "Serialization/ModelWriter.h"
20#include "SparseBuildOctree.h"
21#include "AssetStatsCommand.h"
22#include "Utilities/Parallel.h"
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"
32#include "glm/gtx/compatibility.hpp"
33#include "glm/gtx/component_wise.hpp"
34#include "glm/gtx/transform.hpp"
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
58 if (!loadInfo.properties->hasProperty(key)) {
59 loadInfo.properties->addProperty(key, value);
65 if (!value.
empty() && !loadInfo.properties->hasProperty(key)) {
66 loadInfo.properties->addProperty(key, value);
78 ModelLoadInfo& loadInfo = *context->modelManager->createLoadInfo();
81 loadInfo.
loadFlags = ResourceLoadFlags::ForceUnique;
82 loadInfo.modelFlags = ModelLoadFlags::ForceUnique;
84 if (context->
variables->get(
"resources.models.autoReload",
false)) {
85 loadInfo.
loadFlags |= ResourceLoadFlags::AutoReload;
103 loadInfo.properties = std::make_unique<PropertyStore>();
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);
120 ::setIfNotSet(loadInfo,
"rvm.tagSimplify.keepList", info.
keepList);
125 ::setIfNotSet(loadInfo,
"rvm.export.noHierarchy",
true);
126 ::setIfNotSet(loadInfo,
"rvm.export.discardLines",
true);
127 ::setIfNotSet(loadInfo,
"rvm.export.doNotMergeGeometries",
false);
129 ::setIfNotSet(loadInfo,
"rvm.export.idInTexCoord",
true);
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);
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]);
145 ::setIfNotSet(loadInfo,
"rvm.filter.filter", info.
taskFilter);
147 p.model = context->modelManager->loadResource(&loadInfo);
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"
169 p.remeshSimplification,
174 p.simplificationFactor,
175 p.simplificationMode);
180 Timer timer = Timer::startNew();
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.");
189 while (p.isLoaded ==
false);
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;
200 LOG_DEBUG(logger,
" +- dimensions: [%zu %zu %zu]", xSize, ySize, zSize);
202 if (isEmpty(bounds)) {
203 LOG_DEBUG(logger,
" +- bounding box: <empty>");
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);
214 SparseOctree::CellIndex createCell(
SparseOctree::Grid& grid, SparseOctree::CellMapKey key,
size_t i,
size_t j,
size_t k)
216 SparseOctree::CellIndex cellIndex = grid.cellStore.size();
218 grid.cellMap.emplace(key, cellIndex);
221 cell.x =
static_cast<uint32_t
>(i);
222 cell.y =
static_cast<uint32_t
>(j);
223 cell.z =
static_cast<uint32_t
>(k);
232 const float cullingThreshold = std::min(scene.
maxGeometrySize, input.cullingThreshold);
234 float maxDensity = 0.0f;
235 float minDensity = 1000000000.0f;
239 std::vector<const SparseOctree::EntityRef*> filteredEntities;
240 filteredEntities.reserve(scene.entities.size());
243 size_t explicitlySkipped = 0;
244 size_t missingMesh = 0;
245 size_t smallerThanCullingThreshold = 0;
246 size_t biggerThanMaxSize = 0;
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; }
255 assert(!glm::any(glm::isnan(e.box.min)) && !glm::any(glm::isnan(e.box.max)) &&
"NaN in our boxes.");
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;
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);
270 maxDensity = std::max(maxDensity, density);
271 minDensity = std::min(minDensity, density);
273 if (octree.info.outputOutliers) {
274 filteredEntities.push_back(&e);
277 else if (!octree.info.outputOutliers) {
278 filteredEntities.push_back(&e);
282 filteredEntities.push_back(&e);
287 LOG_INFO(logger,
"Outliers: %zu", outliers);
288 LOG_INFO(logger,
"Max density: %f", maxDensity);
289 LOG_INFO(logger,
"Min density: %f", minDensity);
294 size_t meshCellOverlap = 0;
295 const Geometry::BoundingBox bounds = scene.bounds;
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);
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));
304 glm::uvec3 lo = glm::uvec3(flo);
305 glm::uvec3 hi = glm::min(glm::uvec3(fhi) + glm::uvec3(1), grid.dimensions);
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++) {
311 assert(i < grid.dimensions.x && j < grid.dimensions.y && k < grid.dimensions.z);
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;
318 assert(cell.x == i && cell.y == j && cell.z == k);
322 cellIndex = createCell(grid, key, i, j, k);
328 for (
size_t l = level + 1; l < octree.numLevels; l++) {
333 SparseOctree::CellMapKey ancestorKey = ancestorGrid.cellMapKey(ii, jj, kk);
334 if (
auto at = ancestorGrid.cellMap.find(ancestorKey); at != ancestorGrid.cellMap.end()) {
338 SparseOctree::CellIndex ancestorIndex = createCell(ancestorGrid, ancestorKey, ii, jj, kk);
340 assert(ancestorCell.x == ii && ancestorCell.y == jj && ancestorCell.z == kk);
347 assert(cell.x == i && cell.y == j && cell.z == k);
349 cell.
touches.push_back(e->index);
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);
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);
365 LOG_DEBUG(logger,
"FilterEntities: meshCellOverlap=%zu", meshCellOverlap);
372 scene.entities.resize(model.parts.size());
374 scene.bounds = Geometry::BoundingBox();
375 for (
size_t i = 0; i < model.parts.size(); ++i) {
379 e.index = (uint32_t)i;
380 e.parent = part.parentIndex;
382 if (part.nameIndex != (uint32_t)-1) {
383 auto name = model.getPartName(part);
386 LOG_INFO(logger,
"Skipped object %.*s", StringViewFormat(name));
391 if (part.meshIndex != (uint32_t)-1) {
392 e.mesh = model.meshes[part.meshIndex];
400 LOG_ERROR(logger,
"Skipped part %zu with degenerate mesh; %u count.", i, e.mesh->
getCount());
405 if (part.materialIndex != NoIndex) {
406 e.material = model.materials[part.materialIndex];
408 e.startIndex = part.startIndex;
409 e.vertexCount = part.vertexCount;
411 if (part.boundsIndex != (uint32_t)-1) {
412 e.box = model.bounds[part.boundsIndex];
415 if (isEmpty(e.mesh->boundingBox)) {
418 e.box = e.mesh->boundingBox;
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);
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);
432 scene.bounds += e.box;
433 e.span = e.box.max - e.box.min;
434 e.maxDim = glm::compMax(e.span);
443 if (!isEmpty(scene.bounds)) {
444 scene.bounds.min -= glm::vec3(maxTolerance);
445 scene.bounds.max += glm::vec3(maxTolerance);
451 if (!octree.scenes[0].geometries)
return false;
453 const uint32_t maxCells = (uint32_t)std::pow(2, octree.numLevels);
455 glm::vec3 size0 = octree.scenes[0].bounds.max - octree.scenes[0].bounds.min;
457 glm::uvec3 cells(maxCells);
458 glm::uvec3 levels(octree.numLevels);
461 glm::vec3 cellSize(size0.x / cells.x, size0.y / cells.y, size0.z / cells.z);
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;
468 cells[i] = (uint32_t)std::pow(2u, levels[i]);
469 cellSize[i] = size0[i] / cells[i];
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);
480 octree.grids[l].dimensions[i] = 1;
485 assert(octree.inputs.size() >= octree.numLevels &&
"Invalid inputs.");
493 Value value(kObjectType);
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);
504 Value properties(kObjectType);
505 properties.AddMember(
"bbox", Value(bbox, alloc), alloc);
507 value.AddMember(
"properties", properties, alloc);
510 if (meshRef.
part == (uint32_t)-1) {
511 value.AddMember(
"model", Value(meshRef.
fileName.c_str(), alloc), alloc);
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);
527 if (cell.
meshRefs.empty())
return Value();
529 if (cell.
meshRefs.size() == 1)
return jsonEncodeMeshReference(alloc, info, cell.
meshRefs[0], forceBounds);
531 Value value(kArrayType);
533 value.PushBack(jsonEncodeMeshReference(alloc, info, meshRef,
true), alloc);
538 void jsonWriteAsset(MemoryPoolAllocator<CrtAllocator>& alloc,
540 const std::string& filename,
543 Document hDoc(&alloc);
545 Value entities(kArrayType);
546 entities.PushBack(root, alloc);
547 hDoc.AddMember(
"entities", entities, alloc);
551 if (info.prettyPrint) {
552 PrettyWriter<StringBuffer> writer(buffer);
553 writer.SetIndent(
' ', 2);
554 writer.SetMaxDecimalPlaces(4);
558 Writer<StringBuffer> writer(buffer);
559 writer.SetMaxDecimalPlaces(4);
563 if (0 < info.compressionLevel) {
565 ZSTD_CCtx* zstd_ctx = ZSTD_createCCtx();
567 size_t uncompressedSize = buffer.GetSize();
568 size_t maxSize = ZSTD_compressBound(uncompressedSize);
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));
576 else if (uncompressedSize <= result) {
577 LOG_WARNING(logger,
"%s: compressed size greater or equal to uncompressed size", filename.c_str());
579 ZSTD_freeCCtx(zstd_ctx);
581 std::ofstream file(filename, std::ios::binary);
582 file.write((
const char*)compressed.data(), result);
586 std::ofstream file(filename);
587 file.write(buffer.GetString(), buffer.GetSize());
591 LOG_INFO(logger,
"Wrote asset %s.", filename.c_str());
598 MemoryPoolAllocator alloc;
600 std::vector<std::vector<uint32_t>> weightsPerLevel(octree.numLevels);
601 std::vector<std::vector<Value>> valuesPerLevel(octree.numLevels);
605 std::vector<Geometry::BoundingBox> bounds, childBounds;
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;
614 childBounds.swap(bounds);
615 bounds.resize(grid.cellStore.size());
620 for (
size_t i = 0, n = bounds.size(); i < n; i++) {
621 bounds[i] = grid.cellStore[i].bbox;
626 const glm::uvec3 childLayerDim = octree.grids[level - 1].dimensions;
629 assert(childBounds.size() == childGrid.cellStore.size());
631 for (
size_t i = 0, n = bounds.size(); i < n; i++) {
637 Geometry::BoundingBox box = cell.
bbox;
639 size_t x0 = 2 * cell.x;
640 size_t x1 = std::min(x0 + 2,
size_t(childGrid.dimensions.x));
642 size_t y0 = 2 * cell.y;
643 size_t y1 = std::min(y0 + 2,
size_t(childGrid.dimensions.y));
645 size_t z0 = 2 * cell.z;
646 size_t z1 = std::min(z0 + 2,
size_t(childGrid.dimensions.z));
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++) {
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];
666 std::vector<Value>& values = valuesPerLevel[level];
667 std::vector<uint32_t>& weights = weightsPerLevel[level];
669 std::vector<Value>& prevValues = valuesPerLevel[level ? level - 1 : 0];
670 std::vector<uint32_t>& prevWeights = weightsPerLevel[level ? level - 1 : 0];
672 glm::uvec3 prevDims = octree.grids[level ? level - 1: level].dimensions;
676 values.resize(grid.cellStore.size());
677 weights.resize(grid.cellStore.size());
679 std::string directory = IO::combine(info.filesDirectory, std::to_string(level));
680 IO::createDirectories(IO::combine(directory,
".cogsbin"));
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) {
686 SparseOctree::CellMapKey key = grid.cellMapKey(x, y, z);
687 auto it = grid.cellMap.find(key);
688 if (it == grid.cellMap.end()) {
692 SparseOctree::CellIndex cellIndex = it->second;
694 assert(cell.x == x && cell.y == y && cell.z == z);
696 Value nodeValue(kNullType);
697 uint32_t nodeWeight =
static_cast<uint32_t
>(cell.
meshRefs.size());
699 if (level == 0 && !cell.
meshRefs.empty()) {
701 nodeValue = jsonEncodeMeshReferences(alloc, info, cell,
true);
703 else if (level != 0) {
704 Value lods(kArrayType);
705 Value lvl0(kArrayType);
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;
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) {
715 size_t i = x * xFac + xx;
716 size_t j = y * yFac + yy;
717 size_t k = z * zFac + zz;
719 SparseOctree::CellMapKey childKey = prevGrid.cellMapKey(i, j, k);
720 if (
auto jt = prevGrid.cellMap.find(childKey); jt != prevGrid.cellMap.end()) {
722 Value& v = prevValues[jt->second];
724 for (rapidjson::SizeType r = 0; r < v.Size(); r++) {
725 lvl0.PushBack(v[r], alloc);
728 else if (!v.IsNull()) {
729 lvl0.PushBack(v, alloc);
731 nodeWeight += prevWeights[jt->second];
736 if (cell.
meshRefs.empty() && lvl0.Empty())
continue;
738 Value lvl1 = jsonEncodeMeshReferences(alloc, info, cell,
false);
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);
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);
756 errors.PushBack(Value(octree.inputs[level - 1].threshold), alloc);
757 errors.PushBack(Value(octree.inputs[level].threshold), alloc);
760 Value properties(kObjectType);
761 properties.AddMember(
"bbox", bbox, alloc);
762 properties.AddMember(
"errors", errors, alloc);
764 if (lvl0.Size() == 0) {
766 lods.PushBack(Value(kObjectType), alloc);
768 else if (lvl0.Size() == 1) {
770 lods.PushBack(lvl0[0], alloc);
774 lods.PushBack(lvl0, alloc);
779 lods.PushBack(Value(kObjectType), alloc);
782 lods.PushBack(lvl1, alloc);
785 auto name = std::string(
"lod_") + std::to_string(level) +
":" + std::to_string(x) +
":" + std::to_string(y);
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);
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) +
802 const std::string filename = IO::combine(info.layerDirectory, relative);
803 jsonWriteAsset(alloc, info, filename, std::move(nodeValue));
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);
813 values[cellIndex] = nodeValue;
814 weights[cellIndex] = nodeWeight;
820 const std::string filename = info.assetFileNameStem +
".asset";
821 jsonWriteAsset(alloc, info, filename, std::move(valuesPerLevel[octree.numLevels - 1][0]));
823 AssetStatsCommand::calculateStats(context, properties, filename, info.assetFileNameStem +
"_stats.json");
830 bool matchFilename2(
const std::string name,
const std::string pattern)
832 const auto extension = Cogs::IO::extension(pattern);
833 const auto stem = Cogs::IO::stem(pattern);
835 bool extensionMatch =
false;
836 auto entryExtension = Cogs::IO::extension(name);
837 auto entryStem = Cogs::IO::stem(name);
839 if (extension ==
"*") {
840 extensionMatch =
true;
842 else if (extension == entryExtension) {
843 extensionMatch =
true;
846 bool patternMatch =
false;
851 else if (stem.find(
'*') != std::string::npos) {
853 stemEx.replace(stem.find(
'*'), 1,
".*");
855 std::regex stemRegex(stemEx);
858 std::regex_search(entryStem, match, stemRegex);
859 if (match.size()) patternMatch =
true;
860 }
else if (entryStem == stem) {
864 return extensionMatch && patternMatch;
873void Cogs::Core::SparseBuildOctreeCommand::undo()
882 auto index = properties.findProperty(0, properties.size(), key);
883 return index != (unsigned)-1 ? properties.getFloatArray(index) : std::span<float>{};
887void Cogs::Core::SparseBuildOctreeCommand::applyPost()
889 context->
variables->get(
"resources.globalFrameUpdateQuota")->setInt(0);
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;
897 auto postPattern = properties.getProperty(
"postPattern",
StringView()).to_string();
898 if (!postPattern.empty()) {
899 pattern = pattern + postPattern;
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();
906 std::vector<std::string> sources;
908 LOG_DEBUG(logger,
"Matching files for pattern %s in %.*s:", pattern.c_str(), StringViewFormat(source));
910 if(IO::isFile(directory)) {
911 sources.emplace_back(directory);
914 for (
auto& entry : IO::directoryIterator(directory)) {
917 path = IO::getPath(entry);
919 catch (std::system_error& e) {
920 LOG_FATAL(logger,
"Failed to handle path, code=%d: %s", e.code().value(), e.what());
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);
934 if (sources.empty()) {
935 LOG_WARNING(logger,
"Matching sources empty for pattern %s in %.*s.", pattern.c_str(), StringViewFormat(source));
939 std::vector<SparseOctree::OctreeInfo> infos;
941 for (
auto & source_ : sources) {
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))));
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);
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();
980 info.filter_excludeNodes.clear();
982 Cogs::StringView levelReducer = properties.getProperty(
"levelReducer",
"rationalreducer-vertexcount");
984 case Cogs::hash(
"rationalreducer-vertexcount"):
985 LOG_DEBUG(logger,
"levelReducer=rationalreducer-vertexcount");
986 info.levelReducer = SparseOctree::LevelReducer::RationalReducerVertexCount;
989 LOG_DEBUG(logger,
"levelReducer=meshoptimizer-vertexcount");
990 info.levelReducer = SparseOctree::LevelReducer::MeshOptimizerVertexCount;
992 case Cogs::hash(
"meshoptimizer-sloppy-vertexcount"):
993 LOG_DEBUG(logger,
"levelReducer=meshoptimizer-sloppy-vertexcount");
994 info.levelReducer = SparseOctree::LevelReducer::MeshOptimizerSloppyVertexCount;
997 LOG_ERROR(logger,
"Unknown levelReducer \"%.*s\"", StringViewFormat(levelReducer));
1003 auto prop = properties.getProperty(
"filter_excludeNode",
"").to_string();
1005 info.filter_excludeNodes.emplace_back(std::move(prop));
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;
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;
1030 LOG_DEBUG(logger,
"maxSubtreeSize=%u maxMeshVertexCount=%d maxModelVertexCount=%d maxModelMeshCount=%d",
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());
1041 Cogs::Core::runFrames(context, 3);
1046 auto & hAlloc = hDoc.GetAllocator();
1047 Value entities(kArrayType);
1049 for (
auto & info : infos) {
1051 LOG_WARNING(logger,
"Skipped linking empty %s.", info.assetFileNameStem.c_str());
1055 Value e(kObjectType);
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);
1061 e.AddMember(
"asset", asset, hAlloc);
1063 entities.PushBack(e, hAlloc);
1066 hDoc.AddMember(
"entities", entities, hAlloc);
1068 if (properties.getProperty(
"skipAsset",
false))
return;
1071 StringBuffer buffer;
1072 PrettyWriter<StringBuffer> writer(buffer);
1073 writer.SetIndent(
' ', 2);
1074 writer.SetMaxDecimalPlaces(4);
1075 hDoc.Accept(writer);
1077 auto path = IO::combine(outDirectory.to_string(), assetFileName);
1078 std::ofstream file(path);
1079 file.write(buffer.GetString(), buffer.GetSize());
1082 LOG_DEBUG(logger,
"Wrote layer asset %s.", assetFileName.c_str());
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"));
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());
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);
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"));
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");
1146 octree.context = state->context;
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());
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;
1157 octree.grids.resize(octree.numLevels);
1158 octree.scenes.resize(octree.numLevels);
1166 size_t parameterization = octree.inputs[0].parameterization;
1169 if (p.isLoaded ==
false) {
1170 parameterizationTriggerload(context, p, octree.info);
1171 parameterizationWaitForLoad(context, p);
1173 LOG_ERROR(logger,
"Model load failure");
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;
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);
1192 for (
size_t level = 0; level < octree.numLevels; level++) {
1194 LOG_DEBUG(logger,
"Processing level %zu", level);
1198 if (parameterization != octree.inputs[level].parameterization) {
1199 if (parameterization != ~0u) {
1201 octree.
sources[parameterization].isLoaded =
false;
1203 parameterization = octree.inputs[level].parameterization;
1206 if (p.isLoaded ==
false) {
1207 parameterizationTriggerload(context, p, octree.info);
1208 parameterizationWaitForLoad(context, p);
1210 LOG_ERROR(logger,
"Model load failure");
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)))
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]).",
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);
1234 scene.bounds = bounds;
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());
1243 SparseOctree::distributeGeometryWithIds(octree, properties, level);
1245 octree.scenes[level].entities.clear();
1246 Cogs::Core::runFrames(context, 2);
1251 exportAsset(state->context, octree);
1252 LOG_DEBUG(logger,
"Processed hierarchy in %.3fs.", timerTotal.elapsedSeconds());
A Context instance contains all the services, systems and runtime components needed to use Cogs.
std::unique_ptr< class Variables > variables
Variables service instance.
Log implementation class.
Provides a weakly referenced view over the contents of a string.
constexpr bool empty() const noexcept
Check if the string is empty.
size_t hashLowercase(size_t hashValue=Cogs::hash()) const noexcept
Get the hash code of the string converted to lowercase.
static constexpr size_t NoPosition
No position.
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.
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Contains all Cogs related functionality.
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
void setBounds(Geometry::BoundingBox box)
Set custom bounds for the mesh.
uint32_t getCount() const
Get the vertex count of the mesh.
Model resources define a template for a set of connected entities, with resources such as meshes,...
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.
void apply() override
Run the command.
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.