1#include "SparseBuildOctree.h"
4#include "Resources/Mesh.h"
5#include "Resources/MeshManager.h"
6#include "Resources/Model.h"
7#include "Resources/MaterialManager.h"
9#include "Utilities/Parallel.h"
10#include "Serialization/ModelWriter.h"
12#include "Foundation/Platform/IO.h"
13#include "Foundation/Platform/Timer.h"
14#include "Foundation/BitTwiddling/MortonCode.h"
15#include "Foundation/Logging/Logger.h"
17#include "../Extensions/GeometryProcessing/GeometryProcessing.h"
19#if !defined( EMSCRIPTEN ) && !defined( __APPLE__ )
25#include <glm/gtx/color_space.hpp>
26#include <glm/gtx/compatibility.hpp>
28#ifdef HAS_RATIONAL_REDUCER
29#include <rrapi/rrapi.h>
32#include <meshoptimizer/meshoptimizer.h>
38 using Cogs::Geometry::BoundingBox;
49 std::vector<MeshHandle> meshes;
50 std::string relativePath;
51 std::string absolutePath;
52 size_t vertexCount = 0;
55 struct CellProcessContext
60 std::string relativePrefix;
61 std::string filenamePrefix;
66 std::atomic<size_t> processedCells = 0;
67 std::atomic<size_t> fileCount = 0;
69 std::vector<WorkItem> workItems;
73 std::vector<bool> cellsDone;
75 size_t cellsWritten = 0;
78 float vertexMergeEpsilon = 0.0001f;
79 float clipEpsilon = 0.01f;
80 float featureAngle = 0.7f;
81 float protrusionAngle = 0.7f;
82 size_t maxMeshVertexCount;
83 size_t maxModelVertexCount;
84 size_t maxModelMeshCount;
85 size_t maxSplitDepth = 8;
89 std::vector<glm::vec3> pos;
90 std::vector<glm::vec3> nrm;
91 std::vector<glm::vec2> tex;
93 void clear() { pos.clear(); nrm.clear(); tex.clear(); }
94 bool empty()
const {
return pos.empty(); }
95 size_t size()
const {
return pos.size(); }
96 void resize(
size_t size) { pos.resize(size); nrm.resize(size); tex.resize(size); }
97 void assertSanity()
const { assert(pos.size() == nrm.size()); assert(pos.size() == tex.size()); }
100 struct SplitStackItem {
108 using Splits = Split[3];
111 struct CellWorkerContext
113 CellProcessContext& ctx;
114 std::vector<glm::vec3> clippedV;
115 std::vector<glm::vec3> clippedN;
116 std::vector<glm::vec2> clippedT;
117 std::vector<uint32_t> map;
118 std::vector<uint32_t> unique;
119 std::vector<glm::vec3> tmpPos;
120 std::vector<glm::vec2> tmpTex;
121 std::vector<uint32_t> tmpIx;
122 std::vector<uint32_t> tmpIx2;
127 std::vector<MeshHandle> meshes;
128 std::string relativePath;
129 std::string absolutePath;
130 size_t vertexCount = 0;
131 } smallMeshesModelFile;
135 unsigned calculateClippingMask(glm::vec3
const& position, __m128
const& clipBoxMin, __m128
const& clipBoxMax)
137 auto pos = _mm_set_ps(0.0f, position.z, position.y, position.x);
139 auto cmp1 = _mm_cmplt_ps(pos, clipBoxMax);
140 auto cmp2 = _mm_cmplt_ps(clipBoxMin, pos);
142 auto mask1 = _mm_movemask_ps(cmp1) & 0x7;
143 auto mask2 = _mm_movemask_ps(cmp2) & 0x7;
145 return mask1 | (mask2 << 3);
154 std::vector<float> Vv;
155 std::vector<float> Nn;
156 std::vector<float> Tt;
158 void reserve(
size_t size)
160 Vv.resize(size * 3 * 3);
161 Nn.resize(size * 3 * 3);
162 Tt.resize(size * 3 * 2);
170 thread_local Geom clipA;
171 thread_local Geom clipB;
173 void filterCells(std::vector<Mesh*>& clipped,
174 std::vector<Mesh*>& contained,
175 const Cogs::Geometry::BoundingBox& geoClip,
177 const std::vector<uint32_t>& touches)
179 for (uint32_t index : touches) {
185 const Cogs::Geometry::BoundingBox& bbox = e.box;
186 if ((geoClip.min.x <= bbox.min.x) &&
187 (geoClip.min.y <= bbox.min.y) &&
188 (geoClip.min.z <= bbox.min.z) &&
189 (bbox.max.x <= geoClip.max.x) &&
190 (bbox.max.y <= geoClip.max.y) &&
191 (bbox.max.z <= geoClip.max.z))
193 contained.push_back(mesh);
196 clipped.push_back(mesh);
201 void clipMeshes(CellWorkerContext& wCtx,
203 const Cogs::Geometry::BoundingBox& geoClip,
204 const std::vector<Mesh*>& clipped)
206 std::vector<glm::vec3>& clippedV = wCtx.clippedV;
207 std::vector<glm::vec3>& clippedN = wCtx.clippedN;
208 std::vector<glm::vec2>& clippedT = wCtx.clippedT;
210 for (
Mesh* mesh : clipped) {
212 std::span<const uint32_t> indexes = mesh->getIndexes();
213 auto tris = indexes.size() / 3;
216 assert(pStride ==
sizeof(glm::vec3));
217 const glm::vec3* positions = (glm::vec3*)pData;
220 const glm::vec3* normals = (glm::vec3*)nData;
223 const glm::vec2* texCoords = (glm::vec2*)tData;
227 float clipBox[6] = { geoClip.min.x, geoClip.min.y, geoClip.min.z, geoClip.max.x, geoClip.max.y, geoClip.max.z };
229 auto clipBoxMin = _mm_set_ps(0.0f, geoClip.min.z, geoClip.min.y, geoClip.min.x);
230 auto clipBoxMax = _mm_set_ps(0.0f, geoClip.max.z, geoClip.max.y, geoClip.max.x);
234 clipA.reserve(3 + 6);
235 clipB.reserve(3 + 6);
238 for (
unsigned t = 0; t < tris; t++) {
239 glm::vec3 V_in[3] = {
240 positions[indexes[t * 3 + 0]],
241 positions[indexes[t * 3 + 1]],
242 positions[indexes[t * 3 + 2]],
245 glm::vec3 N_in[3] = {
246 normals[indexes[t * 3 + 0]],
247 normals[indexes[t * 3 + 1]],
248 normals[indexes[t * 3 + 2]],
251 glm::vec2 T_in[3] = {
252 texCoords ? texCoords[indexes[t * 3 + 0]] : glm::vec2(0, 0),
253 texCoords ? texCoords[indexes[t * 3 + 1]] : glm::vec2(0, 0),
254 texCoords ? texCoords[indexes[t * 3 + 2]] : glm::vec2(0, 0),
257 unsigned masks[3] = { 0,0,0 };
258 for (
unsigned k = 0; k < 3; k++) {
259 masks[k] = calculateClippingMask(V_in[k], clipBoxMin, clipBoxMax);
264 if (clippedV.size() < 3 * N_t + 12) {
265 clippedV.resize(3 * N_t + 10 * 1024);
266 clippedN.resize(3 * N_t + 10 * 1024);
267 clippedT.resize(3 * N_t + 10 * 1024);
270 auto V_dst = &clippedV[N_t * 3];
271 auto N_dst = &clippedN[N_t * 3];
272 auto T_dst = &clippedT[N_t * 3];
274 if ((masks[0] & masks[1] & masks[2]) == 63) {
289 else if ((masks[0] | masks[1] | masks[2]) == 63) {
291 N_t += SparseOctree::clipTriangle((
float*)V_dst, (
float*)N_dst, (
float*)T_dst, clipBox, (
float*)V_in, (
float*)N_in, (
float*)T_in);
298 size_t begin = cellGeo.size();
300 size_t numPos = N_t * 3;
301 cellGeo.resize(begin + numPos);
303 std::copy(clippedV.data(), clippedV.data() + numPos, cellGeo.pos.data() + begin);
304 std::copy(clippedN.data(), clippedN.data() + numPos, cellGeo.nrm.data() + begin);
305 std::copy(clippedT.data(), clippedT.data() + numPos, cellGeo.tex.data() + begin);
309 void appendMeshes(Vertices& cellGeo,
310 const std::vector<Mesh*>& contained)
312 for (
Mesh* mesh : contained) {
315 assert(pStride ==
sizeof(glm::vec3));
316 auto positions = (glm::vec3*)pData;
318 auto indexes = mesh->getIndexes();
320 if (!pData)
continue;
323 auto normals = (glm::vec3*)nData;
326 auto texCoords = (glm::vec2*)tData;
329 size_t begin = cellGeo.size();
331 cellGeo.resize(begin + numPos);
333 glm::vec3* pos = cellGeo.pos.data() + begin;
334 glm::vec3* nor = cellGeo.nrm.data() + begin;
335 glm::vec2* tex = cellGeo.tex.data() + begin;
338 for (
size_t i = 0; i < numPos; ++i) {
339 pos[i] = positions[indexes[i]];
340 nor[i] = normals[indexes[i]];
341 tex[i] = texCoords[indexes[i]];
345 for (
size_t i = 0; i < numPos; ++i) {
346 pos[i] = positions[indexes[i]];
347 nor[i] = normals[indexes[i]];
354 struct RationalReducerContext {
355 std::vector<glm::vec3>& positions;
356 std::vector<glm::vec2>& texCoords;
359 rr_bool_t rrExtractPrimitives(
void* rrHandle,
void* userdata)
361 RationalReducerContext& rrCtx = *
reinterpret_cast<RationalReducerContext*
>(userdata);
363 size_t Nv = rrCtx.positions.size();
364 for (
size_t i = 0; i < Nv; i += 3) {
365 size_t id =
static_cast<size_t>(rrCtx.texCoords[i].x);
366 rr_add_polygon(rrHandle, 3, &rrCtx.positions[i].x, (
void*)
id,
nullptr, RR_REDUCABLE);
371 float rrSameMaterial(
void* triangle1data,
void* triangle2data,
void* )
373 size_t tri1 =
reinterpret_cast<size_t>(triangle1data);
374 size_t tri2 =
reinterpret_cast<size_t>(triangle2data);
375 return tri1 == tri2 ? 0.0f : 1.0f;
378 rr_bool_t rrStuffPrimitives(
void* rrHandle,
void* userdata)
380 RationalReducerContext& rrCtx = *
reinterpret_cast<RationalReducerContext*
>(userdata);
382 size_t Nt = std::max(0, rr_get_num_triangles(rrHandle));
383 rrCtx.positions.resize(3 * Nt);
384 rrCtx.texCoords.resize(3 * Nt);
386 for (
size_t t = 0; t < Nt; t++) {
387 rr_triangle_t* rrtri = rr_get_triangle(rrHandle,
static_cast<int>(t));
388 size_t id =
reinterpret_cast<size_t>(rr_triangle_get_data(rrtri));
389 for (
size_t i = 0; i < 3; i++) {
390 rr_vertex_t* vertex = rr_triangle_get_vertex(rrtri,
static_cast<unsigned int>(i));
391 std::memcpy(&rrCtx.positions[3 * t + i].x, rr_vertex_get_coord(vertex), 3 *
sizeof(
float));
392 rrCtx.texCoords[3 * t + i] = glm::vec2(
static_cast<float>(
id), 0.f);
399 void simplifyCellRationalReducer(CellProcessContext& ctx,
400 std::vector<glm::vec3>& positions,
401 std::vector<glm::vec2>& texCoords,
404 if (positions.empty())
return;
406 RationalReducerContext rrCtx{
407 .positions = positions,
408 .texCoords = texCoords
413 void* rrHandle = rr_init(FALSE);
414 rr_set_extract_primitives(rrHandle, rrExtractPrimitives, &rrCtx);
415 rr_set_stuff_primitives(rrHandle, rrStuffPrimitives, &rrCtx);
416 rr_set_same_material(rrHandle, rrSameMaterial, &rrCtx);
417 rr_set_num_native_triangles(rrHandle,
static_cast<int>(positions.size()/3));
418 rr_set_epsilon(rrHandle, 0.1f);
419 if (ctx.octree.info.edgeLengthWeight != 1.0f) {
420 rr_set_edgelength_weight(rrHandle, ctx.octree.info.edgeLengthWeight);
422 rr_set_percentage(rrHandle, percentage);
424 rr_status_t ret = rr_reduce(rrHandle);
432 case RR_ABORT: LOG_ERROR(logger,
"RR_ABORT");
break;
433 case RR_CALLBACK_ERROR: LOG_ERROR(logger,
"RR_CALLBACK_ERROR");
break;
434 case RR_ERROR: LOG_ERROR(logger,
"RR_ERROR");
break;
435 case RR_NODATA: LOG_ERROR(logger,
"RR_NODATA");
break;
436 case RR_NONUMTRIS: LOG_ERROR(logger,
"RR_NONUMTRIS");
break;
439 assert(
false &&
"Illegal enum");
445 void simplifyCellMeshOptimizer(CellWorkerContext& wCtx,
446 std::vector<glm::vec3>& positions,
447 std::vector<glm::vec2>& texCoords,
451 std::vector<uint32_t>& indices = wCtx.tmpIx2; indices.clear();
455 std::vector<uint32_t>& map = wCtx.map; map.clear();
456 std::vector<uint32_t>& unique = wCtx.unique; unique.clear();
457 GeometryProcessing::uniqueVertexSubset(wCtx.ctx.octree.context, unique, map,
458 glm::value_ptr(positions[0]),
sizeof(glm::vec3),
461 static_cast<uint32_t
>(positions.size()));
463 size_t vertexCount = unique.size();
464 std::vector<glm::vec3>& tmpPos = wCtx.tmpPos; tmpPos.resize(vertexCount);
465 std::vector<glm::vec2>& tmpTex = wCtx.tmpTex; tmpTex.resize(vertexCount);
466 for (
size_t i = 0; i < vertexCount; i++) {
467 const size_t old = unique[i];
468 tmpPos[i] = positions[old];
469 tmpTex[i] = texCoords[old];
471 positions.swap(tmpPos);
472 texCoords.swap(tmpTex);
478 size_t targetIndexCount =
static_cast<size_t>(std::floor(0.01f * percentage * indices.size()));
479 float relativeTolerance = 1.f;
481 size_t outputIndexCount = 0;
482 std::vector<uint32_t>& tmpIx = wCtx.tmpIx; tmpIx.resize(indices.size());
484 outputIndexCount = meshopt_simplifySloppy(tmpIx.data(),
485 indices.data(), indices.size(),
486 glm::value_ptr(positions[0]), positions.size(),
sizeof(positions[0]),
487 targetIndexCount, relativeTolerance,
nullptr);
490 outputIndexCount = meshopt_simplify(tmpIx.data(),
491 indices.data(), indices.size(),
492 glm::value_ptr(positions[0]), positions.size(),
sizeof(positions[0]),
493 targetIndexCount, relativeTolerance, 0,
nullptr);
496 if (outputIndexCount < indices.size()) {
497 tmpIx.resize(outputIndexCount);
503 size_t vertexCount = indices.size();
504 std::vector<glm::vec3>& tmpPos = wCtx.tmpPos; tmpPos.resize(vertexCount);
505 std::vector<glm::vec2>& tmpTex = wCtx.tmpTex; tmpTex.resize(vertexCount);
507 for (
size_t i = 0; i + 2 < vertexCount; i += 3) {
508 float t0 = texCoords[indices[i + 0]].x;
509 float t1 = texCoords[indices[i + 1]].x;
510 float t2 = texCoords[indices[i + 2]].x;
512 if (t0 == t1 || t0 == t2) {
521 for (
size_t k = 0; k < 3; k++) {
522 tmpPos[i + k] = positions[indices[i + k]];
523 tmpTex[i + k] = glm::vec2(tex, 0.f);
526 positions.swap(tmpPos);
527 texCoords.swap(tmpTex);
531 void simplifyCell(CellWorkerContext& wCtx,
533 std::vector<uint32_t>& cellGeoIndices)
535 CellProcessContext& ctx = wCtx.ctx;
537 if (cellGeo.empty())
return;
538 assert(cellGeoIndices.empty());
546 size_t level = wCtx.ctx.level;
550 float reduction = wCtx.ctx.octree.inputs[level].reductionError;
551 const uint32_t dropReductionLevelThreshold = info.dropReductionLevelThreshold;
552 if (level >= dropReductionLevelThreshold && info.dropReductionThreshold != 0) {
553 const uint32_t dropReductionThreshold = info.dropReductionThreshold;
554 const uint32_t minReductionThreshold = info.minReductionThreshold;
555 const uint32_t dropRange = dropReductionThreshold - minReductionThreshold;
556 const size_t vertexCount = cellGeo.size();
557 if (vertexCount < dropReductionThreshold) {
558 float ratio = glm::clamp((
float)(dropReductionThreshold - vertexCount) / (
float)dropRange, 0.0f, 1.0f);
559 reduction = glm::lerp(reduction, 0.0f, ratio);
562 if (reduction <= 0.f)
return;
564 switch (wCtx.ctx.octree.info.levelReducer) {
565 case SparseOctree::LevelReducer::RationalReducerVertexCount:
566 simplifyCellRationalReducer(ctx, cellGeo.pos, cellGeo.tex, reduction);
568 case SparseOctree::LevelReducer::MeshOptimizerVertexCount:
569 simplifyCellMeshOptimizer(wCtx, cellGeo.pos, cellGeo.tex, reduction,
false);
571 case SparseOctree::LevelReducer::MeshOptimizerSloppyVertexCount:
572 simplifyCellMeshOptimizer(wCtx, cellGeo.pos, cellGeo.tex, reduction,
true);
575 assert(
false &&
"Unhandled switch case");
579 if (cellGeo.pos.empty()) {
581 cellGeoIndices.clear();
587 assert(cellGeoIndices.empty());
589 std::vector<uint32_t>& map = wCtx.map;
590 std::vector<uint32_t>& unique = wCtx.unique;
591 std::vector<glm::vec3>& tmpPos = wCtx.tmpPos;
592 std::vector<glm::vec2>& tmpTex = wCtx.tmpTex;
597 GeometryProcessing::uniqueVertexSubset(wCtx.ctx.octree.context, unique, map,
598 glm::value_ptr(cellGeo.pos[0]),
sizeof(glm::vec3),
600 glm::value_ptr(cellGeo.tex[0]),
sizeof(glm::vec2),
601 static_cast<uint32_t
>(cellGeo.size()));
604 size_t vertexCount = unique.size();
605 tmpPos.resize(vertexCount);
606 tmpTex.resize(vertexCount);
607 for (
size_t i = 0; i < vertexCount; i++) {
608 const size_t old = unique[i];
609 tmpPos[i] = cellGeo.pos[old];
610 tmpTex[i] = cellGeo.tex[old];
613 cellGeo.pos.swap(tmpPos);
614 cellGeo.tex.swap(tmpTex);
615 cellGeoIndices.swap(map);
622 std::vector<uint32_t>& unique = wCtx.unique; unique.clear();
623 std::vector<uint32_t>& tmpIx = wCtx.tmpIx; tmpIx.clear();
626 GeometryProcessing::normalsFromIndexedTriangles(wCtx.ctx.octree.context, cellGeo.nrm, unique, tmpIx,
627 glm::value_ptr(cellGeo.pos[0]),
sizeof(cellGeo.pos[0]),
static_cast<uint32_t
>(cellGeo.pos.size()),
628 cellGeoIndices.data(),
static_cast<uint32_t
>(cellGeoIndices.size()),
629 wCtx.ctx.featureAngle,
630 wCtx.ctx.protrusionAngle,
634 size_t vertexCount = unique.size();
635 assert(cellGeo.nrm.size() == vertexCount);
637 std::vector<glm::vec3>& tmpPos = wCtx.tmpPos; tmpPos.resize(vertexCount);
638 std::vector<glm::vec2>& tmpTex = wCtx.tmpTex; tmpTex.resize(vertexCount);
639 for (
size_t i = 0; i < vertexCount; i++) {
640 const size_t old = unique[i];
641 tmpPos[i] = cellGeo.pos[old];
642 tmpTex[i] = cellGeo.tex[old];
645 cellGeo.pos.swap(tmpPos);
646 cellGeo.tex.swap(tmpTex);
647 cellGeoIndices.swap(tmpIx);
652 void pushTriangle(Vertices& out,
const glm::vec3* p,
const glm::vec3* n,
const glm::vec2* t)
654 for (
size_t i = 0; i < 3; i++) {
655 out.pos.emplace_back(p[i]);
656 out.nrm.emplace_back(n[i]);
657 out.tex.emplace_back(t[i]);
661 void pushSplitTriangle(Vertices& out014, Vertices& out1234,
const glm::vec2& tex,
662 const glm::vec3& p0,
const glm::vec3& p1,
const glm::vec3& p2,
const glm::vec3& p3,
const glm::vec3& p4,
663 const glm::vec3& n0,
const glm::vec3& n1,
const glm::vec3& n2,
const glm::vec3& n3,
const glm::vec3& n4)
665 out014.pos.push_back(p0); out014.nrm.push_back(n0); out014.tex.push_back(tex);
666 out014.pos.push_back(p1); out014.nrm.push_back(n1); out014.tex.push_back(tex);
667 out014.pos.push_back(p4); out014.nrm.push_back(n4); out014.tex.push_back(tex);
669 out1234.pos.push_back(p1); out1234.nrm.push_back(n1); out1234.tex.push_back(tex);
670 out1234.pos.push_back(p2); out1234.nrm.push_back(n2); out1234.tex.push_back(tex);
671 out1234.pos.push_back(p3); out1234.nrm.push_back(n3); out1234.tex.push_back(tex);
673 out1234.pos.push_back(p4); out1234.nrm.push_back(n4); out1234.tex.push_back(tex);
674 out1234.pos.push_back(p1); out1234.nrm.push_back(n1); out1234.tex.push_back(tex);
675 out1234.pos.push_back(p3); out1234.nrm.push_back(n3); out1234.tex.push_back(tex);
678 float finiteOrZero(
float x) {
679 return std::isfinite(x) ? x : 0.f;
682 void splitGeometry(CellProcessContext& ctx, Vertices& outA, Vertices& outB,
const Vertices& in,
const glm::vec4& plane)
684 const float epsilon = ctx.clipEpsilon;
685 const size_t count = in.size();
686 for (
size_t i = 0; i + 3 <= count; i += 3) {
687 const glm::vec3* P = &in.pos[i];
688 const glm::vec3* N = &in.nrm[i];
689 const glm::vec2 T = in.tex[i];
690 float d0 = dot(P[0], glm::vec3(plane)) + plane.w;
691 float d1 = dot(P[1], glm::vec3(plane)) + plane.w;
692 float d2 = dot(P[2], glm::vec3(plane)) + plane.w;
699 if (-epsilon <= std::min(d0, std::min(d1, d2))) {
700 pushTriangle(outB, &in.pos[i], &in.nrm[i], &in.tex[i]);
702 else if (std::max(d0, std::max(d1, d2)) <= epsilon) {
703 pushTriangle(outA, &in.pos[i], &in.nrm[i], &in.tex[i]);
709 const int situation = (d0 < 0.f ? 1 : 0) + (d1 < 0.f ? 2 : 0) + (d2 < 0.f ? 4 : 0);
711 float w_01 = finiteOrZero(std::abs(d0) / (std::abs(d0) + std::abs(d1)));
712 glm::vec3 p_01 = (1.f - w_01) * P[0] + w_01 * P[1];
713 glm::vec3 n_01 = (1.f - w_01) * N[0] + w_01 * N[1];
715 float w_12 = finiteOrZero(std::abs(d1) / (std::abs(d1) + std::abs(d2)));
716 glm::vec3 p_12 = (1.f - w_12) * P[1] + w_12 * P[2];
717 glm::vec3 n_12 = (1.f - w_12) * N[1] + w_12 * N[2];
719 float w_20 = finiteOrZero(std::abs(d2) / (std::abs(d2) + std::abs(d0)));
720 glm::vec3 p_20 = (1.f - w_20) * P[2] + w_20 * P[0];
721 glm::vec3 n_20 = (1.f - w_20) * N[2] + w_20 * N[0];
725 pushSplitTriangle(outA, outB, T,
726 P[0], p_01, P[1], P[2], p_20,
727 N[0], n_01, N[1], N[2], n_20);
730 pushSplitTriangle(outA, outB, T,
731 P[1], p_12, P[2], P[0], p_01,
732 N[1], n_12, N[2], N[0], n_01);
735 pushSplitTriangle(outB, outA, T,
736 P[2], p_20, P[0], P[1], p_12,
737 N[2], n_20, N[0], N[1], n_12);
740 pushSplitTriangle(outA, outB, T,
741 P[2], p_20, P[0], P[1], p_12,
742 N[2], n_20, N[0], N[1], n_12);
745 pushSplitTriangle(outB, outA, T,
746 P[1], p_12, P[2], P[0], p_01,
747 N[1], n_12, N[2], N[0], n_01);
750 pushSplitTriangle(outB, outA, T,
751 P[0], p_01, P[1], P[2], p_20,
752 N[0], n_01, N[1], N[2], n_20);
765 void writeModelFile(CellProcessContext& ctx,
const std::string& path, std::span<MeshHandle> meshes)
767 size_t N = meshes.size();
771 model.meshes.assign(meshes.begin(), meshes.end());
773 if (ctx.octree.info.colorizeLevels) {
774 model.materials.emplace_back(ctx.materialInstance);
777 model.parts.resize(N);
778 for (
size_t i = 0; i < N; i++) {
780 part.parentIndex = NoParentPart;
781 part.boundsIndex = NoIndex;
782 part.meshIndex =
static_cast<uint32_t
>(i);
783 if (ctx.octree.info.colorizeLevels) {
784 part.materialIndex = 0;
789 uint32_t numVertexes;
790 bool rv = writeModel(ctx.octree.context, numIndexes, numVertexes, path, &model, &ctx.settings);
794 void recordMesh(CellWorkerContext& wCtx,
SparseOctree::Cell& cell,
const Vertices& cellGeo,
const std::vector<uint32_t>& cellGeoIndices = std::vector<uint32_t>())
796 CellProcessContext& ctx = wCtx.ctx;
799 glm::vec3 bbMin = glm::vec3(std::numeric_limits<float>::max());
800 glm::vec3 bbMax = -glm::vec3(std::numeric_limits<float>::max());
801 for (
const glm::vec3& p : cellGeo.pos) {
802 bbMin = glm::min(bbMin, p);
803 bbMax = glm::max(bbMax, p);
807 size_t meshVertexCount = 0;
808 MeshHandle mesh = ctx.octree.context->meshManager->create();
812 if (cellGeoIndices.empty()) {
813 meshVertexCount = cellGeo.size();
816 meshVertexCount = cellGeoIndices.size();
821 if (ctx.maxModelVertexCount && (meshVertexCount < ctx.maxModelVertexCount)) {
829 std::string stem =
"S_" + std::to_string(ctx.fileCount.fetch_add(1)) +
".cogsbin";
830 std::string absolutePath = Cogs::IO::combine(ctx.filenamePrefix, stem);
831 std::string relativePath = Cogs::IO::combine(ctx.relativePrefix, stem);
833 writeModelFile(wCtx.ctx, absolutePath, std::span<MeshHandle>(&mesh, 1));
839 void createCellMesh(CellWorkerContext& wCtx,
841 const Vertices& cellGeo,
842 const std::vector<uint32_t>& cellGeoIndices)
844 assert(cellGeo.pos.size() == cellGeo.nrm.size());
845 assert(cellGeo.pos.size() == cellGeo.tex.size());
846 if (cellGeo.pos.empty())
return;
848 CellProcessContext& ctx = wCtx.ctx;
850 size_t vertexCount = cellGeoIndices.empty() ? cellGeo.size() : cellGeoIndices.size();
851 cell.vertexCount =
static_cast<uint32_t
>(vertexCount);
852 cell.
sizeEstimate =
static_cast<uint32_t
>(2 *
sizeof(glm::vec3) +
sizeof(glm::vec2) * cellGeo.pos.size() +
853 sizeof(uint32_t) * cellGeoIndices.size());
857 if (ctx.maxMeshVertexCount == 0 || vertexCount < ctx.maxMeshVertexCount) {
858 recordMesh(wCtx, cell, cellGeo, cellGeoIndices);
864 std::vector<SplitStackItem> splitStack;
865 if (cellGeoIndices.empty()) {
866 splitStack.push_back({ .geo = cellGeo });
869 splitStack.emplace_back();
870 const size_t N = cellGeoIndices.size();
871 Vertices& out = splitStack.back().geo;
873 for (
size_t i = 0; i < N; i++) {
874 out.pos[i] = cellGeo.pos[cellGeoIndices[i]];
875 out.nrm[i] = cellGeo.nrm[cellGeoIndices[i]];
876 out.tex[i] = cellGeo.tex[cellGeoIndices[i]];
881 while (!splitStack.empty()) {
882 SplitStackItem item = std::move(splitStack.back());
883 splitStack.pop_back();
889 glm::vec3 bbMin = glm::vec3(std::numeric_limits<float>::max());
890 glm::vec3 bbMax = glm::vec3(-std::numeric_limits<float>::max());
891 for (
const glm::vec3& p : item.geo.pos) {
892 bbMin = glm::min(bbMin, p);
893 bbMax = glm::max(bbMax, p);
897 Splits& splits = wCtx.splits;
898 for (Split& split : splits) {
899 split.sides[0].clear();
900 split.sides[1].clear();
905 size_t countIn = item.geo.size();
906 size_t bestCount = std::numeric_limits<size_t>::max();
907 for (
int k = 0; k < 3; k++) {
908 glm::vec4 plane(0.f, 0.f, 0.f, -0.5f * (bbMin[k] + bbMax[k]));
910 splitGeometry(ctx, splits[k].sides[0], splits[k].sides[1], item.geo, plane);
912 size_t countA = splits[k].sides[0].size();
913 size_t countB = splits[k].sides[1].size();
914 size_t count = countA + countB;
930 recordMesh(wCtx, cell, item.geo);
934 Split& bestSplit = splits[bestIx];
935 for (
size_t k = 0; k < 2; k++) {
936 Vertices& side = bestSplit.sides[k];
939 if (side.size() < ctx.maxMeshVertexCount || ctx.maxSplitDepth <= item.depth) {
940 recordMesh(wCtx, cell, side);
945 splitStack.push_back({ .geo = std::move(side), .depth = item.depth + 1 });
951 void processCellWorkerFunc(CellProcessContext* ctx_,
size_t )
953 CellWorkerContext wCtx{
956 CellProcessContext& ctx = wCtx.ctx;
958 std::vector<Mesh*> clipped;
959 std::vector<Mesh*> contained;
961 SharedModel modelReadyForWriting;
963 std::vector<uint32_t> cell_indices;
965 const size_t cellCount = ctx.cellCount;
968 size_t item = ctx.processedCells.fetch_add(1);
969 if (ctx.cellCount <= item)
break;
977 filterCells(clipped, contained, cell.
bbox, ctx.scene, cell.
touches);
981 cell_indices.clear();
982 clipMeshes(wCtx, cellGeo, cell.
bbox, clipped);
983 appendMeshes(cellGeo, contained);
984 simplifyCell(wCtx, cellGeo, cell_indices);
985 createCellMesh(wCtx, cell, cellGeo, cell_indices);
992 auto& writeCells = ctx.writeCells;
993 std::unique_lock guard(writeCells.lock);
996 assert(writeCells.cellsDone[item] ==
false);
997 writeCells.cellsDone[item] =
true;
1000 while (writeCells.cellsWritten < cellCount && writeCells.cellsDone[writeCells.cellsWritten]) {
1002 SharedModel& sharedModel = ctx.writeCells.model;
1004 if (!ctx.workItems[writeCells.cellsWritten].cell->meshRecs.empty()) {
1012 size_t vertexCount = meshRec.mesh->
getCount();
1013 if (!sharedModel.meshes.empty())
1015 bool tooManyVerticesInFile = ctx.maxModelVertexCount < sharedModel.vertexCount + vertexCount;
1016 bool tooManyMeshesInFile = ctx.maxModelMeshCount && (ctx.maxModelMeshCount < (sharedModel.meshes.size() + 1));
1017 if (tooManyVerticesInFile || tooManyMeshesInFile) {
1018 std::swap(sharedModel, modelReadyForWriting);
1019 assert(sharedModel.meshes.empty() && sharedModel.absolutePath.empty() && sharedModel.relativePath.empty() && sharedModel.vertexCount == 0);
1024 if (sharedModel.meshes.empty()) {
1025 std::string stem =
"M_" + std::to_string(ctx.fileCount.fetch_add(1)) +
".cogsbin";
1026 sharedModel.absolutePath = Cogs::IO::combine(ctx.filenamePrefix, stem);
1027 sharedModel.relativePath = Cogs::IO::combine(ctx.relativePrefix, stem);
1031 uint32_t part =
static_cast<uint32_t
>(sharedModel.meshes.size());
1032 sharedModel.meshes.emplace_back(std::move(meshRec.mesh));
1033 sharedModel.vertexCount += vertexCount;
1040 writeCells.cellsWritten++;
1045 if (writeCells.cellsWritten == cellCount) {
1048 if (!sharedModel.meshes.empty()) {
1049 std::swap(modelReadyForWriting, sharedModel);
1055 if (!modelReadyForWriting.meshes.empty()) {
1059 writeModelFile(ctx, modelReadyForWriting.absolutePath, std::span<MeshHandle>(modelReadyForWriting.meshes));
1060 modelReadyForWriting.meshes.clear();
1061 modelReadyForWriting.relativePath.clear();
1062 modelReadyForWriting.absolutePath.clear();
1063 modelReadyForWriting.vertexCount = 0;
1075void Cogs::Core::SparseOctree::distributeGeometryWithIds(Octree & octree,
PropertyStore& properties,
const size_t level)
1077 CellProcessContext ctx{
1079 .grid = octree.grids[level],
1080 .scene = octree.scenes[level],
1081 .relativePrefix = Cogs::IO::combine(octree.info.name, std::to_string(level)),
1082 .filenamePrefix = IO::combine(octree.info.filesDirectory, std::to_string(level)),
1084 .cellCount = octree.grids[level].cellStore.size(),
1085 .processedCells = 0,
1086 .maxMeshVertexCount =
static_cast<size_t>(octree.info.maxMeshVertexCount < 1 ? 0 : std::max(1000, octree.info.maxMeshVertexCount)),
1087 .maxModelVertexCount =
static_cast<size_t>(std::max(0, octree.info.maxModelVertexCount)),
1088 .maxModelMeshCount =
static_cast<size_t>(std::max(0, octree.info.maxModelMeshCount))
1091 if (ctx.octree.info.useCompression) {
1092 ctx.settings.flags |= WriteModelFlags::COMPRESS_ZSTD;
1093 ctx.settings.compressionLevel = octree.info.compressionLevel;
1095 ctx.featureAngle = properties.getProperty(
"featureAngle", ctx.featureAngle);
1097 IO::createDirectories(IO::combine(ctx.filenamePrefix,
"dummy"));
1099 if (octree.info.colorizeLevels) {
1100 MaterialHandle material = octree.context->materialManager->getDefaultMaterial();
1101 ctx.materialInstance = octree.context->materialInstanceManager->createMaterialInstance(material);
1103 float hue = glm::fract(1.61803398875f * level);
1104 ctx.materialInstance->setVec4Property(material->getVec4Key(
"diffuseColor"), glm::vec4(glm::rgbColor(glm::vec3(360.f * hue, 1.f, 1.f)), 1.f));
1106 ctx.materialInstance->setVec4Property(material->getVec4Key(
"diffuseColor"),
1107 glm::vec4(((level >> 2) & 1) ? 1.f : 0.5f,
1108 ((level >> 1) & 1) ? 1.f : 0.5f,
1109 ((level >> 0) & 1) ? 1.f : 0.5f,
1118 ctx.writeCells.cellsDone.resize(ctx.cellCount,
false);
1119 ctx.workItems.reserve(ctx.cellCount);
1120 for (
size_t i = 0; i < ctx.cellCount; i++) {
1121 Cell& cell = ctx.grid.cellStore[i];
1122 ctx.workItems.emplace_back(WorkItem{ .sortKey =
mortonCode(
static_cast<uint16_t
>(cell.x),
1123 static_cast<uint16_t
>(cell.y),
1124 static_cast<uint16_t
>(cell.z)),
1127 std::sort(ctx.workItems.begin(), ctx.workItems.end(), [](
const WorkItem& a,
const WorkItem& b) ->
bool {
1128 if (a.sortKey < b.sortKey) return true;
1129 else if (b.sortKey < a.sortKey) return false;
1130 else if (a.cell->x < b.cell->x) return true;
1131 else if (b.cell->x < a.cell->x) return false;
1132 else if (a.cell->y < b.cell->y) return true;
1133 else if (b.cell->y < a.cell->y) return false;
1134 else if (a.cell->z < b.cell->z) return true;
1135 else if (b.cell->z < a.cell->z) return false;
1139 LOG_DEBUG(logger,
"Level %zu: Sorting occupied cells into Z-order: %.2fs ", level, timer.elapsedSeconds());
1144 size_t hwc = std::min(ctx.cellCount,
static_cast<size_t>(std::thread::hardware_concurrency()));
1145 std::vector<std::thread> workers;
1146 workers.reserve(hwc);
1147 for (
size_t i = 0; i < hwc; i++) {
1148 workers.emplace_back(std::thread(processCellWorkerFunc, &ctx, i));
1151 while (ctx.processedCells.load() < ctx.cellCount) {
1152 std::this_thread::sleep_for(std::chrono::milliseconds(500));
1153 Cogs::Core::runFrames(ctx.octree.context, 1);
1156 for (std::thread& thread : workers) {
1160 LOG_DEBUG(logger,
"Level %zu: Clipping, processing and writing models: %.2fs, cells=%zu, threads=%zu ", level, timer.elapsedSeconds(), ctx.cellCount, hwc);
1165uint32_t Cogs::Core::SparseOctree::clipTriangle(
float* V_dst,
float *N_dst,
float *T_dst,
const float (&clipBox)[6],
const float *V_in,
const float * N_in,
const float *T_in)
1167 auto * tmp_a = &clipA;
1168 auto * tmp_b = &clipB;
1171 for (
unsigned k = 0; k < 3 * 3; k++) {
1172 tmp_a->V[k] = V_in[k];
1173 tmp_a->N[k] = N_in[k];
1175 for (
unsigned k = 0; k < 3 * 2; k++) {
1176 tmp_a->T[k] = T_in[k];
1183 for (
unsigned o = 0; o < 2; o++) {
1184 for (
unsigned a = 0; a < 3; a++) {
1187 unsigned ip = N_a - 1;
1190 float dp = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * ip + a] - clipBox[3 * o + a]);
1192 for (
unsigned in = 0; in < N_a; in++) {
1195 float dn = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * in + a] - clipBox[3 * o + a]);
1197 bool dp_neg = dp < 0.f;
1198 bool dn_neg = dn < 0.f;
1201 if (dp_neg != dn_neg) {
1204 auto wa = std::abs(dn / (dp - dn));
1205 auto wb = std::abs(dp / (dp - dn));
1206 if (!std::isfinite(wa) || !std::isfinite(wb)) {
1211 for (
unsigned k = 0; k < 3; k++) {
1212 tmp_b->V[3 * N_b + k] = wa * tmp_a->V[3 * ip + k] + wb * tmp_a->V[3 * in + k];
1213 tmp_b->N[3 * N_b + k] = wa * tmp_a->N[3 * ip + k] + wb * tmp_a->N[3 * in + k];
1216 for (
unsigned k = 0; k < 2; k++) {
1217 tmp_b->T[2 * N_b + k] = tmp_a->T[2 * ip + k];
1225 for (
unsigned k = 0; k < 3; k++) {
1226 tmp_b->V[3 * N_b + k] = tmp_a->V[3 * in + k];
1227 tmp_b->N[3 * N_b + k] = tmp_a->N[3 * in + k];
1229 for (
unsigned k = 0; k < 2; k++) {
1230 tmp_b->T[2 * N_b + k] = tmp_a->T[2 * in + k];
1247 std::swap(tmp_a, tmp_b);
1253 for (
unsigned i = 1; i + 1 < N_a; i++) {
1255 for (
unsigned k = 0; k < 3; k++) {
1256 *V_dst++ = tmp_a->V[3 * (0) + k];
1257 *N_dst++ = tmp_a->N[3 * (0) + k];
1259 for (
unsigned k = 0; k < 2; k++) {
1260 *T_dst++ = tmp_a->T[2 * (0) + k];
1262 for (
unsigned k = 0; k < 3; k++) {
1263 *V_dst++ = tmp_a->V[3 * (i + 0) + k];
1264 *N_dst++ = tmp_a->N[3 * (i + 0) + k];
1266 for (
unsigned k = 0; k < 2; k++) {
1267 *T_dst++ = tmp_a->T[2 * (i + 0) + k];
1269 for (
unsigned k = 0; k < 3; k++) {
1270 *V_dst++ = tmp_a->V[3 * (i + 1) + k];
1271 *N_dst++ = tmp_a->N[3 * (i + 1) + k];
1273 for (
unsigned k = 0; k < 2; k++) {
1274 *T_dst++ = tmp_a->T[2 * (i + 1) + k];
1281uint32_t Cogs::Core::SparseOctree::clipTriangle(
float * V_dst,
float * N_dst,
const float (&clipBox)[6],
const float * V_in,
const float * N_in)
1283 auto * tmp_a = &clipA;
1284 auto * tmp_b = &clipB;
1287 for (
unsigned k = 0; k < 3 * 3; k++) {
1288 tmp_a->V[k] = V_in[k];
1289 tmp_a->N[k] = N_in[k];
1296 for (
unsigned o = 0; o < 2; o++) {
1297 for (
unsigned a = 0; a < 3; a++) {
1300 unsigned ip = N_a - 1;
1303 float dp = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * ip + a] - clipBox[3 * o + a]);
1305 for (
unsigned in = 0; in < N_a; in++) {
1308 float dn = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * in + a] - clipBox[3 * o + a]);
1310 bool dp_neg = dp < 0.f;
1311 bool dn_neg = dn < 0.f;
1314 if (dp_neg != dn_neg) {
1317 auto wa = std::abs(dn / (dp - dn));
1318 auto wb = std::abs(dp / (dp - dn));
1319 if (!std::isfinite(wa) || !std::isfinite(wb)) {
1324 for (
unsigned k = 0; k < 3; k++) {
1325 tmp_b->V[3 * N_b + k] = wa * tmp_a->V[3 * ip + k] + wb * tmp_a->V[3 * in + k];
1326 tmp_b->N[3 * N_b + k] = wa * tmp_a->N[3 * ip + k] + wb * tmp_a->N[3 * in + k];
1335 for (
unsigned k = 0; k < 3; k++) {
1336 tmp_b->V[3 * N_b + k] = tmp_a->V[3 * in + k];
1337 tmp_b->N[3 * N_b + k] = tmp_a->N[3 * in + k];
1354 std::swap(tmp_a, tmp_b);
1360 for (
unsigned i = 1; i + 1 < N_a; i++) {
1362 for (
unsigned k = 0; k < 3; k++) {
1363 *V_dst++ = tmp_a->V[3 * (0) + k];
1364 *N_dst++ = tmp_a->N[3 * (0) + k];
1366 for (
unsigned k = 0; k < 3; k++) {
1367 *V_dst++ = tmp_a->V[3 * (i + 0) + k];
1368 *N_dst++ = tmp_a->N[3 * (i + 0) + k];
1370 for (
unsigned k = 0; k < 3; k++) {
1371 *V_dst++ = tmp_a->V[3 * (i + 1) + k];
1372 *N_dst++ = tmp_a->N[3 * (i + 1) + k];
1381namespace Cogs::Core::SparseOctree
1383 void distributeGeometryWithIds(Octree & ,
PropertyStore& ,
const size_t ) {}
1385 uint32_t clipTriangle(
float* V_dst,
float *N_dst,
float *T_dst,
const float(&clipBox)[6],
const float *V_in,
const float * N_in,
const float *T_in) {
return 0; }
1386 uint32_t clipTriangle(
float* V_dst,
float *N_dst,
const float(&clipBox)[6],
const float *V_in,
const float * N_in) {
return 0; }
Log implementation class.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
@ TextureCoordinate
Texture coordinate semantic.
constexpr uint64_t mortonCode(uint16_t i, uint16_t j, uint16_t k)
Interleave bits of 3 values to form the 3-way Morton code.
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
void setIndexes(std::span< const uint32_t > collection)
Set the index data to the collection given.
void setTexCoords(std::span< const glm::vec2 > texCoords)
Set the texture coordinate data of the Mesh.
StreamReference getPositionStream()
Get the data of the stream containing positions.
void setNormals(std::span< const glm::vec3 > normals)
Set the normal data of the Mesh.
StreamReference getSemanticStream(ElementSemantic semantic, DataFormat format)
Get the data of the stream containing data with the given semantic, format and minimum element size.
uint32_t getCount() const
Get the vertex count of the mesh.
void setPositions(std::span< const glm::vec3 > positions)
Set the position data 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::vector< uint32_t > touches
Entity index of parts that touches the cell.
uint32_t sizeEstimate
Estimated filesize of cell geometry, used to determine if geometry of multiple cells should be packed...
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::vector< MeshRec > meshRecs
Meshes not yet written to disc.
std::string fileName
File name of cogsbin file with processed geometry.
@ TriangleList
List of triangles.