Cogs.Core
SparseBuildOctree.cpp
1#include "SparseBuildOctree.h"
2#include "Batch.h"
3
4#include "Resources/Mesh.h"
5#include "Resources/MeshManager.h"
6#include "Resources/Model.h"
7#include "Resources/MaterialManager.h"
8
9#include "Utilities/Parallel.h"
10#include "Serialization/ModelWriter.h"
11
12#include "Foundation/Platform/IO.h"
13#include "Foundation/Platform/Timer.h"
14#include "Foundation/BitTwiddling/MortonCode.h"
15#include "Foundation/Logging/Logger.h"
16
17#include "../Extensions/GeometryProcessing/GeometryProcessing.h"
18
19#if !defined( EMSCRIPTEN ) && !defined( __APPLE__ )
20
21#include <span>
22#include <mutex>
23#include <atomic>
24#include <xmmintrin.h>
25#include <glm/gtx/color_space.hpp>
26#include <glm/gtx/compatibility.hpp>
27
28#ifdef HAS_RATIONAL_REDUCER
29#include <rrapi/rrapi.h>
30#endif
31
32#include <meshoptimizer/meshoptimizer.h>
33
34
35namespace
36{
37 using namespace Cogs::Core;
38 using Cogs::Geometry::BoundingBox;
39
40 Cogs::Logging::Log logger = Cogs::Logging::getLogger("SparseBuildOctree");
41
42
43 struct WorkItem {
44 uint64_t sortKey;
46 };
47
48 struct SharedModel {
49 std::vector<MeshHandle> meshes;
50 std::string relativePath;
51 std::string absolutePath;
52 size_t vertexCount = 0;
53 };
54
55 struct CellProcessContext
56 {
60 std::string relativePrefix;
61 std::string filenamePrefix;
62 MaterialInstanceHandle materialInstance;
63 WriteModelSettings settings{};
64 size_t level;
65 size_t cellCount = 0;
66 std::atomic<size_t> processedCells = 0;
67 std::atomic<size_t> fileCount = 0;
68
69 std::vector<WorkItem> workItems;
70
71 struct {
72 std::mutex lock;
73 std::vector<bool> cellsDone;
74 SharedModel model;
75 size_t cellsWritten = 0;
76 } writeCells;
77
78 float vertexMergeEpsilon = 0.0001f; // One tenth of a millimeter
79 float clipEpsilon = 0.01f; // One centimeter
80 float featureAngle = 0.7f;
81 float protrusionAngle = 0.7f;
82 size_t maxMeshVertexCount; // The number of vertices a mesh can have before we start to split it
83 size_t maxModelVertexCount; // Meshes with more vertices will get its own model file
84 size_t maxModelMeshCount; // Max number of small meshes combined in one cogsbin file.
85 size_t maxSplitDepth = 8; // Safety valve for big-mesh-in-cell-split-recursion.
86 };
87
88 struct Vertices {
89 std::vector<glm::vec3> pos;
90 std::vector<glm::vec3> nrm;
91 std::vector<glm::vec2> tex;
92
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()); }
98 };
99
100 struct SplitStackItem {
101 Vertices geo;
102 size_t depth = 0;
103 };
104
105 struct Split {
106 Vertices sides[2];
107 };
108 using Splits = Split[3];
109
110 // Per thread data
111 struct CellWorkerContext
112 {
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;
123
124 Splits splits;
125
126 struct {
127 std::vector<MeshHandle> meshes;
128 std::string relativePath;
129 std::string absolutePath;
130 size_t vertexCount = 0;
131 } smallMeshesModelFile;
132
133 };
134
135 unsigned calculateClippingMask(glm::vec3 const& position, __m128 const& clipBoxMin, __m128 const& clipBoxMax)
136 {
137 auto pos = _mm_set_ps(0.0f, position.z, position.y, position.x);
138
139 auto cmp1 = _mm_cmplt_ps(pos, clipBoxMax);
140 auto cmp2 = _mm_cmplt_ps(clipBoxMin, pos);
141
142 auto mask1 = _mm_movemask_ps(cmp1) & 0x7;
143 auto mask2 = _mm_movemask_ps(cmp2) & 0x7;
144
145 return mask1 | (mask2 << 3);
146 }
147
148 struct Geom
149 {
150 float* V;
151 float* N;
152 float* T;
153
154 std::vector<float> Vv;
155 std::vector<float> Nn;
156 std::vector<float> Tt;
157
158 void reserve(size_t size)
159 {
160 Vv.resize(size * 3 * 3);
161 Nn.resize(size * 3 * 3);
162 Tt.resize(size * 3 * 2);
163
164 V = Vv.data();
165 N = Nn.data();
166 T = Tt.data();
167 }
168 };
169
170 thread_local Geom clipA;
171 thread_local Geom clipB;
172
173 void filterCells(std::vector<Mesh*>& clipped,
174 std::vector<Mesh*>& contained,
175 const Cogs::Geometry::BoundingBox& geoClip,
176 const SparseOctree::SceneInfo& scene,
177 const std::vector<uint32_t>& touches)
178 {
179 for (uint32_t index : touches) {
180 const SparseOctree::EntityRef& e = scene.entities[index];
181 Mesh* mesh = e.mesh.resolve();
182
183 if (mesh->primitiveType != Cogs::PrimitiveType::TriangleList) continue;
184
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))
192 {
193 contained.push_back(mesh);
194 }
195 else {
196 clipped.push_back(mesh);
197 }
198 }
199 }
200
201 void clipMeshes(CellWorkerContext& wCtx,
202 Vertices& cellGeo,
203 const Cogs::Geometry::BoundingBox& geoClip,
204 const std::vector<Mesh*>& clipped)
205 {
206 std::vector<glm::vec3>& clippedV = wCtx.clippedV;
207 std::vector<glm::vec3>& clippedN = wCtx.clippedN;
208 std::vector<glm::vec2>& clippedT = wCtx.clippedT;
209
210 for (Mesh* mesh : clipped) {
211
212 std::span<const uint32_t> indexes = mesh->getIndexes();
213 auto tris = indexes.size() / 3;
214
215 auto [pData, pSize, pStride] = mesh->getPositionStream();
216 assert(pStride == sizeof(glm::vec3));
217 const glm::vec3* positions = (glm::vec3*)pData;
218
219 auto [nData, nSize, nStride] = mesh->getSemanticStream(Cogs::ElementSemantic::Normal, Cogs::DataFormat::X32Y32Z32_FLOAT);
220 const glm::vec3* normals = (glm::vec3*)nData;
221
222 auto [tData, tSize, tStride] = mesh->getSemanticStream(Cogs::ElementSemantic::TextureCoordinate, Cogs::DataFormat::X32Y32_FLOAT);
223 const glm::vec2* texCoords = (glm::vec2*)tData;
224
225
226 // Translate clip-box to origin of geometry
227 float clipBox[6] = { geoClip.min.x, geoClip.min.y, geoClip.min.z, geoClip.max.x, geoClip.max.y, geoClip.max.z };
228
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);
231
232 // We start with three vertices, and for each half-plane, we trade either one or two vertices for two,
233 // i.e., a maximally net increase of 1 vertex per clipping plane.
234 clipA.reserve(3 + 6);
235 clipB.reserve(3 + 6);
236
237 uint32_t N_t = 0;
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]],
243 };
244
245 glm::vec3 N_in[3] = {
246 normals[indexes[t * 3 + 0]],
247 normals[indexes[t * 3 + 1]],
248 normals[indexes[t * 3 + 2]],
249 };
250
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),
255 };
256
257 unsigned masks[3] = { 0,0,0 };
258 for (unsigned k = 0; k < 3; k++) {
259 masks[k] = calculateClippingMask(V_in[k], clipBoxMin, clipBoxMax);
260 }
261
262 // When clipping against one halfplane, a triangle spawns maximally two triangles.
263 // And with six halfplanes, an upper bound is 12 times the input triangles.
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);
268 }
269
270 auto V_dst = &clippedV[N_t * 3];
271 auto N_dst = &clippedN[N_t * 3];
272 auto T_dst = &clippedT[N_t * 3];
273
274 if ((masks[0] & masks[1] & masks[2]) == 63) {
275 V_dst[0] = V_in[0];
276 V_dst[1] = V_in[1];
277 V_dst[2] = V_in[2];
278
279 N_dst[0] = N_in[0];
280 N_dst[1] = N_in[1];
281 N_dst[2] = N_in[2];
282
283 T_dst[0] = T_in[0];
284 T_dst[1] = T_in[1];
285 T_dst[2] = T_in[2];
286
287 N_t++;
288 }
289 else if ((masks[0] | masks[1] | masks[2]) == 63) {
290 // partially inside, needs clipping
291 N_t += SparseOctree::clipTriangle((float*)V_dst, (float*)N_dst, (float*)T_dst, clipBox, (float*)V_in, (float*)N_in, (float*)T_in);
292 }
293 else {
294 // completely outside
295 }
296 }
297
298 size_t begin = cellGeo.size();
299
300 size_t numPos = N_t * 3;
301 cellGeo.resize(begin + numPos);
302
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);
306 }
307 }
308
309 void appendMeshes(Vertices& cellGeo,
310 const std::vector<Mesh*>& contained)
311 {
312 for (Mesh* mesh : contained) {
313
314 auto [pData, pSize, pStride] = mesh->getPositionStream();
315 assert(pStride == sizeof(glm::vec3));
316 auto positions = (glm::vec3*)pData;
317
318 auto indexes = mesh->getIndexes();
319
320 if (!pData) continue;
321
322 auto [nData, nSize, nStride] = mesh->getSemanticStream(Cogs::ElementSemantic::Normal, Cogs::DataFormat::X32Y32Z32_FLOAT);
323 auto normals = (glm::vec3*)nData;
324
325 auto [tData, tSize, tStride] = mesh->getSemanticStream(Cogs::ElementSemantic::TextureCoordinate, Cogs::DataFormat::X32Y32_FLOAT);
326 auto texCoords = (glm::vec2*)tData;
327
328 auto numPos = mesh->getCount();
329 size_t begin = cellGeo.size();
330
331 cellGeo.resize(begin + numPos);
332
333 glm::vec3* pos = cellGeo.pos.data() + begin;
334 glm::vec3* nor = cellGeo.nrm.data() + begin;
335 glm::vec2* tex = cellGeo.tex.data() + begin;
336
337 if (texCoords) {
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]];
342 }
343 }
344 else {
345 for (size_t i = 0; i < numPos; ++i) {
346 pos[i] = positions[indexes[i]];
347 nor[i] = normals[indexes[i]];
348 }
349 }
350 }
351 }
352
353
354 struct RationalReducerContext {
355 std::vector<glm::vec3>& positions;
356 std::vector<glm::vec2>& texCoords;
357 };
358
359 rr_bool_t rrExtractPrimitives(void* rrHandle, void* userdata)
360 {
361 RationalReducerContext& rrCtx = *reinterpret_cast<RationalReducerContext*>(userdata);
362
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);
367 }
368 return TRUE;
369 }
370
371 float rrSameMaterial(void* triangle1data, void* triangle2data, void* /*userdata*/)
372 {
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;
376 }
377
378 rr_bool_t rrStuffPrimitives(void* rrHandle, void* userdata)
379 {
380 RationalReducerContext& rrCtx = *reinterpret_cast<RationalReducerContext*>(userdata);
381
382 size_t Nt = std::max(0, rr_get_num_triangles(rrHandle));
383 rrCtx.positions.resize(3 * Nt);
384 rrCtx.texCoords.resize(3 * Nt);
385 if (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);
393 }
394 }
395 }
396 return TRUE;
397 }
398
399 void simplifyCellRationalReducer(CellProcessContext& ctx,
400 std::vector<glm::vec3>& positions,
401 std::vector<glm::vec2>& texCoords,
402 float percentage)
403 {
404 if (positions.empty()) return;
405
406 RationalReducerContext rrCtx{
407 .positions = positions,
408 .texCoords = texCoords
409 };
410
411 //size_t Nt1 = positions.size() / 3;
412
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);
421 }
422 rr_set_percentage(rrHandle, percentage);
423
424 rr_status_t ret = rr_reduce(rrHandle);
425 rr_end(rrHandle);
426 //size_t Nt2 = positions.size() / 3;
427
428 if (ret != RR_OK) {
429 positions.clear();
430 texCoords.clear();
431 switch (ret) {
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;
437 case RR_OK: break;
438 default:
439 assert(false && "Illegal enum");
440 }
441 }
442 //LOG_DEBUG(logger, "reduction=%.2f%%, Nt=%zu => %zu (%.2f%%)", percentage, Nt1, Nt2, (100.0 * Nt2) / Nt1);
443 }
444
445 void simplifyCellMeshOptimizer(CellWorkerContext& wCtx,
446 std::vector<glm::vec3>& positions,
447 std::vector<glm::vec2>& texCoords,
448 float percentage,
449 bool sloppy)
450 {
451 std::vector<uint32_t>& indices = wCtx.tmpIx2; indices.clear();
452
453 {
454 // Make vertices unique. Do not consider texcoords for this (we will let one unique vertex have a texcoord)
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),
459 nullptr, 0,
460 nullptr, 0,
461 static_cast<uint32_t>(positions.size()));
462
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];
470 }
471 positions.swap(tmpPos);
472 texCoords.swap(tmpTex);
473 indices.swap(map); // since initial mesh is un-indexed, indices are just the map table
474 }
475
476 { // Simplification reduces the amount if indices
477
478 size_t targetIndexCount = static_cast<size_t>(std::floor(0.01f * percentage * indices.size()));
479 float relativeTolerance = 1.f;
480
481 size_t outputIndexCount = 0;
482 std::vector<uint32_t>& tmpIx = wCtx.tmpIx; tmpIx.resize(indices.size());
483 if (sloppy) {
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);
488 }
489 else {
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, /*options*/0, nullptr);
494 }
495 //LOG_DEBUG(logger, "%zu -> %zu, percentage=%.2f", indices.size(), outputIndexCount, percentage);
496 if (outputIndexCount < indices.size()) {
497 tmpIx.resize(outputIndexCount);
498 indices.swap(tmpIx);
499 }
500 }
501
502 { // Create unindexed triangles with identical texcoords on the corners
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);
506
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;
511 float tex;
512 if (t0 == t1 || t0 == t2) {
513 tex = t0;
514 }
515 else if (t1 == t2) {
516 tex = t1;
517 }
518 else {
519 tex = t0;
520 }
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);
524 }
525 }
526 positions.swap(tmpPos);
527 texCoords.swap(tmpTex);
528 }
529 }
530
531 void simplifyCell(CellWorkerContext& wCtx,
532 Vertices& cellGeo,
533 std::vector<uint32_t>& cellGeoIndices)
534 {
535 CellProcessContext& ctx = wCtx.ctx;
536
537 if (cellGeo.empty()) return;
538 assert(cellGeoIndices.empty()); // Mesh are assumed to be non-indexed.
539
540 {
541 // Run reduction.
542 //
543 // Contents of positions, normals and texcoords will be replaced with simplified mesh. The
544 // mesh is not indexed.
545
546 size_t level = wCtx.ctx.level;
547
548 SparseOctree::OctreeInfo& info = wCtx.ctx.octree.info;
549
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);
560 }
561 }
562 if (reduction <= 0.f) return;
563
564 switch (wCtx.ctx.octree.info.levelReducer) {
565 case SparseOctree::LevelReducer::RationalReducerVertexCount:
566 simplifyCellRationalReducer(ctx, cellGeo.pos, cellGeo.tex, reduction);
567 break;
568 case SparseOctree::LevelReducer::MeshOptimizerVertexCount:
569 simplifyCellMeshOptimizer(wCtx, cellGeo.pos, cellGeo.tex, reduction, false);
570 break;
571 case SparseOctree::LevelReducer::MeshOptimizerSloppyVertexCount:
572 simplifyCellMeshOptimizer(wCtx, cellGeo.pos, cellGeo.tex, reduction, true);
573 break;
574 default:
575 assert(false && "Unhandled switch case");
576 break;
577 }
578
579 if (cellGeo.pos.empty()) {
580 cellGeo.clear();
581 cellGeoIndices.clear();
582 return;
583 }
584 }
585
586 {
587 assert(cellGeoIndices.empty());
588
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;
593
594 // Convert mesh to indexed mesh.
595 map.clear();
596 unique.clear();
597 GeometryProcessing::uniqueVertexSubset(wCtx.ctx.octree.context, unique, map,
598 glm::value_ptr(cellGeo.pos[0]), sizeof(glm::vec3),
599 nullptr, 0,
600 glm::value_ptr(cellGeo.tex[0]), sizeof(glm::vec2),
601 static_cast<uint32_t>(cellGeo.size()));
602
603 // Create indexed meshes using the unique vertices
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];
611 }
612
613 cellGeo.pos.swap(tmpPos);
614 cellGeo.tex.swap(tmpTex);
615 cellGeoIndices.swap(map); // since initial mesh is un-indexed, indices are just the map table
616 }
617 {
618 // Calculate new normal vectors.
619 //
620 // Some vertices are duplicated along feature angles, hence we need a subsequent round of remapping.
621
622 std::vector<uint32_t>& unique = wCtx.unique; unique.clear();
623 std::vector<uint32_t>& tmpIx = wCtx.tmpIx; tmpIx.clear();
624
625 cellGeo.nrm.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,
631 true);
632
633 // map positions and texcoords to match new ordering (vertices might have been duplicated and feature edges)
634 size_t vertexCount = unique.size();
635 assert(cellGeo.nrm.size() == vertexCount);
636
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];
643 }
644
645 cellGeo.pos.swap(tmpPos);
646 cellGeo.tex.swap(tmpTex);
647 cellGeoIndices.swap(tmpIx);
648 }
649 }
650
651
652 void pushTriangle(Vertices& out, const glm::vec3* p, const glm::vec3* n, const glm::vec2* t)
653 {
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]);
658 }
659 }
660
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)
664 {
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);
668
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);
672
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);
676 }
677
678 float finiteOrZero(float x) {
679 return std::isfinite(x) ? x : 0.f;
680 }
681
682 void splitGeometry(CellProcessContext& ctx, Vertices& outA, Vertices& outB, const Vertices& in, const glm::vec4& plane)
683 {
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;
693
694 // Expected common case: Triangle lies entirely inside our outside the clip plane.
695 // We add a little slack on the test so that triangles that barely pierces the clip plane
696 // isn't clipped. The point of that is to avoid creating a lot of tiny triangles at the
697 // cost of a slightly wiggly clipping, yielding a slightly bigger bounding box.
698
699 if (-epsilon <= std::min(d0, std::min(d1, d2))) { // situation == 0b000
700 pushTriangle(outB, &in.pos[i], &in.nrm[i], &in.tex[i]);
701 }
702 else if (std::max(d0, std::max(d1, d2)) <= epsilon) { // situation == 0b111
703 pushTriangle(outA, &in.pos[i], &in.nrm[i], &in.tex[i]);
704 }
705 else {
706
707 // Involved case: Triangle intersects the clip plane. One side gets a triangle
708 // and the other gets a quad (two triangles).
709 const int situation = (d0 < 0.f ? 1 : 0) + (d1 < 0.f ? 2 : 0) + (d2 < 0.f ? 4 : 0);
710
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];
714
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];
718
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];
722
723 switch (situation) {
724 case 0b001:
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);
728 break;
729 case 0b010:
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);
733 break;
734 case 0b011:
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);
738 break;
739 case 0b100:
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);
743 break;
744 case 0b101:
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);
748 break;
749 case 0b110:
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);
753 break;
754 default:
755 // assert(false); // Should never happen.
756 // TODO: Fix this assert
757 // ChrisDy: I think commenting out the assert temporarily might be fine (and just discarding the triangle).
758 // I would like to figure out why the nan (if it is a nan) appears.
759 break;
760 }
761 }
762 }
763 }
764
765 void writeModelFile(CellProcessContext& ctx, const std::string& path, std::span<MeshHandle> meshes)
766 {
767 size_t N = meshes.size();
768
769 Model model{};
770
771 model.meshes.assign(meshes.begin(), meshes.end());
772
773 if (ctx.octree.info.colorizeLevels) {
774 model.materials.emplace_back(ctx.materialInstance);
775 }
776
777 model.parts.resize(N);
778 for (size_t i = 0; i < N; i++) {
779 ModelPart& part = model.parts[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;
785 }
786 }
787
788 uint32_t numIndexes;
789 uint32_t numVertexes;
790 bool rv = writeModel(ctx.octree.context, numIndexes, numVertexes, path, &model, &ctx.settings);
791 assert(rv);
792 }
793
794 void recordMesh(CellWorkerContext& wCtx, SparseOctree::Cell& cell, const Vertices& cellGeo, const std::vector<uint32_t>& cellGeoIndices = std::vector<uint32_t>())
795 {
796 CellProcessContext& ctx = wCtx.ctx;
797
798 // Calculate bounds
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);
804 }
805
806 // Create mesh
807 size_t meshVertexCount = 0;
808 MeshHandle mesh = ctx.octree.context->meshManager->create();
809 mesh->setPositions(cellGeo.pos);
810 mesh->setNormals(cellGeo.nrm);
811 mesh->setTexCoords(cellGeo.tex);
812 if (cellGeoIndices.empty()) {
813 meshVertexCount = cellGeo.size();
814 }
815 else {
816 meshVertexCount = cellGeoIndices.size();
817 mesh->setIndexes(cellGeoIndices);
818 }
819
820 // If mesh is considered small, try to stuff it into a shared cogsbin file
821 if (ctx.maxModelVertexCount && (meshVertexCount < ctx.maxModelVertexCount)) {
822 cell.meshRecs.emplace_back(SparseOctree::MeshRec{ .mesh = std::move(mesh), .bbox = BoundingBox{ bbMin, bbMax } });
823 }
824
825 // Otherwise mesh is considered large, and it will get its own cogsbin file.
826 else {
827
828 // File paths
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);
832
833 writeModelFile(wCtx.ctx, absolutePath, std::span<MeshHandle>(&mesh, 1));
834
835 cell.meshRefs.emplace_back(SparseOctree::MeshRef{ .fileName = relativePath, .bbox = BoundingBox{ bbMin, bbMax } });
836 }
837 }
838
839 void createCellMesh(CellWorkerContext& wCtx,
840 SparseOctree::Cell& cell,
841 const Vertices& cellGeo,
842 const std::vector<uint32_t>& cellGeoIndices)
843 {
844 assert(cellGeo.pos.size() == cellGeo.nrm.size());
845 assert(cellGeo.pos.size() == cellGeo.tex.size());
846 if (cellGeo.pos.empty()) return;
847
848 CellProcessContext& ctx = wCtx.ctx;
849
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());
854
855
856 // If the mesh is small enough, we won't split it.
857 if (ctx.maxMeshVertexCount == 0 || vertexCount < ctx.maxMeshVertexCount) {
858 recordMesh(wCtx, cell, cellGeo, cellGeoIndices);
859 return;
860 }
861
862 // Initialize the stack of meshes to split with the cell's mesh.
863 // For simplicity, we convert it to non-indexed. It will be indexed by repackaging.
864 std::vector<SplitStackItem> splitStack;
865 if (cellGeoIndices.empty()) {
866 splitStack.push_back({ .geo = cellGeo });
867 }
868 else {
869 splitStack.emplace_back();
870 const size_t N = cellGeoIndices.size();
871 Vertices& out = splitStack.back().geo;
872 out.resize(N);
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]];
877 }
878 }
879
880 // Continue until the set of meshes to be split is empty.
881 while (!splitStack.empty()) {
882 SplitStackItem item = std::move(splitStack.back());
883 splitStack.pop_back();
884
885 // Calculate the actual bounds of mesh, used to suggest clipping planes.
886 //
887 // Note: We do not shrink the cell bounding box to actual mesh bounds on purpose as the cell
888 // bounding box is used for lod-calcuations of the cell *including* empty space.
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);
894 }
895
896 // Recycle split meshes to reduce memory allocs
897 Splits& splits = wCtx.splits;
898 for (Split& split : splits) {
899 split.sides[0].clear();
900 split.sides[1].clear();
901 }
902
903 // Try three splits (YZ XZ and XY planes through bb center) and choose the best valid split
904 int bestIx = -1;
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]));
909 plane[k] = 1.f;
910 splitGeometry(ctx, splits[k].sides[0], splits[k].sides[1], item.geo, plane);
911
912 size_t countA = splits[k].sides[0].size();
913 size_t countB = splits[k].sides[1].size();
914 size_t count = countA + countB;
915
916 // Update best split that are considered a valid split.
917 if (0 < countA &&
918 0 < countB &&
919 countA < countIn &&
920 countB < countIn &&
921 count < bestCount)
922 {
923 bestCount = count;
924 bestIx = k;
925 }
926 }
927
928 if (bestIx < 0) {
929 // Failed to split, just give up
930 recordMesh(wCtx, cell, item.geo);
931 continue;
932 }
933
934 Split& bestSplit = splits[bestIx];
935 for (size_t k = 0; k < 2; k++) {
936 Vertices& side = bestSplit.sides[k];
937
938 // Resulting side is small enough, store it
939 if (side.size() < ctx.maxMeshVertexCount || ctx.maxSplitDepth <= item.depth) {
940 recordMesh(wCtx, cell, side);
941 }
942
943 // Still big, continue to try splitting it.
944 else {
945 splitStack.push_back({ .geo = std::move(side), .depth = item.depth + 1 });
946 }
947 }
948 }
949 }
950
951 void processCellWorkerFunc(CellProcessContext* ctx_, size_t /*id*/)
952 {
953 CellWorkerContext wCtx{
954 .ctx = *ctx_
955 };
956 CellProcessContext& ctx = wCtx.ctx;
957
958 std::vector<Mesh*> clipped;
959 std::vector<Mesh*> contained;
960 Vertices cellGeo;
961 SharedModel modelReadyForWriting;
962
963 std::vector<uint32_t> cell_indices;
964
965 const size_t cellCount = ctx.cellCount;
966
967 while (true) {
968 size_t item = ctx.processedCells.fetch_add(1);
969 if (ctx.cellCount <= item) break;
970
971 // Process a cell
972 {
973 SparseOctree::Cell& cell = *ctx.workItems[item].cell;
974
975 clipped.clear();
976 contained.clear();
977 filterCells(clipped, contained, cell.bbox, ctx.scene, cell.touches);
978
979 cellGeo.clear();
980
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);
986
987 // workItems[item] is now considered done. Will be tagged as done
988 // when we have the lock below.
989 }
990
991 {
992 auto& writeCells = ctx.writeCells;
993 std::unique_lock guard(writeCells.lock);
994
995 // Tag this cell as done.
996 assert(writeCells.cellsDone[item] == false);
997 writeCells.cellsDone[item] = true;
998
999 // Try to write as many cells as possible
1000 while (writeCells.cellsWritten < cellCount && writeCells.cellsDone[writeCells.cellsWritten]) {
1001
1002 SharedModel& sharedModel = ctx.writeCells.model;
1003
1004 if (!ctx.workItems[writeCells.cellsWritten].cell->meshRecs.empty()) {
1005 SparseOctree::Cell& cell = *ctx.workItems[writeCells.cellsWritten].cell;
1006
1007 // Current cell has still meshes, pop one
1008 SparseOctree::MeshRec meshRec = std::move(cell.meshRecs.back());
1009 cell.meshRecs.pop_back();
1010
1011 // Check if we should flush the current shared model first
1012 size_t vertexCount = meshRec.mesh->getCount();
1013 if (!sharedModel.meshes.empty())
1014 {
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);
1020 }
1021 }
1022
1023 // Check if we should set up a new shared model file
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);
1028 }
1029
1030 // Push mesh into shared model
1031 uint32_t part = static_cast<uint32_t>(sharedModel.meshes.size());
1032 sharedModel.meshes.emplace_back(std::move(meshRec.mesh));
1033 sharedModel.vertexCount += vertexCount;
1034
1035 // Record mesh reference
1036 cell.meshRefs.emplace_back(SparseOctree::MeshRef{ .fileName = sharedModel.relativePath, .part = part, .bbox = meshRec.bbox });
1037 }
1038 else {
1039 // No more meshes in this cell, jump to next cell.
1040 writeCells.cellsWritten++;
1041
1042 // This concludes the last cell.
1043 // - we have the lock, so no other threads are pushing meshes
1044 // - this is the last iteration of this loop for any thread
1045 if (writeCells.cellsWritten == cellCount) {
1046
1047 // Flush out remainig models
1048 if (!sharedModel.meshes.empty()) {
1049 std::swap(modelReadyForWriting, sharedModel);
1050 }
1051 }
1052 }
1053
1054 // Write mesh to disk
1055 if (!modelReadyForWriting.meshes.empty()) {
1056 guard.unlock();
1057
1058 // Do write file. Release lock and let other threads store meshes in the meantime
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;
1064
1065 guard.lock();
1066 }
1067 }
1068 }
1069 }
1070 }
1071
1072}
1073
1074
1075void Cogs::Core::SparseOctree::distributeGeometryWithIds(Octree & octree, PropertyStore& properties, const size_t level)
1076{
1077 CellProcessContext ctx{
1078 .octree = octree,
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)),
1083 .level = 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))
1089 };
1090
1091 if (ctx.octree.info.useCompression) {
1092 ctx.settings.flags |= WriteModelFlags::COMPRESS_ZSTD;
1093 ctx.settings.compressionLevel = octree.info.compressionLevel;
1094 }
1095 ctx.featureAngle = properties.getProperty("featureAngle", ctx.featureAngle);
1096
1097 IO::createDirectories(IO::combine(ctx.filenamePrefix, "dummy"));
1098
1099 if (octree.info.colorizeLevels) {
1100 MaterialHandle material = octree.context->materialManager->getDefaultMaterial();
1101 ctx.materialInstance = octree.context->materialInstanceManager->createMaterialInstance(material);
1102#if 1
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));
1105#else
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,
1110 1.f));
1111#endif
1112 }
1113
1114 // Order processing of cells in Z-order so adjacent cells are processed in the same time,
1115 // and we can pack them early
1116 {
1117 Cogs::Timer timer = Cogs::Timer::startNew();
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)),
1125 .cell = &cell });
1126 }
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;
1136 else return true;
1137 });
1138
1139 LOG_DEBUG(logger, "Level %zu: Sorting occupied cells into Z-order: %.2fs ", level, timer.elapsedSeconds());
1140 }
1141
1142 {
1143 Cogs::Timer timer = Cogs::Timer::startNew();
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));
1149 }
1150
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);
1154 }
1155
1156 for (std::thread& thread : workers) {
1157 thread.join();
1158 }
1159 workers.clear();
1160 LOG_DEBUG(logger, "Level %zu: Clipping, processing and writing models: %.2fs, cells=%zu, threads=%zu ", level, timer.elapsedSeconds(), ctx.cellCount, hwc);
1161 }
1162
1163}
1164
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)
1166{
1167 auto * tmp_a = &clipA;
1168 auto * tmp_b = &clipB;
1169
1170 // Create a line loop from the triangle
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];
1174 }
1175 for (unsigned k = 0; k < 3 * 2; k++) {
1176 tmp_a->T[k] = T_in[k];
1177 }
1178 auto N_a = 3u;
1179
1180 // Both line loop and half-space are convex, so the intersection of the two is convex as well
1181 // If a point is inside the half-space, we keep it, otherwise we discard it.
1182 // Between two points that are inside and outside, we interpolate to find the intersection and insert it.
1183 for (unsigned o = 0; o < 2; o++) {
1184 for (unsigned a = 0; a < 3; a++) {
1185
1186 auto N_b = 0u;
1187 unsigned ip = N_a - 1;
1188
1189 // signed distance for previous point
1190 float dp = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * ip + a] - clipBox[3 * o + a]);
1191
1192 for (unsigned in = 0; in < N_a; in++) {
1193
1194 // signed distance for next point
1195 float dn = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * in + a] - clipBox[3 * o + a]);
1196
1197 bool dp_neg = dp < 0.f;
1198 bool dn_neg = dn < 0.f;
1199
1200 // If sign changes from previous point to next point, insert intersection
1201 if (dp_neg != dn_neg) {
1202
1203 // Weights
1204 auto wa = std::abs(dn / (dp - dn));
1205 auto wb = std::abs(dp / (dp - dn));
1206 if (!std::isfinite(wa) || !std::isfinite(wb)) {
1207 wa = wb = 0.5f;
1208 }
1209
1210 // Blend
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];
1214
1215 }
1216 for (unsigned k = 0; k < 2; k++) {
1217 tmp_b->T[2 * N_b + k] = tmp_a->T[2 * ip + k];
1218 }
1219 N_b++;
1220 assert(N_b <= 9);
1221 }
1222
1223 // If next point is inside half-space, keep it.
1224 if (dn_neg) {
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];
1228 }
1229 for (unsigned k = 0; k < 2; k++) {
1230 tmp_b->T[2 * N_b + k] = tmp_a->T[2 * in + k];
1231 }
1232 N_b++;
1233 assert(N_b <= 9);
1234 }
1235
1236 dp = dn;
1237 ip = in;
1238 }
1239
1240 // If we have less than three points, it is degenerate or empty, just bail out.
1241 if (N_b < 3) {
1242 return 0;
1243 }
1244
1245 // and swap
1246 N_a = N_b;
1247 std::swap(tmp_a, tmp_b);
1248 }
1249 }
1250
1251 // Since the loop is convex, triangulating it is trivial, we just form
1252 // triangles from all edges not touching point 0 and use point 0 as apex.
1253 for (unsigned i = 1; i + 1 < N_a; i++) {
1254
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];
1258 }
1259 for (unsigned k = 0; k < 2; k++) {
1260 *T_dst++ = tmp_a->T[2 * (0) + k];
1261 }
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];
1265 }
1266 for (unsigned k = 0; k < 2; k++) {
1267 *T_dst++ = tmp_a->T[2 * (i + 0) + k];
1268 }
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];
1272 }
1273 for (unsigned k = 0; k < 2; k++) {
1274 *T_dst++ = tmp_a->T[2 * (i + 1) + k];
1275 }
1276 }
1277
1278 return N_a - 2;
1279}
1280
1281uint32_t Cogs::Core::SparseOctree::clipTriangle(float * V_dst, float * N_dst, const float (&clipBox)[6], const float * V_in, const float * N_in)
1282{
1283 auto * tmp_a = &clipA;
1284 auto * tmp_b = &clipB;
1285
1286 // Create a line loop from the triangle
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];
1290 }
1291 auto N_a = 3u;
1292
1293 // Both line loop and half-space are convex, so the intersection of the two is convex as well
1294 // If a point is inside the half-space, we keep it, otherwise we discard it.
1295 // Between two points that are inside and outside, we interpolate to find the intersection and insert it.
1296 for (unsigned o = 0; o < 2; o++) {
1297 for (unsigned a = 0; a < 3; a++) {
1298
1299 auto N_b = 0u;
1300 unsigned ip = N_a - 1;
1301
1302 // signed distance for previous point
1303 float dp = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * ip + a] - clipBox[3 * o + a]);
1304
1305 for (unsigned in = 0; in < N_a; in++) {
1306
1307 // signed distance for next point
1308 float dn = (o == 0 ? -1.f : 1.f) * (tmp_a->V[3 * in + a] - clipBox[3 * o + a]);
1309
1310 bool dp_neg = dp < 0.f;
1311 bool dn_neg = dn < 0.f;
1312
1313 // If sign changes from previous point to next point, insert intersection
1314 if (dp_neg != dn_neg) {
1315
1316 // Weights
1317 auto wa = std::abs(dn / (dp - dn));
1318 auto wb = std::abs(dp / (dp - dn));
1319 if (!std::isfinite(wa) || !std::isfinite(wb)) {
1320 wa = wb = 0.5f;
1321 }
1322
1323 // Blend
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];
1327
1328 }
1329 N_b++;
1330 assert(N_b <= 9);
1331 }
1332
1333 // If next point is inside half-space, keep it.
1334 if (dn_neg) {
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];
1338 }
1339 N_b++;
1340 assert(N_b <= 9);
1341 }
1342
1343 dp = dn;
1344 ip = in;
1345 }
1346
1347 // If we have less than three points, it is degenerate or empty, just bail out.
1348 if (N_b < 3) {
1349 return 0;
1350 }
1351
1352 // and swap
1353 N_a = N_b;
1354 std::swap(tmp_a, tmp_b);
1355 }
1356 }
1357
1358 // Since the loop is convex, triangulating it is trivial, we just form
1359 // triangles from all edges not touching point 0 and use point 0 as apex.
1360 for (unsigned i = 1; i + 1 < N_a; i++) {
1361
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];
1365 }
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];
1369 }
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];
1373 }
1374 }
1375
1376 return N_a - 2;
1377}
1378
1379#else
1380
1381namespace Cogs::Core::SparseOctree
1382{
1383 void distributeGeometryWithIds(Octree & /*octree*/, PropertyStore& /*properties*/, const size_t /*level*/) {}
1384
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; }
1387}
1388
1389#endif
Log implementation class.
Definition: LogManager.h:139
Old timer class.
Definition: Timer.h:37
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ Normal
Normal semantic.
@ 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.
Definition: MortonCode.h:10
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
void setIndexes(std::span< const uint32_t > collection)
Set the index data to the collection given.
Definition: Mesh.h:596
void setTexCoords(std::span< const glm::vec2 > texCoords)
Set the texture coordinate data of the Mesh.
Definition: Mesh.h:504
StreamReference getPositionStream()
Get the data of the stream containing positions.
Definition: Mesh.cpp:136
void setNormals(std::span< const glm::vec3 > normals)
Set the normal data of the Mesh.
Definition: Mesh.h:392
StreamReference getSemanticStream(ElementSemantic semantic, DataFormat format)
Get the data of the stream containing data with the given semantic, format and minimum element size.
Definition: Mesh.cpp:144
uint32_t getCount() const
Get the vertex count of the mesh.
Definition: Mesh.h:1012
void setPositions(std::span< const glm::vec3 > positions)
Set the position data of the Mesh.
Definition: Mesh.h:315
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::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.
Definition: Common.h:116