Cogs.Core
AdaptivePlanarGridSystem.cpp
1#include "AdaptivePlanarGridSystem.h"
2
3#include "Foundation/Logging/Logger.h"
4
5#include "Components/Appearance/MaterialComponent.h"
6#include "Components/Core/SubMeshRenderComponent.h"
7#include "Components/Core/MeshComponent.h"
8#include "Components/Core/TransformComponent.h"
9#include "Components/Core/LodComponent.h"
10#include "Components/Core/SceneComponent.h"
11
12#include "Systems/Core/CameraSystem.h"
13#include "Systems/Core/TransformSystem.h"
14#include "Systems/Core/SubMeshRenderSystem.h"
15
16#include "Resources/Mesh.h"
17#include "Resources/MeshManager.h"
18#include "Resources/VertexFormats.h"
19#include "Resources/MaterialManager.h"
20
21#include "Utilities/FrustumClassification.h"
22#include "Utilities/Simplex.h"
23
24#include "Context.h"
25#include "EntityStore.h"
26
27#include <cmath>
28
29using std::atan2;
30using std::round;
31using std::sort;
32using std::numeric_limits;
33using std::vector;
34using std::equal;
35using std::move;
36
37using glm::max;
38using glm::min;
39using glm::vec2;
40using glm::vec3;
41using glm::vec4;
42using glm::bvec3;
43using glm::dvec2;
44using glm::dvec3;
45using glm::ivec2;
46using glm::ivec3;
47using glm::mat4;
48using glm::dmat2;
49
50using glm::abs;
51using glm::sin;
52using glm::cos;
53using glm::mix;
54using glm::dot;
55using glm::clamp;
56using glm::normalize;
57using glm::any;
58using glm::lessThan;
59
60// Node hierarchy
61// --------------
62//
63// The adaptive planar grid uses a small node hierarchy to manage the tiles:
64//
65// Entity A [AdapativePlanarGridComponent-entity]
66// |
67// +-- Entity B [Group]
68// | |
69// | +-- Entity C1 [MeshPart] (Represents a tile)
70// | |
71// | +-- Entity C2 [MeshPart] (Represents a tile)
72// | |
73// | ...
74// | |
75// | +-- Entity Cm [MeshPart] (Represents a tile)
76// |
77// |
78// +-- Entity D [Default] (Holds debug graphics)
79//
80// Entity A is the entity that contains an AdaptivePlanarGridComponent, and is
81// created by the user. First time the system sees it, it creates this
82// hierarchy.
83//
84// Entity B is a group that contains all the tiles.
85//
86// Entities C1 through Cm are a pool of entities, where C1 through Cn <Cm are
87// currently in used, and are set as visible. Each of these represents a
88// visible tile in the scene. Entities Cn+1 through Cm are invisible and are
89// just kept around until they are required by the scene. The number of tiles
90// is limited, and thus, this pool is also limited. Further, there is no
91// fixed relationship between a tile entity and a tile at a given position.
92// Tile entities are reorganized each time the camera moves.
93//
94// Transforms
95// ----------
96//
97// World space -+-> Root space -+-> Cam space -+-> Clip space
98// | | |
99// | | +--- projection matrix
100// | |
101// | +--- Camera world-to-local
102// |
103// +--- entity A local-to-world
104//
105// Entity A defines its own world space. We have introduced the term root
106// space, which is the world space for the node hierarchy. In the case that
107// Entity A's transform is the identity, these two spaces coincide. If
108// AdaptivePlanarGridComponent::positionable is true, the world space of
109// the grid relative to the root may be freely specified. If it is false,
110// that transform is hijacked, and is effectively a unity transform, i.e.,
111// root and world is the same coordinate systems.
112//
113// All properties of AdaptiveGridCOmponent (e.g. extent) refers to Entity A's
114// world space.
115//
116// AdaptivePlanarGridData::currentExtent is the intersection between the
117// current view frustum and the extent. Thus, it is the subset of the
118// extent that is relevant for the current frustum, and forms the basis
119// for the refinement hierarchy. When the extent changes, it is made slightly
120// larger, to avoid changing the position of the refinement lines all the time.
121//
122// AdaptivePlanarGridData::currentCoordinates are the coordinates used when
123// current extent was defined.
124
125namespace
126{
127 Cogs::Logging::Log logger = Cogs::Logging::getLogger("AdaptivePlanarGridSystem");
128
129 using namespace Cogs::Core;
130
131 size_t maxTiles = 1000;
132 int maxLevel = 20;
133 std::vector<uint8_t> simplexScratch;
134 std::vector<AdaptivePlanarGridQuadTreeNode> quadTree;
135 std::vector<AdaptivePlanarGridIndex> leaves;
136 std::list<AdaptivePlanarGridRefinableNode> refineQueue;
137
138
139 struct Triangle
140 {
141 float key;
142 ivec3 ix;
143 };
144
145 struct Edge
146 {
147 vec3 a;
148 vec3 b;
149 };
150
151 vec3 euclidean(const vec4& h)
152 {
153 return (1.f / h.w)*vec3(h);
154 }
155
156 struct CameraState
157 {
158 glm::mat4 worldToView;
159 glm::mat4 worldToClip;
160 glm::mat4 viewToWorld;
161 glm::vec3 worldUnitXY;
162 glm::vec2 viewportSize;
163 float nearDistance = 0.0f;
164 float farDistance = 0.0f;
165 };
166
167 // Find aabbox of intersection volume between the extent bounding box and the frustums.
168 void analyzeView(Context* context,
169 const AdaptivePlanarGridComponent & gridComp,
170 AdaptivePlanarGridData & gridData,
171 const std::vector<CameraState>& cameras,
172 bool updateDebugGraphics)
173 {
174 if (!std::isfinite(gridComp.extentMin.x) || !std::isfinite(gridComp.extentMin.y) ||
175 !std::isfinite(gridComp.extentMax.x) || !std::isfinite(gridComp.extentMax.y))
176 {
177 gridData.currentExtentMin = dvec2(0.0);
178 gridData.currentExtentMax = dvec2(0.0);
179 return;
180 }
181
182 const auto bboxMin = glm::dvec3(gridComp.extentMin, 0.0) + glm::dvec3(gridComp.displaceMin);
183 const auto bboxMax = glm::dvec3(gridComp.extentMax, 0.0) + glm::dvec3(gridComp.displaceMax);
184 auto bboxSize = bboxMax - bboxMin;
185
186 dvec2 smallestExtentMin(std::numeric_limits<double>::max());
187 dvec2 smallestExtentMax(-std::numeric_limits<double>::max());
188
189 for (auto & camera : cameras) {
190
191 // Find intersection shape of extent bounding box and the frustum of P.
192 auto rows = glm::dmat4(glm::transpose(camera.worldToClip));
193 auto l = rows[3] + rows[0];
194 auto r = rows[3] - rows[0];
195 auto b = rows[3] + rows[1];
196 auto t = rows[3] - rows[1];
197
198 double restrictions[7 * 4] =
199 {
200 1.0, 0.0, 0.0, bboxSize.x,
201 0.0, 1.0, 0.0, bboxSize.y,
202 0.0, 0.0, 1.0, bboxSize.z,
203 -l.x, -l.y, -l.z, l.w + glm::dot(glm::dvec3(l),bboxMin),
204 -r.x, -r.y, -r.z, r.w + glm::dot(glm::dvec3(r),bboxMin),
205 -b.x, -b.y, -b.z, b.w + glm::dot(glm::dvec3(b),bboxMin),
206 -t.x, -t.y, -t.z, t.w + glm::dot(glm::dvec3(t),bboxMin)
207 };
208
209 double objectives[4 * 3] = {
210 1.0, 0.0, 0.0,
211 -1.0, 0.0, 0.0,
212 0.0, 1.0, 0.0,
213 0.0, -1.0, 0.0
214 };
215
216 double solutions[4 * 3];
217 auto rv = simplexMaximize(context,
218 (uintptr_t)simplexScratch.data(),
219 solutions, 3,
220 objectives, 4,
221 restrictions, 7,
222 150);
223
224 if (rv == SimplexResult::NoFeasibleSolutions) { // No intersection found
225 }
226 else if (rv != SimplexResult::OptimalSolutionFound) { // Simplex method failed, just use full extent
227 smallestExtentMin = dvec2(bboxMin);
228 smallestExtentMax = dvec2(bboxMax);
229 }
230 else { // Sane solution.
231
232 // Calc aabbox of the extreme points.
233 glm::dvec2 minWorld = dvec2(solutions[0 * 3 + 0], solutions[0 * 3 + 1]);
234 glm::dvec2 maxWorld = minWorld;
235 for (int i = 1; i < 4; i++) {
236 auto p = dvec2(solutions[i * 3 + 0], solutions[i * 3 + 1]);
237 minWorld = glm::min(minWorld, p);
238 maxWorld = glm::max(maxWorld, p);
239 }
240
241 // Clamp aabbox to aabox of full extent and update smallestExtent.
242 smallestExtentMin = glm::min(smallestExtentMin, glm::max(minWorld + glm::dvec2(bboxMin), glm::dvec2(bboxMin)));
243 smallestExtentMax = glm::max(smallestExtentMax, glm::min(maxWorld + glm::dvec2(bboxMin), glm::dvec2(bboxMax)));
244 }
245
246 }
247
248 if ((smallestExtentMax.x - smallestExtentMin.x < std::numeric_limits<float>::epsilon())
249 || (smallestExtentMax.y - smallestExtentMin.y < std::numeric_limits<float>::epsilon()))
250 {
251 gridData.currentExtentMin = dvec2(0.0);
252 gridData.currentExtentMax = dvec2(0.0);
253 }
254 else {
255
256 if ((gridData.currentExtentMin.x <= smallestExtentMin.x)
257 && (gridData.currentExtentMin.y <= smallestExtentMin.y)
258 && (smallestExtentMax.x <= gridData.currentExtentMax.x)
259 && (smallestExtentMax.y <= gridData.currentExtentMax.y))
260 {
261 // New extent is inside current extent, check if new extent isn't too small
262 dvec2 A = gridData.currentExtentMax - gridData.currentExtentMin;
263 dvec2 B = smallestExtentMax - smallestExtentMin;
264 if (B.x > 0.5*A.x && B.y > 0.5*A.y) {
265 // It is all right, don't change anything
266 return;
267 }
268 }
269 // New extent doesn't match current extent particularly well.
270 // Use new extent, but add some extra room so we don't change the extent all the time.
271 // Still, make sure that we adhere to prescribed extent
272 dvec2 delta = smallestExtentMax - smallestExtentMin;
273 gridData.currentExtentMin = glm::max(smallestExtentMin - 0.25 * delta, glm::dvec2(bboxMin));
274 gridData.currentExtentMax = glm::min(smallestExtentMax + 0.25 * delta, glm::dvec2(bboxMax));
275 }
276
277 if (updateDebugGraphics) {
278 std::vector<glm::vec3> p;
279 p.push_back(glm::vec3(smallestExtentMin.x, smallestExtentMin.y, 0.f));
280 p.push_back(glm::vec3(smallestExtentMax.x, smallestExtentMin.y, 0.f));
281
282 p.push_back(glm::vec3(smallestExtentMax.x, smallestExtentMin.y, 0.f));
283 p.push_back(glm::vec3(smallestExtentMax.x, smallestExtentMax.y, 0.f));
284
285 p.push_back(glm::vec3(smallestExtentMax.x, smallestExtentMax.y, 0.f));
286 p.push_back(glm::vec3(smallestExtentMin.x, smallestExtentMax.y, 0.f));
287
288 p.push_back(glm::vec3(smallestExtentMin.x, smallestExtentMax.y, 0.f));
289 p.push_back(glm::vec3(smallestExtentMin.x, smallestExtentMin.y, 0.f));
290
291 gridData.debugMesh->setPositions(std::move(p));
292 gridData.debugMesh->primitiveType = Cogs::PrimitiveType::LineList;
293 }
294 }
295
296
297
298 int isBoxInsideAnyFrustum(const std::vector<CameraState>& cameras, const glm::vec3& boxMin, const glm::vec3& boxMax, const glm::vec2& nearestWorld, const float tileNorm, const double tileScreenCoverageThreshold)
299 {
300 bool isInsideAny = false;
301 for (auto & camera : cameras) {
302
303 // Cull tiles that are outside the frustum
304 int planes = 0;
305
306 for (int i = 0; i < 8; i++) {
307 vec4 p((i & 1) ? boxMin.x : boxMax.x,
308 (i & 2) ? boxMin.y : boxMax.y,
309 (i & 4) ? boxMin.z : boxMax.z,
310 1.f);
311 vec4 v = camera.worldToView * p;
312 vec4 c = camera.worldToClip * p;
313 planes |= (v.z < 0.f ? 1 : 0) | (-camera.farDistance <= v.z ? 2 : 0) |
314 (c.x <= c.w ? 4 : 0) | (-c.w <= c.x ? 8 : 0) |
315 (c.y <= c.w ? 16 : 0) | (-c.w <= c.y ? 32 : 0);
316 }
317
318 if (planes == 63) {
319 // Tile is inside frustum, check refinement condition.
320
321 vec4 nearestView = camera.worldToView * vec4(nearestWorld, 0.f, 1.f);
322 float d = max(camera.nearDistance, -nearestView.z / nearestView.w);
323 if(camera.farDistance < d) continue;
324
325 const vec3 cameraOriginWorld(camera.viewToWorld[3]);
326 const vec3 cameraZAxisWorld(camera.viewToWorld[2]);
327
328 vec3 tileReferenceL = cameraOriginWorld - d * normalize(cameraZAxisWorld) - tileNorm * camera.worldUnitXY;
329 vec3 tileReferenceR = cameraOriginWorld - d * normalize(cameraZAxisWorld) + tileNorm * camera.worldUnitXY;
330 vec2 tileRefNDCL = vec2(euclidean(camera.worldToClip*vec4(tileReferenceL, 1.f)));
331 vec2 tileRefNDCR = vec2(euclidean(camera.worldToClip*vec4(tileReferenceR, 1.f)));
332
333 vec2 tileRefScreen = camera.viewportSize * (tileRefNDCR - tileRefNDCL);
334 double tileScreenCoverage = double(tileRefScreen.x)*double(tileRefScreen.y);
335
336 if (tileScreenCoverageThreshold < tileScreenCoverage) return 2;
337 isInsideAny = true;
338 }
339 }
340 return isInsideAny ? 1 : 0;
341 }
342
343 void refineTiles(const AdaptivePlanarGridComponent & gridComp,
344 AdaptivePlanarGridData & gridData,
345 const std::vector<CameraState>& cameras,
346 const glm::mat4 lodRefTransform,
347 const float levelOfDetailTolerance)
348 {
349 vec3 cameraOriginWorld(lodRefTransform[3]);
350
351 int gridResolution = 1 << gridData.tileResolutionLog2;
352
353 // levelOfDetailTolerance should be in pixels squared.
354 // Multiply by grid resolution to get triangle quad size.
355 // Divide by four since we let clip be in [-1,1].
356 double tileScreenCoverageThreshold = 0.25 * double(gridResolution * gridResolution * levelOfDetailTolerance);
357
358 // Breadth-first subdivision of domain
359 leaves.clear();
360 quadTree.clear();
361 refineQueue.clear();
362 refineQueue.push_back(AdaptivePlanarGridRefinableNode{ { 0, 0, 0 }, NoParent, 0 });
363
364 while (!refineQueue.empty()) {
365 const auto & currentNode = refineQueue.front();
366
367 double delta = 1.0 / double(1 << currentNode.ix.level);
368
369 vec2 tileMinWorld = mix(gridData.currentExtentMin, gridData.currentExtentMax, delta * dvec2(currentNode.ix.xIndex, currentNode.ix.yIndex));
370 vec2 tileMaxWorld = mix(gridData.currentExtentMin, gridData.currentExtentMax, delta * dvec2(currentNode.ix.xIndex + 1, currentNode.ix.yIndex + 1));
371
372 float tileNorm = max(tileMaxWorld.x - tileMinWorld.x, tileMaxWorld.y - tileMinWorld.y) / gridResolution;
373
374 // Grow bbox to accommodate whatever displacement that the vertex shader might produce.
375 vec3 displacedTileMinWorld = vec3(tileMinWorld, 0.f) + gridComp.displaceMin;
376 vec3 displacedTileMaxWorld = vec3(tileMaxWorld, 0.f) + gridComp.displaceMax;
377
378 // Point in tile nearest lod-reference
379 vec2 nearestWorld = clamp(vec2(cameraOriginWorld), tileMinWorld, tileMaxWorld);
380
381 auto s = isBoxInsideAnyFrustum(cameras, displacedTileMinWorld, displacedTileMaxWorld, nearestWorld, tileNorm, tileScreenCoverageThreshold);
382
383 //if (isBoxInsideAnyFrustum(cameras, displacedTileMinWorld, displacedTileMaxWorld)) {
384 if(s != 0) {
385
386 // Connect the parent to this node that we will populate in this iteration.
387 uint16_t currIndex = static_cast<uint16_t>(quadTree.size());
388 if (currentNode.parent != NoParent) {
389 quadTree[currentNode.parent].children[currentNode.isWhichChild] = currIndex;
390 }
391
392
393 bool tileTooLarge = s == 2;
394 bool belowMaxTileLimit = leaves.size() + refineQueue.size() < size_t(maxTiles);
395 bool belowMaxLevelLimit = currentNode.ix.level + 1 < maxLevel;
396
397 if (belowMaxTileLimit && tileTooLarge && belowMaxLevelLimit) {
398 quadTree.push_back(
400 AdaptivePlanarGridQuadTreeNode::DeadEnd,
401 AdaptivePlanarGridQuadTreeNode::DeadEnd,
402 AdaptivePlanarGridQuadTreeNode::DeadEnd,
403 AdaptivePlanarGridQuadTreeNode::DeadEnd }
404 });
405
406 const uint32_t x = currentNode.ix.xIndex << 1;
407 const uint32_t y = currentNode.ix.yIndex << 1;
408 const uint8_t level = currentNode.ix.level + 1;
409 const int parent = currIndex;
410
411 refineQueue.push_back(AdaptivePlanarGridRefinableNode{ { x, y, level, }, parent, 0 });
412 refineQueue.push_back(AdaptivePlanarGridRefinableNode{ { x + 1, y, level, }, parent, 1 });
413 refineQueue.push_back(AdaptivePlanarGridRefinableNode{ { x, y + 1, level, }, parent, 2 });
414 refineQueue.push_back(AdaptivePlanarGridRefinableNode{ { x + 1, y + 1, level, }, parent, 3 });
415 }
416 else {
417 quadTree.push_back(
419 AdaptivePlanarGridQuadTreeNode::Leaf,
420 AdaptivePlanarGridQuadTreeNode::Leaf,
421 AdaptivePlanarGridQuadTreeNode::Leaf,
422 AdaptivePlanarGridQuadTreeNode::Leaf }
423 });
424
425 leaves.push_back(currentNode.ix);
426 }
427
428 }
429
430 refineQueue.pop_front();
431 }
432 }
433
434
435 void updateTileMaterialInstances(const AdaptivePlanarGridComponent & /*gridComp*/, AdaptivePlanarGridData & gridData)
436 {
437 auto sceneComp = gridData.tileEntitiesGroup->getComponent<SceneComponent>();
438
439 for (auto & c : sceneComp->children) {
440 auto mshRndComp = c->getComponent<SubMeshRenderComponent>();
441 mshRndComp->subMesh = gridData.camYaw;
442 }
443
444 auto & matData = gridData.materialData;
445
446 for (size_t i = 0; i < gridData.tiles.size(); i++) {
447 auto & tile = gridData.tiles[i];
448 tile.materialInstance->setVec4Property(matData.lodLevelsKey,
449 vec4(tile.adjacentLevels[0],
450 tile.adjacentLevels[1],
451 tile.adjacentLevels[2],
452 tile.adjacentLevels[3]));
453 tile.materialInstance->setVec4Property(matData.texCoordWorldTransformKey, tile.texCoordWorldTransform);
454 tile.materialInstance->setVec4Property(matData.texCoordUnitTransformKey, tile.texCoordUnitTransform);
455 tile.materialInstance->setVec4Property(matData.texCoordLinearTransformKey, glm::make_vec4(glm::value_ptr(gridData.texCoordLinearTransform)));
456 }
457 }
458
459 void updatePools(Context * context, Cogs::ComponentModel::Entity * container, AdaptivePlanarGridData &gridData)
460 {
461 const size_t oldSize = gridData.materialPool.size();
462 gridData.materialPool.resize(std::max(gridData.materialPool.size(), gridData.tiles.size()));
463 gridData.entityPool.resize(std::max(gridData.entityPool.size(), gridData.tiles.size()));
464
465 for (size_t i = oldSize; i < gridData.materialPool.size(); ++i) {
466 gridData.materialPool[i] = context->materialInstanceManager->createMaterialInstance(gridData.materialData.material);
467 if (gridData.materialData.initMaterialInstanceCallback) {
468 gridData.materialData.initMaterialInstanceCallback(static_cast<MaterialInstance*>(gridData.materialPool[i].get()), container, gridData.materialData.initMaterialInstanceData);
469 }
470 }
471
472 for (size_t i = oldSize; i < gridData.entityPool.size(); ++i) {
473 gridData.entityPool[i] = context->store->createChildEntity("SubMeshPart", gridData.tileEntitiesGroup.get(), "Pooled Entity " + std::to_string(i));
474 auto entity = gridData.entityPool[i];
475 auto sceneComp = entity->getComponent<SceneComponent>();
476 sceneComp->setChanged();
477 auto meshComp = entity->getComponent<MeshComponent>();
478 meshComp->meshHandle = gridData.gridMesh;
479 meshComp->setChanged();
480 }
481
482 // Keep inactive entities from showing up in the scene.
483 for (size_t i = gridData.tiles.size(); i < gridData.entityPool.size(); ++i) {
484 auto & entity = gridData.entityPool[i];
485
486 auto renderComponent = entity->getComponent<SubMeshRenderComponent>();
487 renderComponent->setVisible(false);
488 }
489 }
490
491 void updateTileEntities(Context* context, const AdaptivePlanarGridComponent & gridComp, AdaptivePlanarGridData & gridData, const glm::mat4 & worldToRoot)
492 {
493 updatePools(context, gridComp.getContainer(), gridData);
494
495 glm::dvec2 fullWidth = gridComp.extentMax - gridComp.extentMin;
496
497 glm::vec2 unityMin = glm::vec2((gridData.currentExtentMin - gridComp.extentMin) / fullWidth);
498 glm::vec2 unityMax = glm::vec2((gridData.currentExtentMax - gridComp.extentMin) / fullWidth);
499 glm::dvec3 origin = context->transformSystem->getOrigin();
500
501 bool originChanged = origin != gridData.currentOrigin;
502 gridData.currentOrigin = origin;
503
504 // Create new child items
505 for (size_t i = 0; i < gridData.tiles.size(); i++) {
506 auto & tile = gridData.tiles[i];
507
508 const float delta = 1.f / (1 << tile.ix.level);
509 const vec2 minLocal = delta * vec2(tile.ix.xIndex, tile.ix.yIndex);
510 const vec2 maxLocal = minLocal + delta;
511
512 auto & entity = gridData.entityPool[i];
513 tile.materialInstance = gridData.materialPool[i];
514
515 auto meshRendComp = entity->getComponent<SubMeshRenderComponent>();
516 meshRendComp->materials.resize(gridData.tessellations);
517
518 for (int k = 0; k < gridData.tessellations; k++) {
519 meshRendComp->materials[k] = tile.materialInstance;
520 }
521
522 meshRendComp->setVisible(true);
523 meshRendComp->layer = gridComp.layer;
524 meshRendComp->objectId = gridData.objectId;
525 meshRendComp->subMesh = -1;
526 meshRendComp->setChanged();
527
528 const vec2 minWorld = mix(vec2(gridData.currentExtentMin), vec2(gridData.currentExtentMax), minLocal);
529 const vec2 maxWorld = mix(vec2(gridData.currentExtentMin), vec2(gridData.currentExtentMax), maxLocal);
530 const vec2 minUnit = mix(unityMin, unityMax, minLocal);
531 const vec2 maxUnit = mix(unityMin, unityMax, maxLocal);
532
533 auto transformComponent = entity->getComponent<TransformComponent>();
534 vec3 newPosition = vec3(minWorld, 0.0);
535 vec3 newScale = vec3(maxWorld - minWorld, 1.f);
536
537 if (transformComponent->position != newPosition ||
538 transformComponent->scale != newScale ||
539 originChanged) {
540
541 transformComponent->position = newPosition;
542 transformComponent->scale = newScale;
543
544 context->transformSystem->updateTransformData(*transformComponent);
545 }
546
547 vec2 texCoordWorldShiftFromTilePos = gridData.texCoordLinearTransform*minWorld;
548 vec2 texCoordUnitShiftFromTilePos = gridData.texCoordLinearTransform*minUnit;
549
550 if (std::isfinite(gridData.texCoordPeriod.x)) {
551 texCoordWorldShiftFromTilePos.x = glm::mod(texCoordWorldShiftFromTilePos.x, gridData.texCoordPeriod.x);
552 texCoordUnitShiftFromTilePos.x = glm::mod(texCoordUnitShiftFromTilePos.x, gridData.texCoordPeriod.x);
553 }
554 if (std::isfinite(gridData.texCoordPeriod.y)) {
555 texCoordWorldShiftFromTilePos.y = glm::mod(texCoordWorldShiftFromTilePos.y, gridData.texCoordPeriod.y);
556 texCoordUnitShiftFromTilePos.y = glm::mod(texCoordUnitShiftFromTilePos.y, gridData.texCoordPeriod.y);
557 }
558
559 tile.texCoordWorldTransform = vec4(maxWorld - minWorld, texCoordWorldShiftFromTilePos);
560 tile.texCoordUnitTransform = vec4(maxUnit - minUnit, texCoordUnitShiftFromTilePos);
561
562 const auto worldBox0 = glm::vec3(minWorld, 0) + gridComp.displaceMin;
563 const auto worldBox1 = glm::vec3(maxWorld, 0) + gridComp.displaceMax;
564 vec3 rootBox0(std::numeric_limits<float>::max());
565 vec3 rootBox1(-std::numeric_limits<float>::max());
566 for (int ii = 0; ii < 8; ii++) {
567 glm::bvec3 end((ii & 1) != 0,
568 -(ii & 2) != 0,
569 -(ii & 4) != 0);
570 const auto ch = worldToRoot * vec4(mix(worldBox0, worldBox1, end), 1.f);
571 const auto c = (1.f / ch.w)*vec3(ch);
572 rootBox0 = min(rootBox0, c);
573 rootBox1 = max(rootBox1, c);
574 }
575 auto renderComponent = entity->getComponent<SubMeshRenderComponent>();
576 Cogs::Geometry::BoundingBox box{ rootBox0, rootBox1 };
577 context->subMeshRenderSystem->setBounds(renderComponent, box, context->transformSystem->getLocalToWorld(transformComponent));
578 }
579 }
580
581 void setupGridMesh(Context* context, const int tileResolutionLog2, AdaptivePlanarGridData & gridData)
582 {
583 if (gridData.tileResolutionLog2 == tileResolutionLog2) return;
584
585 gridData.gridMesh = context->meshManager->create();
586 gridData.tileResolutionLog2 = tileResolutionLog2;
587 const int gridResolution = 1 << tileResolutionLog2;
588
589 vector<Triangle> T(2 * gridResolution * gridResolution);
590 ivec2 indices[2][2][3] = {
591 { { ivec2(0, 0), ivec2(1, 0), ivec2(0, 1) }, { ivec2(1, 0), ivec2(1, 1), ivec2(0, 1) } },
592 { { ivec2(0, 0), ivec2(1, 0), ivec2(1, 1) }, { ivec2(0, 0), ivec2(1, 1), ivec2(0, 1) } }
593 };
594 gridData.gridMesh->clearIndexes();
595
596 vector<uint32_t> I(2 * 3 * gridResolution * gridResolution);
597 for (int d = 0; d < gridData.tessellations; d++) {
598 const float theta = (2.0f * glm::pi<float>() * d) / float(gridData.tessellations);
599 vec2 dir(cos(theta), sin(theta));
600 int e = abs(dot(vec2(1, 1), dir)) < abs(dot(vec2(-1, 1), dir)) ? 1 : 0;
601 for (int j = 0; j < gridResolution; j++) {
602 for (int i = 0; i < gridResolution; i++) {
603 for (int t = 0; t < 2; t++) {
604 vec2 key(0.f, 0.f);
605 for (int k = 0; k < 3; k++) {
606 ivec2 p = ivec2(i, j) + indices[e][t][k];
607 key += vec2(p);
608 T[2 * (j*gridResolution + i) + t].ix[k] = p.y*(gridResolution + 1) + p.x;
609 }
610 T[2 * (j*gridResolution + i) + t].key = dot(dir, key);
611 }
612 }
613 }
614 sort(T.begin(), T.end(), [](const Triangle& a, const Triangle& b) ->bool { return a.key > b.key; });
615 for (size_t i = 0; i < T.size(); i++) {
616 I[3 * i + 0] = T[i].ix[0];
617 I[3 * i + 1] = T[i].ix[1];
618 I[3 * i + 2] = T[i].ix[2];
619 }
620 gridData.gridMesh->addSubMesh(std::span(I), Cogs::PrimitiveType::TriangleList);
621 }
622
623 vec2 s(1.f / gridResolution, 1.f / gridResolution);
624 auto vertices = gridData.gridMesh->mapPositions(0, (gridResolution + 1)*(gridResolution + 1));
625 for (int j = 0; j <= gridResolution; j++) {
626 for (int i = 0; i <= gridResolution; i++) {
627 float edge = 0;
628 if (j == 0) { edge = 3 + (i < gridResolution / 2 ? 0.f : 0.5f); }
629 else if (j == gridResolution) { edge = 4 + (i < gridResolution / 2 ? 0.f : 0.5f); }
630 else if (i == 0) { edge = 1 + (j < gridResolution / 2 ? 0.f : 0.5f); }
631 else if (i == gridResolution) { edge = 2 + (j < gridResolution / 2 ? 0.f : 0.5f); }
632 vertices[(gridResolution + 1)*j + i].x = s.x*i;
633 vertices[(gridResolution + 1)*j + i].y = s.y*j;
634 vertices[(gridResolution + 1)*j + i].z = edge;
635 }
636 }
637
638 gridData.gridMesh->setBounds(Cogs::Geometry::BoundingBox{ { 0.f, 0.f, 0.f },{ 1.0f, 1.0f, 1.0f } });
639 }
640
641 void setupProxyMesh(Context* context, const AdaptivePlanarGridComponent & gridComp, AdaptivePlanarGridData& gridData)
642 {
643 // Create dummy geometry with insufficient vertices for a single triangle,
644 // so it will produce no rendering output. But since we set the bounding
645 // box, it will push the near and far planes so the child tiles won't be
646 // culled unnecessary.
647 gridData.proxyMesh = context->meshManager->create();
648 vector<uint32_t> I(1);
649 gridData.proxyMesh->clearIndexes();
650 gridData.proxyMesh->addSubMesh(std::span(I), Cogs::PrimitiveType::TriangleList);
651 gridData.proxyMesh->setBounds(Cogs::Geometry::BoundingBox{ { 0.f, 0.f, 0.f },{ 1.f, 1.f, 1.f } });
652
653 auto meshComponent = gridComp.getComponent<MeshComponent>();
654 if (meshComponent) {
655 meshComponent->meshHandle = gridData.proxyMesh;
656 meshComponent->setChanged();
657 }
658 }
659
660 void setupDebugMesh(Context * context, const AdaptivePlanarGridComponent & gridComp, AdaptivePlanarGridData & gridData)
661 {
662 gridData.debugGraphics = context->store->createChildEntity("Default", gridComp.getContainer(), "Debug Graphics");
663 gridData.debugMesh = context->meshManager->create();
664 auto matComp = gridData.debugGraphics->getComponent<MaterialComponent>();
665 matComp->diffuseColor = vec4(1.f, 0.f, 0.f, 1.f);
666 matComp->enableLighting = false;
667 }
668
669 void updateTileTopology(AdaptivePlanarGridData & gridData)
670 {
671 gridData.tiles.clear();
672 gridData.tiles.reserve(leaves.size());
673
674 for (size_t i = 0; i < leaves.size(); i++) {
675 const AdaptivePlanarGridIndex & leaf = leaves[i];
676
677 // Determine refinement level of neighbors
678 int levelSize = 1 << leaf.level;
679 int edgeLevels[4];
680
681 int shifts[4][2] = {
682 { -1, 0 },
683 { 1, 0 },
684 { 0, -1 },
685 { 0, 1 }
686 };
687
688 for (int e = 0; e < 4; e++) {
689 int xIndex = int(leaf.xIndex) + shifts[e][0];
690 int yIndex = int(leaf.yIndex) + shifts[e][1];
691 int edgeLevel = leaf.level;
692
693 if ((0 <= xIndex) && (xIndex < levelSize) && (0 <= yIndex) && (yIndex < levelSize)) {
694
695 int currLevel = 0;
696 int currQuadNode = 0;
697 while (currLevel < maxLevel) {
698
699 if (currQuadNode == AdaptivePlanarGridQuadTreeNode::DeadEnd) {
700 // child has been culled, we can use whatever ref level we want
701 break;
702 }
703 else if (currQuadNode == AdaptivePlanarGridQuadTreeNode::Leaf) {
704 // we prematurely hit a leaf, restrict refinement level to this node
705 edgeLevel = currLevel - 1;
706 break;
707 }
708
709 // We have a valid node, we may descend
710 if (currLevel == leaf.level) {
711 // we have found the node, and if it is a leaf, we have its
712 // refinement level. Otherwise, if it is an internal node,
713 // it has more refined children that will match this level
714 // at the boundary.
715 //assert(quadTree[currQuadNode].debugIndex.xIndex == xIndex);
716 //assert(quadTree[currQuadNode].debugIndex.yIndex == yIndex);
717 break;
718 }
719
720 // which bit to inspect to determine which child to descend into
721 int currBit = leaf.level - currLevel - 1;
722 int whichChild = 2 * ((yIndex >> currBit) & 1) + ((xIndex >> currBit) & 1);
723 currQuadNode = quadTree[currQuadNode].children[whichChild];
724 currLevel++;
725 }
726 }
727
728 edgeLevels[e] = max(0, 5 - max(0, leaf.level - edgeLevel));
729 }
730
731 gridData.tiles.push_back(AdaptivePlanarGridTile{ leaf,{
732 uint8_t(edgeLevels[0]),
733 uint8_t(edgeLevels[1]),
734 uint8_t(edgeLevels[2]),
735 uint8_t(edgeLevels[3]) },
736 vec4(0.f),
737 vec4(0.f),
739 }
740 }
741
742
743}
744
745Cogs::Core::AdaptivePlanarGridSystem::AdaptivePlanarGridSystem(Memory::Allocator * allocator, SizeType /*capacity*/) :
746 ComponentSystemWithDataPool(allocator, 16)
747{
748 simplexScratch.resize(simplexScratchSize(3, 4, 8));
749}
750
751
752
753
754
755
756
757
758void Cogs::Core::AdaptivePlanarGridSystem::registerMaterial(const AdaptivePlanarGridComponent * gridComp, MaterialHandle material, AdaptivePlanarGridMaterialData::InitMaterialInstanceCallback * initMaterialInstanceCallback, void * initMaterialInstanceData)
759{
760 auto & gridData = getData(gridComp);
761
762 auto & materialData = gridData.materialData;
763
764 materialData.material = material;
765 materialData.lodLevelsKey = material->getVec4Key("lodLevels");
766 materialData.texCoordWorldTransformKey = material->getVec4Key("texCoordWorldTransform");
767 materialData.texCoordUnitTransformKey = material->getVec4Key("texCoordUnitTransform");
768 materialData.texCoordLinearTransformKey = material->getVec4Key("texCoordLinearTransform");
769 materialData.initMaterialInstanceCallback = initMaterialInstanceCallback;
770 materialData.initMaterialInstanceData = initMaterialInstanceData;
771
772 auto * container = gridComp->getContainer();
773 for (auto & item : gridData.materialPool) {
774 item = context->materialInstanceManager->createMaterialInstance(gridData.materialData.material);
775 if (initMaterialInstanceCallback) {
776 initMaterialInstanceCallback(static_cast<MaterialInstance*>(item.get()), container, initMaterialInstanceData);
777 }
778 }
779}
780
781
782void Cogs::Core::AdaptivePlanarGridSystem::setTexCoordTransform(const AdaptivePlanarGridComponent * gridComp, const glm::mat2 transform, const glm::vec2 period)
783{
784 auto & gridData = getData(gridComp);
785 gridData.texCoordLinearTransform = transform;
786 gridData.texCoordPeriod = period;
787}
788
789
791{
792 for (const auto & gridComp : pool) {
793 const TransformComponent * lodRefTrComp = nullptr;
794 if (auto l = gridComp.lodReference.lock(); l) {
795 lodRefTrComp = l->getComponent<TransformComponent>();
796 }
797 if (!lodRefTrComp) {
798 lodRefTrComp = context->cameraSystem->getMainCamera()->getComponent<TransformComponent>();
799 }
800 if(!lodRefTrComp) continue;
801
802 std::vector<CameraComponent*> cameraComps;
803 for (auto & weak : gridComp.cameras) {
804 if (auto entity = weak.lock(); entity) {
805 if (auto * comp = entity->getComponent<CameraComponent>(); comp) {
806 if (((comp->flags & CameraFlags::EnableRender) != 0) && ((comp->layerMask & gridComp.layer) != 0)) {
807 cameraComps.push_back(comp);
808 }
809 }
810 }
811 }
812
813 if (cameraComps.empty()) {
814 if (auto * comp = lodRefTrComp->getComponent<CameraComponent>(); comp) {
815 if (((comp->flags & CameraFlags::EnableRender) != 0) && ((comp->layerMask & gridComp.layer) != 0)) {
816 cameraComps.push_back(comp);
817 }
818 }
819 else {
820 auto * mcomp = context->cameraSystem->getMainCamera();
821 if (((mcomp->flags & CameraFlags::EnableRender) != 0) && ((mcomp->layerMask & gridComp.layer) != 0)) {
822 cameraComps.push_back(mcomp);
823 }
824 }
825 }
826
827 auto & gridData = getData(&gridComp);
828 if (!gridData.initialized) {
829 setupGridMesh(context, gridComp.tileResolutionLog2, gridData);
830 setupProxyMesh(context, gridComp, gridData);
831 setupDebugMesh(context, gridComp, gridData);
832 gridData.tileEntitiesGroup = context->store->createChildEntity("Group", gridComp.getContainer(), "Tiles");
833
834 auto renderComponent = gridComp.getContainer()->getComponent<RenderComponent>();
835
836 if (renderComponent) {
837 gridData.objectId = renderComponent->objectId;
838 }
839
840 gridData.initialized = true;
841 }
842
843 if (gridComp.hasChanged()) {
844 setupGridMesh(context, gridComp.tileResolutionLog2, gridData);
845 }
846
847 auto trComp = gridComp.getComponent<TransformComponent>();
848
849 context->transformSystem->updateTransformData(*trComp);
850 const glm::mat4 worldToRoot = context->transformSystem->getLocalToWorld(trComp);
851 const glm::mat4 rootToWorld = glm::inverse(worldToRoot);
852 //const glm::mat4 worldToCam = cameraData.viewMatrix * worldToRoot;
853 //const glm::mat4 camToWorld = rootToWorld * cameraData.inverseViewMatrix;
854 //const glm::mat4 clipToWorld = rootToWorld * cameraData.inverseViewProjectionMatrix;
855 //const glm::mat4 worldToClip = cameraData.viewProjection * worldToRoot;
856
857 std::vector<CameraState> cameras;
858 for (size_t i = 0; i < cameraComps.size(); i++) {
859 auto * camComp = cameraComps[i];
860 auto & camData = context->cameraSystem->getData(camComp);
861 if (camData.viewportSize.x < 1.f || camData.viewportSize.y < 1.f) continue;
862 if ((camComp->flags & CameraFlags::EnableRender) == 0) continue;
863
864 // We run before the transform & camera systems (since we want to set position
865 // on items that should be processed by these), and thus, the camera matrices
866 // haven't been set up yet. We calc fresh view and use the projection from
867 // last frame.
868 auto * camTrComp = camComp->getComponent<TransformComponent>();
869 context->transformSystem->updateTransformData(*camTrComp);
870
871 const glm::mat4& inverseViewMatrix = context->transformSystem->getLocalToWorld(camTrComp);
872 const glm::mat4 viewMatrix = glm::inverse(inverseViewMatrix);
873
874
875 cameras.emplace_back(CameraState());
876 auto & c = cameras.back();
877
878
879 c.worldToView = viewMatrix * worldToRoot;
880 c.worldToClip = camData.projectionMatrix * c.worldToView;
881 c.viewToWorld = rootToWorld * inverseViewMatrix;
882 c.worldUnitXY = normalize(vec3(c.viewToWorld[0])) + normalize(vec3(c.viewToWorld[1]));
883 c.viewportSize = camData.viewportSize;
884 c.nearDistance = camComp->nearDistance;
885 c.farDistance = camComp->enableClippingPlaneAdjustment ? std::numeric_limits<float>::max() : camComp->farDistance;
886 }
887
888 if (gridComp.updateLoD) {
889 if (!gridData.materialData.material) continue;
890
891 if(!cameras.empty()) {
892 auto lodComponent = gridComp.getComponent<LodComponent>();
893
894 // Determine 2D orientation of camera z-axis, do determine patch draw order
895 context->transformSystem->updateTransformData(*lodRefTrComp);
896 const mat4 & viewToWorld = context->transformSystem->getLocalToWorld(lodRefTrComp);// cameraData.inverseViewMatrix;
897 if (std::isfinite(viewToWorld[2].x) && std::isfinite(viewToWorld[2].y)) {
898 const float theta = (float(gridData.tessellations) / (2.0f * glm::pi<float>())) * std::atan2(viewToWorld[2].y, viewToWorld[2].x);
899 gridData.camYaw = max(0, min(gridData.tessellations - 1, int(round(theta + 0.5f * gridData.tessellations))));
900 }
901 else {
902 gridData.camYaw = 0;
903 }
904
905 analyzeView(context, gridComp, gridData, cameras, false);
906 refineTiles(gridComp, gridData, cameras, viewToWorld, lodComponent->geometricTolerance);
907 updateTileTopology(gridData);
908 updateTileEntities(context, gridComp, gridData, worldToRoot);
909 }
910
911 if (gridComp.debugGeometry) {
912 // Clear any lingering debug geometry when LOD is enabled.
913 auto meshComp = gridData.debugGraphics->getComponent<MeshComponent>();
914 if (meshComp->meshHandle) {
916 }
917 }
918 }
919 else if (gridComp.debugGeometry) {
920 // Create debug geometry, if requested, and if we currently don't update the
921 // LoD, and it hasn't been done yet (meshHandle is null)
922 auto meshComp = gridData.debugGraphics->getComponent<MeshComponent>();
923
924 if (!meshComp->meshHandle) {
925 meshComp->meshHandle = gridData.debugMesh;
926
927 analyzeView(context, gridComp, gridData, cameras, true);
928 }
929 }
930
931 updateTileMaterialInstances(gridComp, gridData);
932
933 //const dvec2 extentMin = gridComp.extentMin - gridData.currentCoordinates + dvec2(gridComp.displaceMin);
934 //const dvec2 extentMax = gridComp.extentMax - gridData.currentCoordinates + dvec2(gridComp.displaceMax);
935
936 // Update bounding box. XY-bounds are defined via the transformation.
937 // TODO: handle position.
938 //gridData.proxyMesh->setBounds(Geometry::BoundingBox{ { extentMin.x, extentMin.y, gridComp.displaceMin.z }, { extentMax.x, extentMax.y, gridComp.displaceMax.z } });
939 }
940}
941
void setChanged()
Sets the component to the ComponentFlags::Changed state with carry.
Definition: Component.h:202
ComponentType * getComponent() const
Definition: Component.h:159
class Entity * getContainer() const
Get the container currently owning this component instance.
Definition: Component.h:151
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
Definition: Entity.h:35
bool debugGeometry
Enable creation of miscellaneous debug geometry, like the intersected frustum and so on.
bool updateLoD
Enable/disable updates of level-of-detail hierarchy.
glm::vec3 displaceMin
Minimum corner of vertex displacement. Forwarded to misc bounding boxes.
glm::vec3 displaceMax
Maximum corner of vertex displacement. Forwarded to misc bounding boxes.
WeakEntityPtr lodReference
Entity to use as center reference to level of detail calculations, defaults to main camera.
glm::dvec2 extentMax
Maximum corner of extent to cover.
int tileResolutionLog2
Internal resolution of each tile in the grid.
RenderLayers layer
Layer used to render tiles.
std::vector< WeakEntityPtr > cameras
List of cameras for which frustums will be used to deduce visible geometry, defaults to main camera.
glm::dvec2 extentMin
Minimum corner of extent to cover.
Context * context
Pointer to the Context instance the system lives in.
void update()
Updates the system state to that of the current frame.
Component system with parallel data per component stored in a pool similar to how the components them...
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class EntityStore * store
Entity store.
Definition: Context.h:231
EntityPtr createChildEntity(const StringView &type, ComponentModel::Entity *parent, const StringView &name=StringView())
Create a new Entity, parenting it to the given parent.
Contains data describing level of detail behavior for the entity the component belongs to.
Definition: LodComponent.h:43
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
Base component for all rendering content.
constexpr void setVisible(bool visible)
Set the specific visibility.
Contains information on how the entity behaves in the scene.
Renders a mesh with flexible submesh usage.
std::vector< MaterialInstanceHandle > materials
Materials used to render individual sub-meshes.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
glm::dvec3 getOrigin() const
Gets the Origin offset of the scene.
void updateTransformData(const TransformComponent &component)
Force an update of the transform data associated with the given component.
Log implementation class.
Definition: LogManager.h:139
Base allocator implementation.
Definition: Allocator.h:30
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
COGSCORE_DLL_API SimplexResult simplexMaximize(Context *context, uintptr_t scratch, double *solutions, const int variableCount, const double *objectives, const int objectiveCount, const double *restrictions, const int restrictionCount, const int maxIterationCount, const bool printSteps=false)
Find the maximum of a linear programming problem using the simplex method.
Definition: Simplex.cpp:452
@ EnableRender
Renderable.
COGSCORE_DLL_API size_t simplexScratchSize(const size_t variableCount, const size_t objectiveCount, const size_t restrictionCount)
Number of bytes of scratch array required by simplexMaximize.
Definition: Simplex.cpp:440
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
ComponentIndex SizeType
Type used to track the size of pools.
Definition: Component.h:19
Exposes material properties for legacy entities and code.
Material instances represent a specialized Material combined with state for all its buffers and prope...
void setBounds(Geometry::BoundingBox box)
Set custom bounds for the mesh.
Definition: Mesh.h:298
void clearIndexes()
Clear all index data, also clearing all sub-meshes.
Definition: Mesh.h:607
MappedStream< glm::vec3 > mapPositions(const size_t start, const size_t end)
Map the position stream for write access, range between start and end.
Definition: Mesh.h:364
void addSubMesh(std::span< uint32_t > collection, PrimitiveType::EPrimitiveType primitiveType)
Add a sub-mesh to the Mesh.
Definition: Mesh.cpp:189
void setPositions(std::span< const glm::vec3 > positions)
Set the position data of the Mesh.
Definition: Mesh.h:315
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
@ LineList
List of lines.
Definition: Common.h:120
@ TriangleList
List of triangles.
Definition: Common.h:116