Cogs.Core
AssetSystem.cpp
1#include "AssetSystem.h"
2
3#include "Context.h"
4#include "EntityStore.h"
5
6#include "Resources/AssetManager.h"
7#include "Resources/ModelManager.h"
8#include "Resources/MaterialManager.h"
9
10#include "Serialization/SceneReader.h"
11#include "Serialization/EntityCreator.h"
12
13#include "Systems/Core/TransformSystem.h"
14#include "Systems/Core/CameraSystem.h"
15
16#include "Resources/ResourceStore.h"
17#include "Resources/ModelManager.h"
18#include "Serialization/ModelLoader.h"
19
20#include "Systems/Core/ClipShapeSystem.h"
21#include "Systems/Core/RenderSystem.h"
22#include "Systems/Core/MeshSystem.h"
23#include "Systems/Core/SceneSystem.h"
24#include "Systems/Core/DynamicComponentSystem.h"
25
26#include "Resources/MaterialInstance.h"
27
28#include "Platform/Instrumentation.h"
29
30#include "Services/Variables.h"
31#include "Services/QualityService.h"
32
33#include "Utilities/Parallel.h"
34
35#include "AssetInstanceData.h"
36
37#include "Foundation/Collections/Pool.h"
38#include "Foundation/Logging/Logger.h"
39#include "Foundation/Platform/IO.h"
40#include "Foundation/Platform/Timer.h"
41#include "Foundation/Memory/MemTypeAllocator.h"
42
43
44using namespace Cogs::Geometry;
45
46namespace
47{
48 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("AssetSystem");
49
50 constexpr Cogs::StringView lodFreezeVariableName = "resources.assets.lodFreeze";
51 constexpr Cogs::StringView cullModelsName = "resources.assets.cullModels";
52 constexpr Cogs::StringView maxModelsInFlightVariableName = "resources.assets.maxModelsInFlight";
53 constexpr Cogs::StringView maxModelLoadsPerFrameVariableName = "resources.assets.maxModelLoadsPerFrame";
54 constexpr Cogs::StringView maxDistanceVariableName = "resources.assets.maxDistance";
55 constexpr Cogs::StringView maxLodDepthVariableName = "resources.assets.maxLodDepth";
56 constexpr Cogs::StringView allowMaterialOverride = "resources.assets.allowMaterialOverride";
57
58 constexpr bool kDefaultLodFreeze = false;
59 constexpr int kDefaultMaxModelsInFlight = 100;
60 constexpr int kDefaultMaxModelLoadsPerFrame = 100;
61 constexpr float kDefaultMaxDistance = -1.0f;
62 constexpr int kDefaultMaxLodDepth = -1;
63 constexpr bool kDefaultAllowMaterialOverride = true;
64
65 const Cogs::Core::StringRef idsString = Cogs::Core::Strings::add("ids");
66 const Cogs::Core::StringRef bboxString = Cogs::Core::Strings::add("bbox");
67 const Cogs::Core::StringRef errorsString = Cogs::Core::Strings::add("errors");
68 const Cogs::Core::StringRef byteSizeString = Cogs::Core::Strings::add("byteSize");
69
70 constexpr uint32_t NoValue = static_cast<uint32_t>(-1);
71
72 template<class T> using AssetInstanceVector = std::vector<T, Cogs::Memory::MemTypeAllocator<T, Cogs::MemBlockType::AssetInstance>>;
73 template<class T> using AssetSystemVector = std::vector<T, Cogs::Memory::MemTypeAllocator<T, Cogs::MemBlockType::AssetSystemData>>;
74
75 bool cullModelsValue = true;
76
77}
78
79namespace Cogs::Core
80{
82 {
83 enum EFlags
84 {
85 Staging = SceneEntityFlags::Last << 1,
86 Ready = Staging << 1,
87 Spawned = Ready << 1,
88 };
89 };
90
92 {
93 uint32_t index = 0;
94 uint32_t flags = 0;
95
96 union
97 {
98 struct
99 {
100 uint32_t target;
101 uint32_t current;
102 uint32_t index;
103 } lod;
104
105 struct
106 {
107 uint32_t index;
108 } model;
109
110 struct AssetInstanceData * assetInstance;
111 };
112
113 uint32_t nextSibling = ::NoValue;
114 uint32_t nextLodSibling = ::NoValue;
115
116 EntityPtr entity;
117 ComponentModel::Entity * entityPtr = nullptr;
118
119 void setFlag(uint32_t flag, bool value) { flags = (flags & ~flag) | (uint32_t(-(int32_t)value) & flag); }
120
121 void setStaging(bool value) { setFlag(EntityInstanceFlags::Staging, value); }
122 bool isStaging() const { return flags & EntityInstanceFlags::Staging; }
123
124 void setReady(bool value) { setFlag(EntityInstanceFlags::Ready, value); }
125 bool isReady() const { return flags & EntityInstanceFlags::Ready; }
126
127 void setSpawned(bool value) { setFlag(EntityInstanceFlags::Spawned, value); }
128 bool isSpawned() const { return flags & EntityInstanceFlags::Spawned; }
129
130 bool isAtDesiredLod() const { return lod.current == lod.target; }
131 bool isVisible() const { return lod.target != ::NoValue; }
132
133 bool isLodGroup() const { return flags & SceneEntityFlags::LodGroup; }
134 bool isAsset() const { return flags & SceneEntityFlags::Asset; }
135 bool isModel() const { return flags & SceneEntityFlags::Model; }
136 bool hasChildren() const { return flags & SceneEntityFlags::Children; }
137 bool hasError() const { return flags & SceneEntityFlags::Error; }
138 };
139
141 {
142 AssetInstanceVector<float> errors;
143 AssetInstanceVector<uint32_t> ids;
144 AssetInstanceVector<uint32_t> lods;
145
146 uint32_t previousTarget = ::NoValue;
147 uint32_t nextLodSibling = ::NoValue;
148 };
149
151 {
152 ModelHandle model;
153 std::string path;
154 uint32_t byteSize = 0;
155 uint32_t references = 0;
156 struct AssetModelRequest * request = nullptr;
157 bool requested = false;
158 bool ready = false;
159
160 void setReady() { ready = true; }
161
162 void unref()
163 {
164 assert(references > 0 && "Cannot dereference with zero count.");
165
166 if (--references == 0) {
167 model = {};
168 ready = false;
169 requested = false;
170
171 if (request) {
172 request->canceled = true;
173 }
174 }
175 }
176
177 void ref()
178 {
179 ++references;
180 requested = true;
181 }
182 };
183
185 {
186 uint32_t index = ::NoValue;
187 uint32_t fileIndex = ::NoValue;
188 uint32_t part = ::NoValue;
189 uint32_t depth = 0;
190 AssetGpuStats cost;
191 bool requested = false;
192 };
193
195 {
196 SceneInstanceData() : boxes(MemBlockType::AssetInstance) {}
197
198 AssetInstanceVector<uint32_t> roots; // One per scene top level entity
199 AssetInstanceVector<EntityInstanceData> entities; // One per scene entity
200
201 AssetInstanceVector<LodInstanceData> lods; // One per scene lod group
202 Memory::TypedBuffer<Geometry::BoundingBox> boxes; // One per scene entity
203
204 AssetInstanceVector<ModelFileData> modelFiles;
205 AssetInstanceVector<ModelInstanceData> models;
206
207 EntityInstanceData & operator[](size_t index) { return entities[index]; }
208
209 bool initialized = false;
210
211 Geometry::BoundingBox & getLocalBounds(const EntityInstanceData & e) { return boxes[e.index]; }
212 LodInstanceData & getLodData(const EntityInstanceData & e) { assert(e.isLodGroup()); return lods[e.lod.index]; }
213 ModelInstanceData & getModelData(const EntityInstanceData & e) { return models[e.model.index]; }
214 };
215
217 {
219 spawnedEntities(MemBlockType::AssetInstance),
220 destroyedEntities(MemBlockType::AssetInstance)
221 {}
222
223 struct AssetSystemData * systemData = nullptr;
224
225 AssetInstanceData * parentInstance = nullptr;
226 AssetInstanceData * next = nullptr;
227 AssetInstanceData * prev = nullptr;
228
229 MaterialInstanceHandle material;
230
231 StringView path;
232 AssetHandle asset;
233 uint16_t assetGeneration = 0;
234
235 Entity * container = nullptr;
237
238 uint32_t objectId = NoObjectId;
239 RenderLayers renderLayers = RenderLayers::Default;
241
242 SceneInstanceData scene;
243
244 AssetInstanceStats stats;
245
246 std::vector<uint32_t> selectedIdRanges;
247
248 bool visible = false;
249 bool onDemand = false;
250 bool relativePaths = false;
251 bool overrideMaterial = false;
252 bool cloneMaterial = false;
253 bool initialized = false;
254 bool freezeLod = false;
255 bool forceTolerance = false;
256
257 float tolerance = 1.0f;
258 float priority = 0.0f;
259 float minDistance = 1.f;
260
261 std::string directoryPath;
262
263 Memory::TypedBuffer<EntityId> spawnedEntities;
264 Memory::TypedBuffer<EntityId> destroyedEntities;
265
266 bool useOverrideMaterial() const { return overrideMaterial; }
267 bool useCloneMaterial() const { return cloneMaterial; }
268 bool useRelativePaths() const { return relativePaths; }
269
270 void spawned(EntityId entityId)
271 {
272 EntityId& dst = spawnedEntities.grow();
273 dst = entityId;
274
275 ++stats.frame.spawnedEntities;
276 ++stats.numEntities;
277 }
278
279 void destroyed(EntityId entityId)
280 {
281 EntityId& dst = destroyedEntities.grow();
282 dst = entityId;
283
284 ++stats.frame.destroyedEntities;
285 --stats.numEntities;
286 }
287
288 void link(AssetInstanceData * instance)
289 {
290 systemData = instance->systemData;
291 parentInstance = instance;
292
293 prev = instance;
294 next = instance->next;
295 if (instance->next) instance->next->prev = this;
296 instance->next = this;
297 }
298
299 void unlink()
300 {
301 prev->next = next;
302 if (next) next->prev = prev;
303 next = prev = nullptr;
304 }
305
306 void destroy();
307
308 SceneEntityDefinition & getDefinition(EntityInstanceData & instance)
309 {
310 return asset->definition.scene[instance.index];
311 }
312 };
313
315 {
316 Entity * container;
317 ComponentHandle component;
318 AssetHandle asset;
319
320 AssetInstanceData * instanceData;
321 };
322
324 {
326 instanceData(MemBlockType::AssetInstance),
327 requestPool(MemBlockType::AssetSystemData)
328 {}
329
332
333 AssetSystemVector<AssetModelRequest *> pendingRequests;
334 AssetSystemVector<AssetModelRequest *> inFlightRequests;
335 AssetSystemVector<AssetModelRequest *> stillInFlight;
336
337 // Scratch memory used by AssetSystem::update.
338 // Held here to minimize reallocations.
339 AssetSystemVector<AssetUpdate> newUpdates;
340 AssetSystemVector<AssetInstanceData*> currentOnDemandInstances;
341 AssetSystemVector<AssetUpdate> readyUpdates;
342 AssetSystemVector<AssetInstanceData*> liveAssetInstances;
343
344 // Matrices used by getLodReference, pulled from main camera
345 glm::mat4 worldFromView = glm::mat4(1.f);
346 glm::mat4 viewFromWorld = glm::mat4(1.f);
347 glm::mat4 clipFromWorld = glm::mat4(1.f);
348 glm::mat4 worldFromClip = glm::mat4(1.f);
349 float minDistance = 0.f;
350 float maxDistance = 0.f;
351 uint32_t maxLodDepth = 0;
352
353 MessageId entityCreatedId = NoMessage;
354 MessageId entityDestroyedId = NoMessage;
355
356 MaterialInstanceHandle defaultMaterialHandle;
357
358 void removeRequests(AssetInstanceData* assetInstance)
359 {
360 for (AssetModelRequest* req : pendingRequests) {
361 if (req->assetInstance == assetInstance) req->canceled = true;
362 }
363 for (AssetModelRequest* req : inFlightRequests) {
364 if (req->assetInstance == assetInstance) req->canceled = true;
365 }
366 for (AssetModelRequest* req : stillInFlight) {
367 if (req->assetInstance == assetInstance) req->canceled = true;
368 }
369 }
370
371 };
372
373 void AssetInstanceData::destroy()
374 {
375 unlink();
376
377 systemData->instanceData.destroy(this);
378 }
379
380 uint32_t findFirstLodChild(const AssetDefinition & definition, uint32_t firstChild, int32_t targetLevel);
381
382 void initializeAssetInstance(AssetInstanceData & assetInstance)
383 {
384 auto initTime = assetInstance.stats.frame.startInit();
385 assetInstance.assetGeneration = static_cast<uint16_t>(assetInstance.asset->getGeneration());
386 assetInstance.directoryPath = IO::parentPath(assetInstance.asset->getSource());
387
388 assetInstance.stats = {};
389#if ENABLE_COST_TRACKING
390 assetInstance.stats.max.memory = 512 * 1024 * 1024;
391 assetInstance.stats.max.drawCalls = 100'000;
392 assetInstance.stats.max.primitiveCount = 10'000'000;
393#endif
394 assetInstance.stats.frameLimits.processingTime = 0.010;
395
396 const AssetDefinition & definition = assetInstance.asset->definition;
397 SceneInstanceData & scene = assetInstance.scene;
398
399 scene.roots.reserve(definition.scene.numTopLevelEntities);
400 scene.entities.resize(definition.scene.entities.size());
401 scene.lods.reserve(definition.scene.numLodGroups);
402 scene.boxes.resize(definition.scene.entities.size());
403
404 std::unordered_map<size_t, uint32_t> modelFileIndexes;
405
406 for (size_t i = 0; i < definition.scene.entities.size(); ++i) {
407 const SceneEntityDefinition& entityDef = definition.scene.entities[i];
408 EntityInstanceData & entityInstance = scene[i];
409 PropertyRange properties = definition.scene.getProperties(entityDef);
410
411 entityInstance.index = uint32_t(i);
412 entityInstance.flags = entityDef.flags;
413 entityInstance.nextSibling = entityDef.nextSibling;
414 entityInstance.nextLodSibling = entityDef.nextLodSibling;
415
416 if (entityDef.parentIndex == ::NoValue) {
417 scene.roots.emplace_back(uint32_t(i));
418 }
419
420 // Entity is a AssetSystem managed lod-group
421 if (entityDef.isLodGroup()) {
422 entityInstance.lod.index = (uint32_t)scene.lods.size();
423 auto & lodData = scene.lods.emplace_back();
424
425 entityInstance.lod.current = entityInstance.lod.target = ::NoValue;
426
427 std::span<const uint32_t> lodIds = properties.getProperty(idsString, std::span<const uint32_t>());
428 if (!lodIds.empty()) {
429 size_t lodCount = lodIds.size();
430 if (lodCount % 2) {
431 LOG_ERROR(logger, "Lod id range array should have an even number of items (n=%zu)", lodCount);
432 }
433 else {
434 lodData.ids.clear();
435 lodData.ids.reserve(lodCount);
436 for (size_t k = 0; k + 1 < lodCount; k += 2) {
437 uint32_t a = lodIds[k + 0];
438 uint32_t b = lodIds[k + 1];
439 if (b < a) {
440 LOG_ERROR(logger, "Lod id range contains an invalid range ([%u, %u])", a, b);
441 lodData.ids.clear();
442 break;
443 }
444 if (!lodData.ids.empty() && b < lodData.ids.back()) {
445 LOG_ERROR(logger, "Lod id ranges are not sorted.");
446 lodData.ids.clear();
447 break;
448 }
449 lodData.ids.emplace_back(a);
450 lodData.ids.emplace_back(b);
451 }
452 }
453 }
454
455 std::span<const float> bboxValues = properties.getProperty(bboxString, std::span<const float>());
456 if (bboxValues.size() == 6) {
457 scene.boxes[entityDef.index] = Geometry::BoundingBox(std::span<const float, 6>(bboxValues));
458 }
459 else {
460 LOG_ERROR(logger, "Lod level item bounding box has wrong number of elements (%zu != 6)", bboxValues.size());
461 scene.boxes[entityDef.index] = Cogs::Geometry::BoundingBox();
462 }
463
464 std::span<const float> errorValues = properties.getProperty(errorsString, std::span<const float>());
465 if (errorValues.size() != entityDef.lod.numLods) {
466 LOG_WARNING(logger, "Lod level item count (=%u) does not match number of error values (=%zu)", entityDef.lod.numLods, errorValues.size());
467 }
468
469 if (errorValues.size()) {
470 lodData.errors.assign(errorValues.begin(), errorValues.end());
471 // We have at least one item, fill until we have enought exponentially.
472 while (lodData.errors.size() < entityDef.lod.numLods) lodData.errors.push_back(lodData.errors.back() * lodData.errors.back());
473 }
474
475 // No values at all
476 else {
477 lodData.errors.resize(entityDef.lod.numLods);
478
479 // If we have a bounding box, use its diagonal as a scale for the levels
480 float scale = 1.f;
481 if (!isEmpty(scene.boxes[entityDef.index])) scale = glm::distance(scene.boxes[entityDef.index].min, scene.boxes[entityDef.index].max);
482
483 for (size_t l = 0; l < lodData.errors.size(); ++l) {
484 lodData.errors[l] = scale * scale * float(l) * float(l);
485 }
486 }
487
488 lodData.lods.resize(entityDef.lod.numLods);
489 for (uint32_t j = 0; j < entityDef.lod.numLods; ++j) {
490 lodData.lods[j] = findFirstLodChild(definition, entityDef.firstChild, j);
491 }
492
493 }
494
495 else if (entityDef.isModel()) {
496 uint32_t modelIndex = uint32_t(scene.models.size());
497 ModelInstanceData & modelData = scene.models.emplace_back();
498 modelData.index = entityInstance.index;
499 modelData.cost = { 0, 0, 0 };
500 modelData.part = entityDef.model.part;
501 modelData.depth = entityDef.model.depth;
502 entityInstance.model.index = modelIndex;
503
504 if (entityInstance.hasError()) continue;
505
506 // Extract bounding box if provided
507 Cogs::Geometry::BoundingBox bbox;
508 if (entityDef.numProperties) {
509 std::span<const float> bboxValues = properties.getProperty(bboxString, std::span<const float>());
510 if (bboxValues.size() == 6) {
511 bbox = Geometry::BoundingBox(std::span<const float, 6>(bboxValues));
512 }
513 }
514 scene.boxes[entityDef.index] = bbox;
515
516 // Link to existing ModelFileData or create new instance
517 std::string path;
518 const PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDef.model.index);
519 if (pathItem.type == PropertyType::UnsignedInteger) {
520 path = createAssetResourcePathFromIndex(pathItem.uintValue, AssetResourceType::Model);
521 }
522 else if (pathItem.type == PropertyType::String) {
523 path = definition.scene.properties.getString(entityDef.model.index).to_string();
524 }
525
526 size_t code = StringView(path).hash();
527 if (auto foundIt = modelFileIndexes.find(code); foundIt != modelFileIndexes.end()) {
528 modelData.fileIndex = foundIt->second;
529 }
530
531 else {
532 uint32_t fileIndex = (uint32_t)scene.modelFiles.size();
533 ModelFileData & modelFile = scene.modelFiles.emplace_back();
534
535 if (assetInstance.useRelativePaths() && IO::isRelative(path)) {
536 modelFile.path = IO::combine(assetInstance.directoryPath, std::move(path));
537 } else {
538 modelFile.path = std::move(path);
539 }
540
541 int byteSize = properties.getProperty(byteSizeString, 0);
542 if (byteSize) {
543 modelFile.byteSize = byteSize;
544 } else {
545 modelFile.byteSize = 1000;
546 }
547
548 modelData.fileIndex = fileIndex;
549 modelFileIndexes[code] = fileIndex;
550 }
551 }
552 }
553
554 scene.initialized = true;
555 }
556
557 glm::vec3 euclidean(const glm::vec4& a)
558 {
559 return (1.f / a.w)*glm::vec3(a);
560 }
561
563 {
564 glm::vec3 frustumEdgesP[4];
565 glm::vec3 frustumEdgesD[4];
566
567 glm::vec3 lodRef = glm::vec3(0, 0, 0);
568 glm::vec4 planes[12] = { glm::vec4(0,0,0,1), glm::vec4(0,0,0,1),glm::vec4(0,0,0,1),glm::vec4(0,0,0,1),glm::vec4(0,0,0,1) };
569 uint32_t planeCount = 6;
570 bool invertedClip = false; // Invert the test for the clip planes (plane 6 and up).
571
572 float nearDistance = 1.f;
573 float maxDistance = FLT_MAX;
574 uint32_t maxLodDepth = ~0u;
575 uint32_t frustumCullIn = 0;
576 uint32_t frustumCullOut = 0;
577 uint32_t bboxCullOut = 0;
578 };
579
580 void getLodReference(Context* context, AssetInstanceData& assetInstance, AssetLodReference & lodRef, Entity * ref)
581 {
582 const AssetSystemData* systemData = assetInstance.systemData;
583
585 if (!trCompRef) return;
586 if (trCompRef->hasChanged()) {
587 context->transformSystem->updateTransformData(*trCompRef);
588 }
589
590 const glm::mat4& worldFromModel = context->transformSystem->getLocalToWorld(ref->getComponent<TransformComponent>());
591 const glm::mat4 modelFromWorld = glm::inverse(worldFromModel);
592 const glm::mat4 modelFromView = modelFromWorld * systemData->worldFromView;
593 const glm::mat4 rawViewProjectionTransposed = glm::transpose(systemData->clipFromWorld * worldFromModel);
594
595 lodRef.lodRef = euclidean(modelFromView * glm::vec4(0, 0, 0, 1));
596 lodRef.nearDistance = std::max(assetInstance.minDistance, systemData->minDistance);
597 lodRef.planes[0] = rawViewProjectionTransposed[3] + rawViewProjectionTransposed[0]; // Left.
598 lodRef.planes[1] = rawViewProjectionTransposed[3] - rawViewProjectionTransposed[0]; // Right.
599 lodRef.planes[2] = rawViewProjectionTransposed[3] + rawViewProjectionTransposed[1]; // Bottom.
600 lodRef.planes[3] = rawViewProjectionTransposed[3] - rawViewProjectionTransposed[1]; // Top.
601
602 if (0 < systemData->maxDistance) {
603 lodRef.planes[4] = glm::transpose(systemData->viewFromWorld * worldFromModel) * glm::vec4(0, 0, 1, systemData->maxDistance);
604 lodRef.maxDistance = systemData->maxDistance;
605 }
606 else {
607 lodRef.planes[4] = glm::vec4(0, 0, 0, 1); // will always pass
608 }
609
610 // calc near plane through eye
611 lodRef.planes[5] = glm::vec4(normalize(euclidean(modelFromView * glm::vec4(0, 0, -1, 1)) - lodRef.lodRef), 0.f);
612 lodRef.planes[5].w = -dot(glm::vec3(lodRef.planes[5]), lodRef.lodRef);
613
614 lodRef.planeCount = 6;
615 if (const ClipShapeComponent* clipComp = assetInstance.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
616 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
617 switch (clipData.shape) {
619 break;
622 assert(clipData.planeCount == 6);
623 for (size_t i = 0; i < clipData.planeCount; i++) {
624 lodRef.planes[lodRef.planeCount++] = clipData.planes[i] * worldFromModel;
625 }
626 lodRef.invertedClip = clipData.shape == ClipShapeType::InvertedCube;
627 break;
628 default:
629 assert(false && "Unhandled case");
630 break;
631 }
632 }
633
634 lodRef.maxLodDepth = systemData->maxLodDepth;
635
636 const glm::mat4 M = modelFromWorld * systemData->worldFromClip;
637 for (size_t i = 0; i < 4; i++) {
638 glm::vec3 a = euclidean(M * glm::vec4((i & 1) ? -1.f : 1.f,
639 (i & 2) ? -1.f : 1.f,
640 -1.f, 1.f));
641 glm::vec3 b = euclidean(M * glm::vec4((i & 1) ? -1.f : 1.f,
642 (i & 2) ? -1.f : 1.f,
643 1.f, 1.f));
644 float az = dot(lodRef.planes[5], glm::vec4(a, 1.f));
645 float bz = dot(lodRef.planes[5], glm::vec4(b, 1.f));
646 lodRef.frustumEdgesD[i] = (1.f / (bz - az)) * (b - a); // Scale direction to unit step along depth in view
647 lodRef.frustumEdgesP[i] = a - az * lodRef.frustumEdgesD[i]; // Move back until plane through eye
648 }
649 }
650
651 bool isOutside(AssetLodReference& lodReference, const Geometry::BoundingBox & bbox)
652 {
653 float minDist, maxDist;
654 bool outsideOne = false;
655 size_t fullyInside = 0;
656 for (size_t j = 0; j < 6; j++) {
657 minDist = FLT_MAX;
658 maxDist = -FLT_MAX;
659 for (size_t i = 0; i < 8; i++) {
660 glm::vec4 p((i & 1) ? bbox.min.x : bbox.max.x,
661 (i & 2) ? bbox.min.y : bbox.max.y,
662 (i & 4) ? bbox.min.z : bbox.max.z,
663 1.f);
664 float t = dot(lodReference.planes[j], p);
665 minDist = std::min(minDist, t);
666 maxDist = std::max(maxDist, t);
667 }
668 if (maxDist < 0.f) {
669 outsideOne = true;
670 }
671 if (0 <= minDist) {
672 fullyInside++;
673 }
674 }
675
676 { // ClipShape clipping
677 size_t fullyInsideClip = 0;
678 bool outsideOneClip = false;
679 for (size_t j = 6; j < lodReference.planeCount; j++) {
680 float min = FLT_MAX;
681 float max = -FLT_MAX;
682 for (size_t i = 0; i < 8; i++) {
683 glm::vec4 p((i & 1) ? bbox.min.x : bbox.max.x,
684 (i & 2) ? bbox.min.y : bbox.max.y,
685 (i & 4) ? bbox.min.z : bbox.max.z,
686 1.f);
687 float t = dot(lodReference.planes[j], p);
688 min = std::min(min, t);
689 max = std::max(max, t);
690 }
691 if (max < 0.f) {
692 outsideOneClip = true;
693 }
694 if (0 <= min) {
695 fullyInsideClip++;
696 }
697 }
698
699 if (lodReference.invertedClip == false) {
700 outsideOne = outsideOne || outsideOneClip;
701 fullyInside += fullyInsideClip;
702 }
703 else {
704 if (6 + fullyInsideClip == lodReference.planeCount) {
705 return true;
706 }
707 }
708 }
709
710 if (outsideOne) {
711 // bbox is completely outside one of the frustum planes, absolutely outside.
712 lodReference.frustumCullOut++;
713 return true;
714 }
715 else if (fullyInside == lodReference.planeCount) {
716 // bbox is completely inside all of the frustum planes, absolutely inside.
717 lodReference.frustumCullIn++;
718 return false;
719 }
720 else {
721 // bbox intersects some of the planes, indeterminate.
722 // Tighten frustum around depth-range of bbox and check that against the bbox planes
723 minDist = std::max(0.f, std::min(lodReference.maxDistance, minDist));
724 maxDist = std::max(0.f, std::min(lodReference.maxDistance, maxDist));
725 uint32_t planes = 63u;
726 for (size_t i = 0; i < 4; i++) {
727 glm::vec3 pa = lodReference.frustumEdgesP[i] + minDist * lodReference.frustumEdgesD[i];
728 planes = planes & ((pa.x < bbox.min.x ? 1 : 0) |
729 (pa.y < bbox.min.y ? 2 : 0) |
730 (pa.z < bbox.min.z ? 4 : 0) |
731 (bbox.max.x < pa.x ? 8 : 0) |
732 (bbox.max.y < pa.y ? 16 : 0) |
733 (bbox.max.z < pa.z ? 32 : 0));
734 glm::vec3 pb = lodReference.frustumEdgesP[i] + maxDist * lodReference.frustumEdgesD[i];
735 planes = planes & ((pb.x < bbox.min.x ? 1 : 0) |
736 (pb.y < bbox.min.y ? 2 : 0) |
737 (pb.z < bbox.min.z ? 4 : 0) |
738 (bbox.max.x < pb.x ? 8 : 0) |
739 (bbox.max.y < pb.y ? 16 : 0) |
740 (bbox.max.z < pb.z ? 32 : 0));
741 }
742 if (planes != 0) {
743 lodReference.bboxCullOut++;
744 return true;
745 }
746 }
747
748 // Cannot guarantee that it is not visible
749 lodReference.frustumCullIn++;
750 return false;
751 }
752
753 void updateLodGroupDesiredLevel(AssetInstanceData & instance, AssetLodReference & lodReference, EntityInstanceData & lodInstance, uint32_t lodDepth, const float tolerance)
754 {
755 auto & localBounds = instance.scene.getLocalBounds(lodInstance);
756 auto & lodData = instance.scene.getLodData(lodInstance);
757 auto & errors = lodData.errors;
758
759
760 lodData.previousTarget = lodInstance.lod.target;
761
762 if (!instance.visible) {
763 lodInstance.lod.target = ::NoValue;
764 return;
765 }
766
767 if (!instance.selectedIdRanges.empty() && !lodData.ids.empty()) {
768 // If lod level contains id ranges and we have specified which we
769 // are interested in, only instantiate lod level if there is some
770 // overlap between these two sets of ranges.
771
772 const std::vector<uint32_t>& selectedIds = instance.selectedIdRanges;
773 const size_t selectedCount = selectedIds.size() / 2;
774
775 const AssetInstanceVector<uint32_t>& lodIds = lodData.ids;
776 const size_t lodCount = lodIds.size() / 2;
777
778 // Since the two ranges are sorted, it is possible to do this more
779 // cleverly, reducing complexity from n^2 to n. But the number of
780 // ranges are assumed to be low (4 or less), the naive n^2 approach
781 // is probably ok.
782 bool overlap = false;
783 for (size_t j = 0; !overlap && j < selectedCount; j++) {
784 const uint32_t selectedMin = selectedIds[2 * j + 0];
785 const uint32_t selectedMax = selectedIds[2 * j + 1];
786
787 for (size_t i = 0; !overlap && i < lodCount; i++) {
788 const uint32_t lodMin = lodIds[2 * i + 0];
789 const uint32_t lodMax = lodIds[2 * i + 1];
790
791 // Check if there is some overlap between [selectedMin,seletedMax] and [lodMin,lodMax],
792 if (lodMin <= selectedMax && selectedMin <= lodMax) {
793 overlap = true;
794 }
795 }
796 }
797 if (!overlap) {
798 lodInstance.lod.target = ::NoValue;
799 return;
800 }
801 }
802
803 const bool outside = isOutside(lodReference, localBounds);
804 if (outside) {
805 lodInstance.lod.target = ::NoValue;
806 } else {
807 const uint32_t maxLodDepth = lodReference.maxLodDepth;
808 const size_t maxLocalLodDepth = maxLodDepth - std::min(maxLodDepth, lodDepth);
809
810 // lodref constrainted to bounding box.
811 glm::vec3 nearest = glm::max(localBounds.min, glm::min(localBounds.max, lodReference.lodRef));
812 auto d = glm::max(lodReference.nearDistance, glm::distance(nearest, lodReference.lodRef));
813
814 auto error = d * tolerance;
815
816 const size_t N = errors.size();
817 const size_t M = N - 1;
818 const size_t O = M - std::min(M, maxLocalLodDepth);
819 if (N == 0) {
820 // No lod-levels
821 lodInstance.lod.target = ::NoValue;
822 }
823 else if (lodInstance.lod.current == ::NoValue) {
824 // No geometry present, load the most crappy version to have something to show.
825 lodInstance.lod.target = static_cast<uint32_t>(M);
826 }
827 else if (M <= 8) {
828 // Small number of levels, use linear search
829 size_t i = O;
830 for (; i < M && errors[i + 1] < error; i++) {}
831 lodInstance.lod.target = static_cast<uint32_t>(i);
832 }
833 else {
834 // Bisection to find lod with appropriate error.
835 size_t i1 = M;
836 size_t i0 = O;
837 while (i0 + 1 < i1) {
838 size_t m = (i0 + i1) / 2;
839 assert(i0 != m);
840 assert(i1 != m);
841 bool less = error < errors[m];
842 i0 = less ? i0 : m; // random probability, so conditional move is prob better than branch prediction.
843 i1 = less ? m : i1;
844 }
845 if (i0 < i1) {
846 bool less = error < errors[i1];
847 i0 = less ? i0 : i1;
848 }
849 lodInstance.lod.target = static_cast<uint32_t>(i0);
850 }
851 }
852 }
853
854 uint32_t findFirstLodChild(const AssetDefinition & definition, uint32_t firstChild, int32_t targetLevel)
855 {
856 uint32_t childIndex = definition.scene.getChild(firstChild, 0);
857
858 for (int32_t i = 0; i < targetLevel; ++i) {
859 // Skip all on current lod level
860 auto ccDef = &definition.scene[childIndex];
861
862 while (ccDef->nextLodSibling != ::NoValue) {
863 ccDef = &definition.scene[ccDef->nextLodSibling];
864 }
865
866 childIndex = ccDef->nextSibling;
867 }
868
869 return childIndex;
870 }
871
872 void calculateModelCost(Context * /*context*/, ModelInstanceData & modelData, Model * model)
873 {
874 modelData.cost = { 0, 0, 0 };
875 //modelData.cost.memory = modelData.byteSize;
876
877 auto parts = modelData.part == ::NoValue ? std::span(model->parts) : std::span(&model->parts[modelData.part], &model->parts[modelData.part] + 1);
878
879 for (auto & part : parts) {
880 //TODO: Handle line, point etc.
881 if (part.vertexCount == ::NoValue && part.meshIndex != ::NoValue) {
882 auto & mesh = model->meshes[part.meshIndex];
883 modelData.cost.primitiveCount += mesh->getCount() / 3;
884 } else {
885 modelData.cost.primitiveCount += part.vertexCount / 3;
886 }
887
888 modelData.cost.drawCalls += part.meshIndex != ::NoValue ? 1 : 0;
889 }
890 }
891
892#if ENABLE_COST_TRACKING
893 void calculateCost(Context* context, AssetInstanceData & instance, EntityInstanceData & entityInstance)
894 {
895 if (entityInstance.hasError()) return;
896
897 if (entityInstance.isLodGroup() && entityInstance.lod.current != ::NoValue) {
898 auto & lodData = instance.scene.getLodData(entityInstance);
899 uint32_t childIndex = lodData.lods[entityInstance.lod.current];
900
901 while (childIndex != ::NoValue) {
902 calculateCost(context, instance, instance.scene[childIndex]);
903
904 childIndex = instance.scene[childIndex].nextLodSibling;
905 }
906 } else if (entityInstance.isAsset() && entityInstance.isSpawned()) {
907 auto & nestedInstance = *entityInstance.assetInstance;
908 nestedInstance.stats.current = { 0, 0, 0 };
909 for (size_t i = 0; i < nestedInstance.scene.roots.size(); ++i) {
910 calculateCost(context, nestedInstance, nestedInstance.scene[nestedInstance.scene.roots[i]]);
911 }
912 instance.stats.current += nestedInstance.stats.current;
913 } else if (entityInstance.isModel() && entityInstance.isSpawned()) {
914 auto & modelData = instance.scene.getModelData(entityInstance);
915 instance.stats.current += modelData.cost;
916 } else {
917
918 }
919 }
920#endif
921
922#if ENABLE_COST_TRACKING
923 void estimateCost(Context* context, AssetInstanceData & instance, EntityInstanceData & entityInstance)
924 {
925 if (entityInstance.hasError()) return;
926
927 if (entityInstance.isLodGroup() && entityInstance.isVisible()) {
928 auto & lodData = instance.scene.getLodData(entityInstance);
929 uint32_t childIndex = lodData.lods[entityInstance.lod.target];
930
931 while (childIndex != ::NoValue) {
932 estimateCost(context, instance, instance.scene[childIndex]);
933
934 childIndex = instance.scene[childIndex].nextLodSibling;
935 }
936 } else if (entityInstance.isAsset() && entityInstance.isSpawned()) {
937 auto & nestedInstance = *entityInstance.assetInstance;
938 nestedInstance.stats.projected = { 0, 0, 0 };
939 for (size_t i = 0; i < nestedInstance.scene.roots.size(); ++i) {
940 estimateCost(context, nestedInstance, nestedInstance.scene[nestedInstance.scene.roots[i]]);
941 }
942 instance.stats.projected += nestedInstance.stats.projected;
943 } else if (entityInstance.isModel()) {
944 //auto & modelData = instance.scene.getModelData(entityInstance);
945 //instance.stats.projected.memory += modelData.byteSize;
946 instance.stats.projected.drawCalls += 1;
947 } else {
948 const uint64_t bogusEstimate = 10 * 1024;
949 instance.stats.projected.memory += bogusEstimate;
950 }
951 }
952#endif
953
954 void updateAssetDesiredLevels(Context* context, AssetInstanceData& assetInstance, uint32_t lodDepth, uint32_t maxIterations);
955
956 void updateEntityDesiredLevels(Context* context, AssetInstanceData & instance, AssetLodReference & lodReference, EntityInstanceData & entityInstance, uint32_t lodDepth, const float tolerance)
957 {
958 if (entityInstance.isLodGroup()) {
959 updateLodGroupDesiredLevel(instance, lodReference, entityInstance, lodDepth, tolerance);
960
961 if (entityInstance.isVisible() && entityInstance.isAtDesiredLod()) {
962 auto & lodData = instance.scene.getLodData(entityInstance);
963 uint32_t childIndex = lodData.lods[entityInstance.lod.target];
964
965 // Entities of a given lod-group, we increase lod depth when we recurse into that
966 while (childIndex != ::NoValue) {
967
968 updateEntityDesiredLevels(context, instance, lodReference, instance.scene[childIndex], lodDepth + 1 + entityInstance.lod.target, tolerance);
969
970 childIndex = instance.scene[childIndex].nextLodSibling;
971 }
972 }
973 } else if (entityInstance.isSpawned()) {
974 auto & definition = instance.asset->definition;
975 auto & entityDefinition = definition.scene[entityInstance.index];
976
977 if (entityInstance.isAsset()) {
978 entityInstance.assetInstance->tolerance = instance.tolerance;
979 entityInstance.assetInstance->forceTolerance = instance.forceTolerance;
980#if ENABLE_COST_TRACKING
981 entityInstance.assetInstance->stats.toleranceScale = instance.stats.toleranceScale;
982#endif
983 entityInstance.assetInstance->visible = instance.visible;
984
985 entityInstance.assetInstance->stats.frustumCullIn = 0;
986 entityInstance.assetInstance->stats.frustumCullOut = 0;
987 entityInstance.assetInstance->stats.bboxCullOut = 0;
988 updateAssetDesiredLevels(context, *entityInstance.assetInstance, lodDepth, 1);
989 instance.stats.frustumCullIn += entityInstance.assetInstance->stats.frustumCullIn;
990 instance.stats.frustumCullOut += entityInstance.assetInstance->stats.frustumCullOut;
991 instance.stats.bboxCullOut += entityInstance.assetInstance->stats.bboxCullOut;
992 } else if (entityInstance.hasChildren()) {
993 uint32_t childIndex = entityDefinition.firstChild;
994
995 while (childIndex != ::NoValue) {
996 updateEntityDesiredLevels(context, instance, lodReference, instance.scene[childIndex], lodDepth, tolerance);
997
998 childIndex = definition.scene[childIndex].nextSibling;
999 }
1000 }
1001 }
1002 }
1003
1004#if ENABLE_COST_TRACKING
1005 bool updateToleranceScale(AssetInstanceStats & stats)
1006 {
1007 const float currentToleranceScale = stats.toleranceScale;
1008 const float frameToleranceMax = glm::min(currentToleranceScale + stats.toleranceScaleIncrement, stats.toleranceScaleMax);
1009 const float frameToleranceMin = glm::max(currentToleranceScale - stats.toleranceScaleIncrement, stats.toleranceScaleMin);
1010
1011 if (stats.projected.memory > stats.max.memory) {
1012 const float newToleranceScale = (0.9f * stats.max.memory) / glm::max((decltype(stats.current.memory))1, stats.current.memory) * stats.toleranceScale;
1013 stats.toleranceScale = glm::clamp(newToleranceScale, frameToleranceMin, frameToleranceMax);
1014 return true;
1015 } else if (stats.projected.memory < 0.8f * stats.max.memory) {
1016 if (stats.current.memory != 0) {
1017 const float targetToleranceScale = stats.max.memory / glm::max((decltype(stats.current.memory))1, stats.current.memory) * currentToleranceScale;
1018 stats.toleranceScale = glm::clamp(targetToleranceScale, frameToleranceMin, frameToleranceMax);
1019 } else {
1020 stats.toleranceScale = frameToleranceMax;
1021 }
1022
1023 return true;
1024 }
1025
1026 return false;
1027 }
1028#endif
1029
1030 void updateAssetDesiredLevels(Context* context, AssetInstanceData & assetInstance, uint32_t lodDepth, uint32_t maxIterations)
1031 {
1032 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::updateAssetDesiredLevels");
1033 auto lodTime = assetInstance.stats.frame.startLodCalculation();
1034
1035 AssetLodReference lodReference = {};
1036 getLodReference(context, assetInstance, lodReference, assetInstance.container);
1037
1038 auto & scene = assetInstance.scene;
1039
1040 float tolerance = assetInstance.tolerance;
1041#if ENABLE_COST_TRACKING
1042 uint64_t prevIteration = assetInstance.stats.projected.memory;
1043#endif
1044#if ENABLE_COST_TRACKING
1045 for (uint32_t it = 0; it < maxIterations; it++) {
1046 float clampedTolerance = glm::max(1e-5f, (1.f / assetInstance.stats.toleranceScale) * tolerance);
1047#else
1048 if (maxIterations > 0) {
1049 float clampedTolerance = tolerance;
1050#endif
1051 if (assetInstance.forceTolerance) clampedTolerance = assetInstance.tolerance;
1052
1053 for (auto & root : scene.roots) {
1054 updateEntityDesiredLevels(context, assetInstance, lodReference, scene[root], lodDepth, clampedTolerance);
1055 }
1056
1057 assetInstance.stats.frustumCullIn += lodReference.frustumCullIn;
1058 assetInstance.stats.frustumCullOut += lodReference.frustumCullOut;
1059 assetInstance.stats.bboxCullOut += lodReference.bboxCullOut;
1060
1061#if ENABLE_COST_TRACKING
1062 if (assetInstance.parentInstance) {
1063 break;
1064 }
1065
1066 {
1067 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::estimateCost");
1068
1069 assetInstance.stats.projected = { 0, 0, 0 };
1070 for (size_t i = 0; i < scene.roots.size(); ++i) {
1071 estimateCost(context, assetInstance, scene[scene.roots[i]]);
1072 }
1073 }
1074
1075 if (assetInstance.stats.projected.memory == prevIteration) {
1076 break;
1077 } else {
1078 prevIteration = assetInstance.stats.projected.memory;
1079 }
1080
1081 if (!updateToleranceScale(assetInstance.stats)) {
1082 break;
1083 }
1084#endif
1085 }
1086 }
1087
1088 void clearEntity(Context * context, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance)
1089 {
1090 if (entityInstance.entity) {
1091 assetInstance.destroyed(entityInstance.entity->getId());
1092 }
1093
1094 entityInstance.entity = {};
1095 entityInstance.setSpawned(false);
1096 entityInstance.setReady(false);
1097
1098 if (entityInstance.isLodGroup()) {
1099 entityInstance.lod.current = ::NoValue;
1100 }
1101
1102 if (entityInstance.isStaging()) {
1103 entityInstance.setStaging(false);
1104 }
1105
1106 if (entityInstance.hasChildren()) {
1107 auto & definition = assetInstance.asset->definition;
1108 auto & entityDefinition = definition.scene[entityInstance.index];
1109
1110 for (uint32_t i = 0; i < entityDefinition.numChildren; ++i) {
1111 uint32_t childIndex = definition.scene.getChild(entityDefinition.firstChild, i);
1112
1113 clearEntity(context, assetInstance, assetInstance.scene[childIndex]);
1114 }
1115 } else if (entityInstance.isAsset() && entityInstance.assetInstance) {
1116 for (auto & instance : entityInstance.assetInstance->scene.entities) {
1117 clearEntity(context, *entityInstance.assetInstance, instance);
1118 }
1119
1120 entityInstance.assetInstance->destroy();
1121 entityInstance.assetInstance = nullptr;
1122 } else if (entityInstance.isModel()) {
1123 auto & modelData = assetInstance.scene.getModelData(entityInstance);
1124
1125 //if (modelData.model && spawned) {
1126 // //TODO: Send destroyed message for all parts.
1127 // assetInstance.stats.numEntities -= modelData.part == ::NoValue ? (uint32_t)modelData.model->parts.size() : 1;
1128 //}
1129
1130 if (modelData.requested) {
1131 auto & modelFile = assetInstance.scene.modelFiles[modelData.fileIndex];
1132 modelFile.unref();
1133 modelData.requested = false;
1134 }
1135 }
1136 }
1137
1138 bool checkEntity(Context * context, AssetLodReference& lodReference, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance)
1139 {
1140 if (entityInstance.hasError()) return true;
1141
1142 if (cullModelsValue && entityInstance.isModel()) {
1143 AssetDefinition& assetDef = assetInstance.asset->definition;
1144 SceneEntityDefinition& entityDef = assetDef.scene[entityInstance.index];
1145 const Cogs::Geometry::BoundingBox& bbox = assetInstance.scene.boxes[entityDef.index];
1146 if (!isEmpty(bbox) && isOutside(lodReference, bbox)) {
1147 ModelInstanceData& modelData = assetInstance.scene.getModelData(entityInstance);
1148 if (entityInstance.isSpawned() || entityInstance.isReady() || modelData.requested) {
1149 clearEntity(context, assetInstance, entityInstance);
1150 }
1151 return true;
1152 }
1153 }
1154
1155 if (entityInstance.isReady()) return true;
1156
1157 if (entityInstance.isModel()) {
1158 auto& modelData = assetInstance.scene.getModelData(entityInstance);
1159 auto& modelFile = assetInstance.scene.modelFiles[modelData.fileIndex];
1160
1161 constexpr uint32_t kPriorityPerLevel = 1000;
1162 constexpr uint32_t kMaxInstancePriority = 100000;
1163
1164 if (!modelData.requested) {
1165 if (!modelFile.requested) {
1166 AssetModelRequest* request = assetInstance.systemData->requestPool.create();
1167 request->assetInstance = &assetInstance;
1168 request->modelFileIndex = modelData.fileIndex;
1169 request->path = modelFile.path;
1170 request->priority += (uint32_t)((float)kMaxInstancePriority * assetInstance.priority);
1171 modelFile.request = request;
1172 assetInstance.systemData->pendingRequests.emplace_back(request);
1173 ++assetInstance.stats.frame.modelsRequested;
1174 ++assetInstance.stats.numRequests;
1175
1176 modelFile.requested = true;
1177 }
1178
1179 if (modelFile.request) {
1180 modelFile.request->priority += modelData.depth * kPriorityPerLevel;
1181 }
1182
1183 modelFile.ref();
1184
1185 modelData.requested = true;
1186 }
1187
1188 if (modelFile.ready) {
1189 entityInstance.setReady(true);
1190 return true;
1191 }
1192
1193 if (!modelFile.model) return false;
1194
1195 if (!modelFile.model->isLoaded()) return false;
1196
1197 for (auto & mesh : modelFile.model->meshes) {
1198 if (!mesh->isInitialized() && mesh->getCount() == 0) continue;
1199 if (!mesh->hasAttachedResource()) return false;
1200 }
1201
1202 if (!assetInstance.useOverrideMaterial() && !assetInstance.useCloneMaterial()) {
1203 for (auto & material : modelFile.model->materials) {
1204 if (!material->isActive()) return false;
1205 }
1206 }
1207
1208 modelFile.setReady();
1209
1210 entityInstance.setReady(true);
1211
1212 // Trigger a new frame as this model ready might open up for new lod-levels.
1213 context->engine->setDirty();
1214 return true;
1215 } else if (entityInstance.isAsset()) {
1216 auto & definition = assetInstance.asset->definition;
1217 auto & entityDefinition = definition.scene[entityInstance.index];
1218
1219 if (!entityInstance.assetInstance) {
1220 entityInstance.assetInstance = assetInstance.systemData->instanceData.create();
1221 entityInstance.assetInstance->link(&assetInstance);
1222 entityInstance.assetInstance->container = parentInstance->entityPtr;
1223 entityInstance.assetInstance->objectId = assetInstance.objectId;
1224 entityInstance.assetInstance->renderLayers = assetInstance.renderLayers;
1225 entityInstance.assetInstance->clipShapeComponent = assetInstance.clipShapeComponent;
1226 }
1227
1228 if (!entityInstance.assetInstance->initialized) {
1229
1230 std::string path;
1231 const PropertyInfo& pathItem = definition.scene.properties.getPropertyByIndex(entityDefinition.asset.index);
1232 if (pathItem.type == PropertyType::UnsignedInteger) {
1233 path = createAssetResourcePathFromIndex(pathItem.uintValue, AssetResourceType::Asset);
1234 }
1235 else if (pathItem.type == PropertyType::String) {
1236 path = definition.scene.properties.getString(entityDefinition.asset.index).to_string();
1237 }
1238
1239 if (assetInstance.useRelativePaths() && IO::isRelative(path)) {
1240 path = IO::combine(assetInstance.directoryPath, std::move(path));
1241 }
1242
1243 entityInstance.assetInstance->asset = context->assetManager->loadAsset(path, NoResourceId, AssetLoadFlags::None);
1244
1245 entityInstance.assetInstance->initialized = true;
1246
1247 if (assetInstance.onDemand) {
1248 entityInstance.assetInstance->onDemand = true;
1249 }
1250
1251 if (assetInstance.useOverrideMaterial()) {
1252 entityInstance.assetInstance->overrideMaterial = true;
1253 entityInstance.assetInstance->material = assetInstance.material;
1254 }
1255 if (assetInstance.useCloneMaterial()) {
1256 entityInstance.assetInstance->cloneMaterial = true;
1257 entityInstance.assetInstance->material = assetInstance.material;
1258 }
1259 }
1260
1261 if (!entityInstance.assetInstance->asset->isLoaded()) {
1262 return false;
1263 }
1264
1265 if (!entityInstance.assetInstance->scene.initialized) {
1266 auto flags = (AssetFlags)entityDefinition.asset.flags;
1267 entityInstance.assetInstance->onDemand = (flags & AssetFlags::InstantiateOnDemand) != 0;
1268 entityInstance.assetInstance->relativePaths = (flags & AssetFlags::RelativePaths) != 0;
1269
1270 initializeAssetInstance(*entityInstance.assetInstance);
1271 }
1272
1273 bool ready = true;
1274 for (const uint32_t rootIx : entityInstance.assetInstance->scene.roots) {
1275 ready &= checkEntity(context, lodReference, *entityInstance.assetInstance, entityInstance.assetInstance->scene[rootIx], parentInstance);
1276 }
1277 entityInstance.setReady(ready);
1278
1279 // Trigger a new frame as this model ready might open up for new lod-levels.
1280 context->engine->setDirty();
1281 return ready;
1282
1283 } else if (entityInstance.isLodGroup()) {
1284 auto & definition = assetInstance.asset->definition;
1285 auto & lodData = assetInstance.scene.getLodData(entityInstance);
1286
1287 entityInstance.lod.target = uint32_t(lodData.lods.size()) - 1;
1288 uint32_t childIndex = entityInstance.lod.current != ::NoValue ? entityInstance.lod.current : lodData.lods.back();
1289
1290 bool retval = true;
1291 while (childIndex != ::NoValue) {
1292 retval &= checkEntity(context, lodReference, assetInstance, assetInstance.scene[childIndex], parentInstance);
1293 childIndex = definition.scene[childIndex].nextLodSibling;
1294 }
1295 return retval;
1296 } else if (entityInstance.hasChildren()) {
1297 auto & definition = assetInstance.asset->definition;
1298 auto & entityDefinition = definition.scene[entityInstance.index];
1299
1300 uint32_t childIndex = entityDefinition.firstChild;
1301
1302 while (childIndex != ::NoValue) {
1303 if (!checkEntity(context, lodReference, assetInstance, assetInstance.scene[childIndex], parentInstance)) {
1304 return false;
1305 }
1306
1307 childIndex = definition.scene[childIndex].nextSibling;
1308 }
1309 }
1310
1311 return true;
1312 }
1313
1314 bool spawnEntity(Context * context, AssetLodReference& lodReference, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance, EntityParentMode parentMode);
1315
1316 bool spawnLodGroup(Context * context, AssetLodReference& lodReference, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance, EntityParentMode /*parentMode*/)
1317 {
1318 assert(entityInstance.isLodGroup() && "Invalid LoD group instance.");
1319
1320 if (entityInstance.isAtDesiredLod()) return false;
1321
1322 auto & scene = assetInstance.scene;
1323 auto & lodData = scene.getLodData(entityInstance);
1324
1325 if (lodData.previousTarget != ::NoValue && lodData.previousTarget != entityInstance.lod.target && lodData.previousTarget != entityInstance.lod.current) {
1326 uint32_t currentLodChild = lodData.lods[lodData.previousTarget];
1327
1328 while (currentLodChild != ::NoValue) {
1329 clearEntity(context, assetInstance, scene[currentLodChild]);
1330 currentLodChild = scene[currentLodChild].nextLodSibling;
1331 }
1332
1333 lodData.previousTarget = ::NoValue;
1334 }
1335
1336 if (!entityInstance.isVisible()) {
1337 clearEntity(context, assetInstance, entityInstance);
1338 return {};
1339 }
1340
1341 if (entityInstance.lod.target != ::NoValue) {
1342 uint32_t currentLodChild = lodData.lods[entityInstance.lod.target];
1343
1344 while (currentLodChild != ::NoValue) {
1345 scene[currentLodChild].setStaging(false);
1346 currentLodChild = scene[currentLodChild].nextLodSibling;
1347 }
1348 }
1349
1350 const uint32_t firstChildIndex = lodData.lods[entityInstance.lod.target];
1351
1352 bool ready = true;
1353 uint32_t childIndex = firstChildIndex;
1354 while (childIndex != ::NoValue) {
1355 auto & childInstance = scene[childIndex];
1356
1357 childInstance.setStaging(true);
1358
1359 // Check if the target LOD level is ready to be switched to.
1360 ready &= checkEntity(context, lodReference, assetInstance, childInstance, parentInstance);
1361
1362 childIndex = childInstance.nextLodSibling;
1363 }
1364
1365 if (!ready) return false;
1366
1367 if (entityInstance.lod.current != ::NoValue) {
1368 uint32_t currentLodChild = lodData.lods[entityInstance.lod.current];
1369
1370 while (currentLodChild != ::NoValue) {
1371 clearEntity(context, assetInstance, scene[currentLodChild]);
1372 currentLodChild = scene[currentLodChild].nextLodSibling;
1373 }
1374 }
1375
1376 auto currentParent = parentInstance;
1377
1378 childIndex = firstChildIndex;
1379 while (childIndex != ::NoValue) {
1380 spawnEntity(context, lodReference, assetInstance, scene[childIndex], currentParent, EntityParentMode::TransformOnly);
1381
1382 childIndex = scene[childIndex].nextLodSibling;
1383 }
1384
1385 entityInstance.lod.current = entityInstance.lod.target;
1386
1387 return true;
1388 }
1389
1390 bool spawnAsset(Context * context, AssetLodReference& lodReference, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance)
1391 {
1392 if (entityInstance.isSpawned()) return false;
1393 if (!entityInstance.isReady()) return false;
1394
1395 auto & assetInstance = *entityInstance.assetInstance;
1396
1397 EntityInstanceData root;
1398 root.entityPtr = parentInstance->entityPtr;
1399
1400 for (auto & subroot : assetInstance.scene.roots) {
1401 spawnEntity(context, lodReference, assetInstance, assetInstance.scene[subroot], parentInstance, EntityParentMode::TransformOnly);
1402 }
1403
1404 entityInstance.setSpawned(true);
1405 entityInstance.setStaging(false);
1406
1407 return true;
1408 }
1409
1410 bool spawnModel(Context * context, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance, EntityParentMode parentMode)
1411 {
1412 if (entityInstance.isSpawned()) return false;
1413 if (!entityInstance.isReady()) return false;
1414
1415 if (entityInstance.hasError()) {
1416 entityInstance.setSpawned(true);
1417 return true;
1418 }
1419
1420 auto & modelData = assetInstance.scene.getModelData(entityInstance);
1421 assert(modelData.index == entityInstance.index && "Model data attached to invalid entity instance.");
1422 auto & modelFile = assetInstance.scene.modelFiles[modelData.fileIndex];
1423
1424 auto model = modelFile.model.resolve();
1425
1426 if (!model->isLoaded()) return false;
1427 if (model->meshes.empty()) return false;
1428
1429 auto & scene = assetInstance.asset->definition.scene;
1430 auto & entityDefinition = scene[entityInstance.index];
1431
1432 EntityCreationContext entityContext{};
1433 entityContext.context = context;
1434 entityContext.scene = &assetInstance.asset->definition.scene;
1435 entityContext.parent = parentInstance->entityPtr;
1436 entityContext.parentMode = parentMode;
1437 entityContext.skipModel = true;
1438
1439 entityInstance.entity = createEntity(entityContext, entityDefinition);
1440
1441 assetInstance.spawned(entityInstance.entity->getId());
1442
1443 auto parentTransform = entityInstance.entity->getComponentHandle<TransformComponent>();
1444 auto parentSceneComponent = entityInstance.entity->getComponentHandle<SceneComponent>();
1445
1446 size_t begin = modelData.part != ::NoValue ? modelData.part : 0;
1447 size_t end = modelData.part != ::NoValue ? modelData.part + 1 : model->parts.size();
1448 assert(end > begin && "Invalid part range");
1449 size_t count = end - begin;
1450 assert(count && "Invalid part range.");
1451
1452 std::vector<EntityPtr> entities(count);
1453 context->store->createEntities(count, entities);
1454
1455 std::vector<ComponentModel::ComponentHandle> transformComponents(count);
1456 std::vector<ComponentModel::ComponentHandle> sceneComponents(count);
1457
1458 for (size_t i = 0; i < count; ++i) {
1459 auto & part = model->parts[begin + i];
1460 const EntityPtr& entity = entities[i];
1461
1462 auto sceneComponent = context->sceneSystem->createComponent();
1463 sceneComponents[i] = sceneComponent;
1464 entity->addComponent(sceneComponent);
1465
1466 auto tcHandle = context->transformSystem->createComponent();
1467 transformComponents[i] = tcHandle;
1468 entity->addComponent(tcHandle);
1469 auto transformComponent = tcHandle.resolveComponent<TransformComponent>();
1470
1471 context->transformSystem->setLocalTransform(transformComponent, model->getPartTransform(part));
1472
1473 if (part.parentIndex == ::NoValue) {
1474 transformComponent->parent = parentTransform;
1475 parentSceneComponent.resolveComponent<SceneComponent>()->children.emplace_back(entity);
1476 } else {
1477 if (part.parentIndex < begin || end <= part.parentIndex) {
1478 LOG_ERROR(logger, "Model part parent index %u is outside of requested model part range [%zu, %zu) (%s)", part.parentIndex, begin, end, modelFile.path.c_str());
1479 }
1480 else {
1481 const ComponentHandle& childParentSceneComponent = sceneComponents[part.parentIndex - begin];
1482 childParentSceneComponent.resolveComponent<SceneComponent>()->children.emplace_back(entity);
1483 transformComponent->parent = transformComponents[part.parentIndex - begin];
1484 }
1485 }
1486
1487 transformComponent->setChanged();
1488
1489 if (part.meshIndex != NoIndex) {
1490 auto mcHandle = context->meshSystem->createComponent();
1491 entity->addComponent(mcHandle);
1492
1493 auto meshComponent = mcHandle.resolveComponent<MeshComponent>();
1494 meshComponent->meshHandle = model->meshes[part.meshIndex];
1495 meshComponent->setChanged();
1496
1497 auto mrcHandle = context->renderSystem->createComponent();
1498 entity->addComponent(mrcHandle);
1499
1500 auto renderComponent = mrcHandle.resolveComponent<MeshRenderComponent>();
1501 if (part.boundsIndex != NoIndex) {
1502 context->renderSystem->setLocalBounds(renderComponent, model->bounds[part.boundsIndex]);
1503 }
1504 renderComponent->startIndex = part.startIndex;
1505 renderComponent->vertexCount = part.vertexCount;
1506 renderComponent->objectId = assetInstance.objectId;
1507 renderComponent->layer = assetInstance.renderLayers;
1508 renderComponent->clipShapeComponent = assetInstance.clipShapeComponent;
1509 renderComponent->customRayPickHandling = assetInstance.customRayPickHandling;
1510
1511 MaterialInstanceHandle partMaterial = part.materialIndex < model->materials.size()
1512 ? model->materials[part.materialIndex]
1514 if (assetInstance.useOverrideMaterial()) {
1515 renderComponent->material = assetInstance.material;
1516 }
1517 else if(assetInstance.useCloneMaterial()){
1518 MaterialInstanceHandle master = assetInstance.material;
1519 renderComponent->material = context->materialInstanceManager->createMaterialInstance(context->materialManager->generateHandle(master->material));
1520 renderComponent->material->clone(master.resolve());
1521 // Copy over settings from partMaterial
1522 if(partMaterial){
1523 renderComponent->material->setPermutation(partMaterial->getPermutation());
1524 renderComponent->material->instanceFlags = partMaterial->instanceFlags;
1525 renderComponent->material->options = partMaterial->options;
1526 renderComponent->material->cloneMatchingProperties(partMaterial.resolve());
1527 renderComponent->material->cloneMatchingVariants(partMaterial.resolve());
1528 }
1529 }
1530 else if(HandleIsValid(partMaterial)){
1531 renderComponent->material = partMaterial;
1532 }
1533 else {
1534 MaterialHandle master = context->materialManager->getDefaultMaterial();
1535 renderComponent->material = context->materialInstanceManager->createMaterialInstance(master);
1536 }
1537 renderComponent->setChanged();
1538 }
1539
1540 assetInstance.spawned(entity->getId());
1541 }
1542
1543 calculateModelCost(context, modelData, model);
1544
1545 entityInstance.setSpawned(true);
1546 entityInstance.setStaging(false);
1547
1548 return true;
1549 }
1550
1551 bool spawnEntity(Context * context, AssetLodReference& lodReference, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance, EntityParentMode parentMode)
1552 {
1553 auto & definition = assetInstance.asset->definition;
1554 auto & scene = assetInstance.scene;
1555 auto & entityDefinition = definition.scene[entityInstance.index];
1556
1557 if (entityDefinition.isLodGroup()) {
1558 return spawnLodGroup(context, lodReference, assetInstance, entityInstance, parentInstance, parentMode);
1559 } else if (entityDefinition.isAsset()) {
1560 return spawnAsset(context, lodReference, entityInstance, parentInstance);
1561 } else if (entityDefinition.isModel()) {
1562 return spawnModel(context, assetInstance, entityInstance, parentInstance, parentMode);
1563 } else {
1564 if (entityDefinition.isEmpty()) {
1565 entityInstance.setSpawned(true);
1566 return true;
1567 }
1568
1569 EntityCreationContext entityContext{};
1570 entityContext.context = context;
1571 entityContext.scene = &definition.scene;
1572 entityContext.parent = parentInstance->entityPtr;
1573 entityContext.parentMode = parentMode;
1574
1575 entityInstance.entity = createEntity(entityContext, entityDefinition);
1576 entityInstance.entityPtr = entityInstance.entity.get();
1577 entityInstance.setSpawned(true);
1578 entityInstance.setStaging(false);
1579
1580 assetInstance.spawned(entityInstance.entity->getId());
1581
1582 auto rHandle = entityInstance.entity->getComponentHandle<MeshRenderComponent>();
1583 if(rHandle){
1584 auto renderComponent = rHandle.resolveComponent<MeshRenderComponent>();
1585 if(renderComponent){
1586 renderComponent->material = assetInstance.material;
1587 }
1588 }
1589
1590 if (entityDefinition.numChildren) {
1591 uint32_t childIndex = entityDefinition.firstChild;
1592
1593 while (childIndex != ::NoValue) {
1594 checkEntity(context, lodReference, assetInstance, scene[childIndex], parentInstance);
1595
1596 spawnEntity(context, lodReference, assetInstance, scene[childIndex], &entityInstance, EntityParentMode::Default);
1597
1598 childIndex = definition.scene[childIndex].nextSibling;
1599 }
1600 }
1601
1602 return true;
1603 }
1604 }
1605
1606 void updateEntity(Context * context, AssetLodReference& lodReference, AssetInstanceData & assetInstance, EntityInstanceData & entityInstance, EntityInstanceData * parentInstance)
1607 {
1608 auto & scene = assetInstance.scene;
1609
1610 if (entityInstance.isLodGroup()) {
1611 if (!entityInstance.isAtDesiredLod()) {
1612 spawnEntity(context, lodReference, assetInstance, entityInstance, parentInstance, EntityParentMode::TransformOnly);
1613 } else if (entityInstance.isVisible()) {
1614 auto & lodData = scene.getLodData(entityInstance);
1615 uint32_t childIndex = lodData.lods[entityInstance.lod.target];
1616
1617 while (childIndex != ::NoValue) {
1618 updateEntity(context, lodReference, assetInstance, scene[childIndex], parentInstance);
1619
1620 childIndex = scene[childIndex].nextLodSibling;
1621 }
1622 }
1623 } else if (entityInstance.isSpawned()) {
1624 if (entityInstance.isAsset()) {
1625 entityInstance.assetInstance->priority = assetInstance.priority;
1626 for (auto & root : entityInstance.assetInstance->scene.roots) {
1627 updateEntity(context, lodReference, *entityInstance.assetInstance, entityInstance.assetInstance->scene[root], parentInstance);
1628 }
1629 } else if (entityInstance.hasChildren()) {
1630 auto & definition = assetInstance.asset->definition;
1631 auto & entityDefinition = definition.scene[entityInstance.index];
1632
1633 for (uint32_t i = 0; i < entityDefinition.numChildren; ++i) {
1634 auto & childDefinition = definition.scene[definition.scene.getChild(entityDefinition.firstChild, i)];
1635
1636 updateEntity(context, lodReference, assetInstance, scene[childDefinition.index], &entityInstance);
1637 }
1638 }
1639 } else if (entityInstance.isStaging()) {
1640 if (entityInstance.isAsset() || entityInstance.isModel()) {
1641 if (checkEntity(context, lodReference, assetInstance, entityInstance, parentInstance)) {
1642 spawnEntity(context, lodReference, assetInstance, entityInstance, parentInstance, EntityParentMode::TransformOnly);
1643 }
1644 } else {
1645 spawnEntity(context, lodReference, assetInstance, entityInstance, parentInstance, EntityParentMode::TransformOnly);
1646 }
1647 } else {
1648 if (!entityInstance.isSpawned()) {
1649 if (checkEntity(context, lodReference, assetInstance, entityInstance, parentInstance)) {
1650 spawnEntity(context, lodReference, assetInstance, entityInstance, parentInstance, EntityParentMode::TransformOnly);
1651 }
1652 }
1653 }
1654 }
1655
1656 void dispatchMessages(Context * context, AssetInstanceData & assetInstance)
1657 {
1658 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::dispatch");
1659 auto dispatchTime =assetInstance.stats.frame.startDispatch();
1660
1661 auto systemData = assetInstance.systemData;
1662
1663 //TODO: Check for subscribers before dispatch.
1664 //TODO: Cache dispatch table per entity (?)
1665 //TODO: Batch entities in single created/destroyed message
1666
1667 for (EntityId spawned : assetInstance.spawnedEntities) {
1668 context->dynamicComponentSystem->sendMessage(assetInstance.container, systemData->entityCreatedId, (void *)spawned);
1669 }
1670
1671 for (EntityId destroyed : assetInstance.destroyedEntities) {
1672 context->dynamicComponentSystem->sendMessage(assetInstance.container, systemData->entityDestroyedId, (void *)destroyed);
1673 }
1674
1675 assetInstance.spawnedEntities.clear();
1676 assetInstance.destroyedEntities.clear();
1677 }
1678
1679 void processInFlightRequests(Context* context, AssetSystemData * systemData)
1680 {
1681 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::processInFlightRequests");
1682
1683 for (auto request : systemData->inFlightRequests) {
1684 if (request->canceled) {
1685 context->modelManager->cancelModelLoad(request->model);
1686 systemData->requestPool.destroy(request);
1687 continue;
1688 }
1689
1690 if (request->model && request->model->isLoaded()) {
1691 auto assetInstance = request->assetInstance;
1692
1693 auto & modelFile = assetInstance->scene.modelFiles[request->modelFileIndex];
1694 modelFile.model = request->model;
1695 modelFile.request = nullptr;
1696
1697 --assetInstance->stats.numRequests;
1698
1699 systemData->requestPool.destroy(request);
1700 } else {
1701 systemData->stillInFlight.emplace_back(request);
1702 }
1703 }
1704
1705 std::swap(systemData->stillInFlight, systemData->inFlightRequests);
1706 systemData->stillInFlight.clear();
1707 }
1708
1709 size_t processPendingRequests(Context* context,
1710 AssetSystemData* systemData,
1711 ModelManager* modelManager,
1712 AssetSystemVector<AssetModelRequest*>& pendingRequests,
1713 AssetSystemVector<AssetModelRequest*>& inFlightRequests,
1714 const size_t maxInFlight,
1715 const size_t maxProcessed)
1716 {
1717 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::processPendingRequests");
1718
1719 size_t processed = 0;
1720
1721 if (!pendingRequests.empty()) {
1722 std::sort(pendingRequests.begin(), pendingRequests.end(), [](auto a, auto b) -> bool
1723 {
1724 return a->priority < b->priority;
1725 });
1726 }
1727
1728 while (!pendingRequests.empty() && processed < maxProcessed && inFlightRequests.size() < maxInFlight) {
1729 auto request = pendingRequests.back();
1730 pendingRequests.pop_back();
1731
1732 if (systemData && request->canceled) {
1733 LOG_TRACE(logger, "Discarding cancelled pending request");
1734 systemData->requestPool.destroy(request);
1735 continue;
1736 }
1737
1738 auto assetInstance = request->assetInstance;
1739
1741 if (assetInstance && assetInstance->useOverrideMaterial()) {
1743 }
1744 if (assetInstance && assetInstance->useCloneMaterial()) {
1746 }
1747
1748 request->model = modelManager ? modelManager->loadModel(request->path, NoResourceId, flags) : ModelHandle{};
1749
1750 inFlightRequests.emplace_back(request);
1751
1752 ++processed;
1753 }
1754
1755 while (systemData && maxInFlight < inFlightRequests.size()) {
1756 LOG_TRACE(logger, "Cancelling in-flight request and moving it to pending as limits are violated.");
1757
1758 AssetModelRequest* request = inFlightRequests.back();
1759 inFlightRequests.pop_back();
1760
1761 if (request->model) {
1762 context->modelManager->cancelModelLoad(request->model);
1763 request->model = ModelHandle{};
1764 }
1765
1766 pendingRequests.emplace_back(request);
1767 }
1768
1769 return processed;
1770 }
1771
1772 void pullCameraMatrices(Context* context, AssetSystemData* systemData)
1773 {
1774 if (CameraComponent* mainCam = context->cameraSystem->getMainCamera(); mainCam) {
1775 if (TransformComponent* trCompLod = mainCam->getComponent<TransformComponent>(); trCompLod) {
1776
1777 if (trCompLod->hasChanged()) {
1778 context->transformSystem->updateTransformData(*trCompLod);
1779 }
1780 const CameraData& camData = context->cameraSystem->getData(mainCam);
1781
1782 systemData->worldFromView = context->transformSystem->getLocalToWorld(trCompLod);
1783 systemData->worldFromClip = systemData->worldFromView * glm::inverse(camData.rawProjectionMatrix);
1784
1785 systemData->viewFromWorld = glm::inverse(systemData->worldFromView);
1786 systemData->clipFromWorld = camData.rawProjectionMatrix * systemData->viewFromWorld;
1787
1788 systemData->minDistance = mainCam->enableClippingPlaneAdjustment ? mainCam->nearPlaneLimit : mainCam->nearDistance;
1789 systemData->maxDistance = context->variables->get(maxDistanceVariableName, kDefaultMaxDistance);
1790
1791 if (int maxLodDepth = context->variables->get(maxLodDepthVariableName, kDefaultMaxLodDepth); 0 <= maxLodDepth) {
1792 systemData->maxLodDepth = maxLodDepth;
1793 }
1794 else {
1795 systemData->maxLodDepth = ~0u;
1796 }
1797 return;
1798 }
1799 }
1800 systemData->worldFromView = glm::mat4(1.f);
1801 systemData->worldFromClip = glm::mat4(1.f);
1802 systemData->viewFromWorld = glm::mat4(1.f);
1803 systemData->clipFromWorld = glm::mat4(1.f);
1804 }
1805
1806 void updateRenderPropertiesRecurse(const AssetInstanceData& instanceData, Entity* entity) {
1807
1808 if (RenderComponent* renderComp = entity->getComponent<RenderComponent>(); renderComp) {
1809 renderComp->layer = instanceData.renderLayers;
1810 renderComp->objectId = instanceData.objectId;
1811 renderComp->clipShapeComponent = instanceData.clipShapeComponent;
1812 renderComp->setChanged();
1813 }
1814
1815 if (SceneComponent* sceneComp = entity->getComponent<SceneComponent>(); sceneComp) {
1816 for (EntityPtr& e : sceneComp->children) {
1817 updateRenderPropertiesRecurse(instanceData, e.get());
1818 }
1819 }
1820 }
1821
1822 // Recursively update the entities of an instantiated model
1823 void updateInstancedModelChildEntityRenderProperties(Context* context, AssetInstanceData& assetInstance, Entity& entity)
1824 {
1825 if (RenderComponent* renderComp = entity.getComponent<RenderComponent>(); renderComp) {
1826 renderComp->objectId = assetInstance.objectId;
1827 renderComp->layer = assetInstance.renderLayers;
1828 renderComp->clipShapeComponent = assetInstance.clipShapeComponent;
1829 renderComp->setChanged();
1830 }
1831
1832 if (SceneComponent* sceneComp = entity.getComponent<SceneComponent>(); sceneComp) {
1833 for (EntityPtr& child : sceneComp->children) {
1834 assert(child);
1835 updateInstancedModelChildEntityRenderProperties(context, assetInstance, *child.get());
1836 }
1837 }
1838 }
1839
1840 void updateAssetInstanceRenderProperties(Context* context, AssetInstanceData& assetInstance);
1841
1842 void updateEntityInstanceRenderProperties(Context* context, AssetInstanceData& assetInstance, EntityInstanceData& entityInstance)
1843 {
1844 if (entityInstance.isLodGroup()) {
1845
1846 if (entityInstance.isVisible() && entityInstance.isAtDesiredLod()) {
1847 LodInstanceData& lodData = assetInstance.scene.getLodData(entityInstance);
1848 uint32_t childIndex = lodData.lods[entityInstance.lod.target];
1849 while (childIndex != ::NoValue) {
1850 updateEntityInstanceRenderProperties(context, assetInstance, assetInstance.scene[childIndex]);
1851 childIndex = assetInstance.scene[childIndex].nextLodSibling;
1852 }
1853 }
1854
1855 }
1856
1857 else if (entityInstance.isSpawned()) {
1858
1859 const AssetDefinition& definition = assetInstance.asset->definition;
1860 const SceneEntityDefinition& entityDefinition = definition.scene[entityInstance.index];
1861
1862 if (entityInstance.isAsset()) {
1863 entityInstance.assetInstance->clipShapeComponent = assetInstance.clipShapeComponent;
1864 updateAssetInstanceRenderProperties(context, *entityInstance.assetInstance);
1865 }
1866
1867 else if (entityInstance.isModel()) {
1868 if (entityInstance.entity) {
1869 updateInstancedModelChildEntityRenderProperties(context, assetInstance, *entityInstance.entity.get());
1870 }
1871 }
1872
1873 else if (entityInstance.hasChildren()) {
1874
1875 uint32_t childIndex = entityDefinition.firstChild;
1876 while (childIndex != ::NoValue) {
1877 updateEntityInstanceRenderProperties(context, assetInstance, assetInstance.scene[childIndex]);
1878 childIndex = definition.scene[childIndex].nextSibling;
1879 }
1880
1881 }
1882 }
1883
1884 }
1885
1886 void updateAssetInstanceRenderProperties(Context* context, AssetInstanceData& assetInstance)
1887 {
1888 for (uint32_t root : assetInstance.scene.roots) {
1889 updateEntityInstanceRenderProperties(context, assetInstance, assetInstance.scene[root]);
1890 }
1891 }
1892
1893
1894
1895}
1896
1897
1898void Cogs::Core::AssetBounds::getBounds(Context* /*context*/, Cogs::Geometry::BoundingBox& bounds)
1899{
1900 for (auto& assetComponent : assetSystem->pool) {
1901 const auto bbox = assetSystem->getWorldBounds(&assetComponent, false);
1902 if (!isEmpty(bbox))
1903 bounds += bbox;
1904 }
1905}
1906
1907bool Cogs::Core::AssetBounds::getBounds(Context* context, const ComponentModel::Entity* entity, Cogs::Geometry::BoundingBox& bounds, bool ignoreVisibility) const
1908{
1909 auto assetComponent = entity->getComponent<AssetComponent>();
1910 if (!assetComponent) return false;
1911 const auto bbox = context->assetSystem->getWorldBounds(assetComponent, ignoreVisibility);
1912 if (!isEmpty(bbox)) {
1913 bounds = bbox;
1914 return true;
1915 }
1916 else
1917 return false;
1918}
1919
1920
1921Cogs::Core::AssetSystem::AssetSystem(Memory::Allocator * allocator, SizeType capacity) : ComponentSystemWithDataPools(allocator, capacity) {}
1922
1923Cogs::Core::AssetSystem::~AssetSystem() = default;
1924
1926{
1927 this->context = context;
1928
1929 systemData = std::make_unique<AssetSystemData>();
1930
1931 context->modelManager->registerLoader(new CogsModelLoader());
1932
1933 systemData->entityCreatedId = context->dynamicComponentSystem->registerMessage("entityCreated");
1934 systemData->entityDestroyedId = context->dynamicComponentSystem->registerMessage("entityDestroyed");
1935
1936 context->variables->getOrAdd(lodFreezeVariableName, kDefaultLodFreeze);
1937 context->variables->getOrAdd(cullModelsName, cullModelsValue);
1938 if (Variable* var = context->variables->get(maxModelsInFlightVariableName); var->isEmpty()) {
1939 context->variables->set(maxModelsInFlightVariableName, kDefaultMaxModelsInFlight);
1940 }
1941 if (Variable* var = context->variables->get(maxModelLoadsPerFrameVariableName); var->isEmpty()) {
1942 context->variables->set(maxModelLoadsPerFrameVariableName, kDefaultMaxModelLoadsPerFrame);
1943 }
1944 if (Variable* var = context->variables->get(maxDistanceVariableName); var->isEmpty()) {
1945 context->variables->set(maxDistanceVariableName, kDefaultMaxDistance);
1946 }
1947 if (Variable* var = context->variables->get(maxLodDepthVariableName); var->isEmpty()) {
1948 context->variables->set(maxLodDepthVariableName, kDefaultMaxLodDepth);
1949 }
1950 context->variables->getOrAdd(allowMaterialOverride, kDefaultAllowMaterialOverride);
1951
1952}
1953
1955{
1956 const float qualityScale = context->qualityService->assetSystemToleranceScale;
1957
1958 bool freeze = context->variables->get(lodFreezeVariableName, kDefaultLodFreeze);
1959
1960 if (const Variable* var = context->variables->get(cullModelsName); !var->isEmpty()) {
1961 cullModelsValue = var->getBool();
1962 }
1963
1964 pullCameraMatrices(context, systemData.get());
1965
1966 bool allowOverrideMaterial = context->variables->get(allowMaterialOverride, true);
1967
1968 // Run through all components,
1969 // - Check if asset resource has changed or has been updated.
1970 // - Populate newUpdates with detected changes.
1971 // - Populate currentOnDemandInstances with all assets that are flagged as ondemand.
1972 AssetSystemVector<AssetUpdate>& newUpdates = systemData->newUpdates; assert(newUpdates.empty());
1973 AssetSystemVector<AssetInstanceData*>& currentOnDemandInstances = systemData->currentOnDemandInstances; assert(currentOnDemandInstances.empty());
1974 for (auto & assetComponent : pool) {
1975
1976
1977
1978 AssetData& assetData = getData<AssetData>(&assetComponent);
1979
1980 // Check if the asset resource of the asset property has changed, other changes handled below.
1981 if (assetData.hasChanged(assetComponent.asset)) {
1982 assetData.setAsset(assetComponent.asset);
1983
1984 // First run, set up initial asset instance data with destructor
1985 // that frees the full chain of linked asset instance datas.
1986 if (!assetData.instanceData) {
1987 auto instanceData = systemData->instanceData.create();
1988 assetData.instanceData = std::shared_ptr<AssetInstanceData>(instanceData, [&](AssetInstanceData * d)
1989 {
1990 auto current = d->next;
1991 while (current) {
1992 auto next = current->next;
1993 systemData->removeRequests(current);
1994 systemData->instanceData.destroy(current);
1995 current = next;
1996 }
1997 systemData->removeRequests(d);
1998 systemData->instanceData.destroy(d);
1999 });
2000 }
2001
2002 // Populate head of asset instance data with data from the componnent.
2003 //
2004 // This is only triggered by change of asset resource.
2005 AssetInstanceData* assetInstance = assetData.instanceData.get();
2006 assetInstance->systemData = systemData.get();
2007 assetInstance->asset = assetData.asset;
2008 assetInstance->container = assetComponent.getContainer();
2009 assetInstance->scene.initialized = false;
2010 assetInstance->onDemand = (assetComponent.flags & AssetFlags::InstantiateOnDemand) != 0;
2011 assetInstance->relativePaths = (assetComponent.flags & AssetFlags::RelativePaths) != 0;
2012 assetInstance->overrideMaterial = allowOverrideMaterial && (assetComponent.flags & AssetFlags::OverrideMaterial) != 0;
2013 assetInstance->cloneMaterial = allowOverrideMaterial && (assetComponent.flags & AssetFlags::CloneMaterial) != 0;
2014 assetInstance->customRayPickHandling = assetData.customRayPickHandling;
2015 assetInstance->priority = assetComponent.priority;
2016
2017 if (const RenderComponent* renderComponent = assetComponent.getContainer()->getComponent<RenderComponent>(); renderComponent) {
2018 assetInstance->objectId = renderComponent->objectId;
2019 assetInstance->renderLayers = renderComponent->layer;
2020 }
2021
2022 // Create material if needed
2023 if (assetInstance->useOverrideMaterial() || assetInstance->useCloneMaterial()) {
2024 if (assetComponent.material) {
2025 assetInstance->material = assetComponent.material;
2026 } else {
2027
2028 if (!systemData->defaultMaterialHandle) {
2029 systemData->defaultMaterialHandle = context->materialInstanceManager->createMaterialInstance(context->materialManager->getDefaultMaterial());
2030 }
2031 assetInstance->material = systemData->defaultMaterialHandle;
2032 }
2033 }
2034
2035 // Schedule asset for update.
2036 newUpdates.push_back({
2037 assetComponent.getContainer(),
2038 getHandle(&assetComponent),
2039 assetComponent.asset,
2040 assetInstance
2041 });
2042 }
2043
2044 // Handle other changes than changed asset resource (handled above):
2045 if (assetData.asset && assetData.instanceData) {
2046 AssetInstanceData& assetInstance = *assetData.instanceData.get();
2047
2048 // Forward clipshape if present
2050 if (const ClipShapeRefComponent* clipRefComp = assetComponent.getComponent<ClipShapeRefComponent>(); clipRefComp) {
2051 if (EntityPtr clipShape = clipRefComp->clipShape.lock(); clipShape) {
2052 clipShapeComponent = clipShape->getComponentHandle<ClipShapeComponent>();
2053 }
2054 }
2055 bool forward = assetInstance.clipShapeComponent != clipShapeComponent;
2056 assetInstance.clipShapeComponent = clipShapeComponent;
2057
2058 // Forward objectId and layer from the render component
2059 if (const RenderComponent* renderComponent = assetComponent.getContainer()->getComponent<RenderComponent>(); renderComponent) {
2060 forward = forward || (assetInstance.objectId != renderComponent->objectId);
2061 assetInstance.objectId = renderComponent->objectId;
2062
2063 forward = forward || (assetInstance.renderLayers != renderComponent->layer);
2064 assetInstance.renderLayers = renderComponent->layer;
2065 }
2066
2067 // Something has changed, recurse down and update everything
2068 if (forward) {
2069 updateAssetInstanceRenderProperties(context, assetInstance);
2070 }
2071 }
2072
2073 // If asset is on demand, pull data from component that doesn't require an update the asset.
2074 if (assetData.asset && assetData.instanceData && assetData.instanceData->onDemand) {
2075 assetData.instanceData->freezeLod = freeze || assetComponent.freezeLod;
2076 assetData.instanceData->forceTolerance = assetComponent.forceTolerance;
2077 assetData.instanceData->tolerance = (assetComponent.forceTolerance ? 1.f : qualityScale) * assetComponent.tolerance;
2078 assetData.instanceData->minDistance = assetComponent.minDistance;
2079
2080 assetData.instanceData->selectedIdRanges.clear();
2081 if (!assetComponent.idRanges.empty()) {
2082
2083 size_t count = assetComponent.idRanges.size() / 2;
2084 std::vector<uint32_t>& selectedIdRanges = assetData.instanceData->selectedIdRanges;
2085 for (size_t k = 0; k < count; k++) {
2086 const uint32_t rangeMin = assetComponent.idRanges[2 * k + 0];
2087 const uint32_t rangeMax = assetComponent.idRanges[2 * k + 1];
2088 if (rangeMax < rangeMin || (!selectedIdRanges.empty() && rangeMin < selectedIdRanges.back()) ) {
2089 // Either invalid range or unsorted/overlapping ranges.
2090 selectedIdRanges.clear();
2091 break;
2092 }
2093 selectedIdRanges.push_back(rangeMin);
2094 selectedIdRanges.push_back(rangeMax);
2095 }
2096 }
2097
2098 currentOnDemandInstances.emplace_back(assetData.instanceData.get());
2099 }
2100 }
2101
2102 // Run through all new asset updates
2103 // - release the associated node hiearchies,
2104 // - Populate readyUpdates with non-ondemand updates that is loaded.
2105 // - Non-loaded assets gets their generation updated when loaded,
2106 // which puts them into this queue again
2107 AssetSystemVector<AssetUpdate>& readyUpdates = systemData->readyUpdates; assert(readyUpdates.empty());
2108 for (auto & assetUpdate : newUpdates) {
2109 if (!assetUpdate.asset) {
2110 context->store->removeChildren(assetUpdate.container);
2111 } else if (assetUpdate.asset && assetUpdate.asset->isLoaded()) {
2112
2113 for (auto & resourceDefinition : assetUpdate.asset->definition.resources) {
2114 context->assetManager->instantiateResource(assetUpdate.asset, resourceDefinition);
2115 }
2116
2117 if (!assetUpdate.instanceData->onDemand) {
2118 context->store->removeChildren(assetUpdate.container);
2119 readyUpdates.emplace_back(assetUpdate);
2120 }
2121 }
2122 }
2123 newUpdates.clear();
2124
2125 // Run through new non-ondemand asset updates that are ready.
2126 for (auto & assetUpdate : readyUpdates) {
2127 auto asset = assetUpdate.asset.resolve();
2128 const size_t entityCount = asset->definition.scene.entities.size();
2129 std::vector<ComponentModel::Entity*> entities(entityCount);
2130 for (auto & def : asset->definition.scene.entities) {
2131 Entity* parent = assetUpdate.container;
2132 if (def.parentIndex != static_cast<uint32_t>(-1)) {
2133 assert(def.parentIndex < entityCount && entities[def.parentIndex]);
2134 parent = entities[def.parentIndex];
2135 }
2136 assert(def.index < entityCount && entities[def.index] == nullptr);
2137 entities[def.index] = createEntity(context, asset->definition.scene, def, parent);
2138 }
2139 }
2140 readyUpdates.clear();
2141
2142 // Run through ondemand requests in SystemData::inFlightRequests
2143 // - If cancelled, destroy
2144 // - If loaded, remove from list and update corresponding modelFile in assetInstance.
2145 processInFlightRequests(context, systemData.get());
2146
2147 // Run through newOnDemandInstances
2148 // - Initialize asset instance
2149 // - Populate liveAssetInstances current set of assets that are visible with a non-empty scene
2150 AssetSystemVector<AssetInstanceData*>& liveAssetInstances = systemData->liveAssetInstances; assert(liveAssetInstances.empty());
2151 liveAssetInstances.reserve(currentOnDemandInstances.size());
2152 for (auto assetInstance : currentOnDemandInstances) {
2153 assetInstance->stats.frame = {};
2154
2155 if (!assetInstance->scene.initialized) {
2156 initializeAssetInstance(*assetInstance);
2157 }
2158
2159 if (assetInstance->scene.entities.empty()) continue;
2160
2161 auto sceneComponent = assetInstance->container->getComponent<SceneComponent>();
2162
2163 const bool visible = sceneComponent ? sceneComponent->visible : true;
2164 const bool visibleChanged = visible != assetInstance->visible;
2165
2166 if (!visible && !visibleChanged) continue;
2167
2168 assetInstance->visible = visible;
2169
2170 liveAssetInstances.emplace_back(assetInstance);
2171 }
2172 currentOnDemandInstances.clear();
2173
2174 // Recursively figure out target lod-levels for active on-demand assets.
2175 Parallel::forEach(context, liveAssetInstances.size(), [&](size_t i)
2176 {
2177 auto assetInstance = liveAssetInstances[i];
2178
2179 if (!assetInstance->freezeLod) {
2180 assetInstance->stats.frustumCullIn = 0;
2181 assetInstance->stats.frustumCullOut = 0;
2182 assetInstance->stats.bboxCullOut = 0;
2183 updateAssetDesiredLevels(context, *assetInstance, 0, 1);
2184 }
2185 }, "AssetSystem::updateLiveInstances");
2186
2187 for (auto assetInstance : liveAssetInstances) {
2188 {
2189 auto processingTime = assetInstance->stats.frame.startProcessing();
2190
2191 {
2192 auto updateTime = assetInstance->stats.frame.startUpdate();
2193
2194 {
2195 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::updateEntities");
2196
2197 EntityInstanceData root;
2198 root.entityPtr = assetInstance->container;
2199
2200 AssetLodReference lodReference = {};
2201 getLodReference(context, *assetInstance, lodReference, assetInstance->container);
2202
2203 for (size_t i = 0; i < assetInstance->scene.roots.size(); ++i) {
2204 updateEntity(context, lodReference, *assetInstance, assetInstance->scene[assetInstance->scene.roots[i]], &root);
2205 }
2206 }
2207
2208 CpuInstrumentationScope(SCOPE_ASSETSYSTEM, "AssetSystem::calculateCost");
2209
2210 auto costTime = assetInstance->stats.frame.startCostCalculation();
2211
2212#if ENABLE_COST_TRACKING
2213 assetInstance->stats.current = { 0, 0, 0 };
2214 for (size_t i = 0; i < assetInstance->scene.roots.size(); ++i) {
2215 calculateCost(context, *assetInstance, assetInstance->scene[assetInstance->scene.roots[i]]);
2216 }
2217#endif
2218 }
2219
2220 dispatchMessages(context, *assetInstance);
2221 }
2222
2223 double limit = assetInstance->stats.frameLimits.processingTime;
2224 if (assetInstance->stats.frame.processingTime > limit) {
2225 LOG_TRACE(logger, "Asset processing time exceeded limit %.3fs.", limit);
2226 LOG_TRACE(logger, " Lod time: %.3fs", assetInstance->stats.frame.lodTime);
2227 LOG_TRACE(logger, " Update time: %.3fs", assetInstance->stats.frame.updateTime);
2228 LOG_TRACE(logger, " Cost time: %.3fs", assetInstance->stats.frame.calculateCostTime);
2229 LOG_TRACE(logger, " ------------------------");
2230 LOG_TRACE(logger, " Entities spawned: %d", assetInstance->stats.frame.spawnedEntities);
2231 LOG_TRACE(logger, " Entities destroyed: %d", assetInstance->stats.frame.destroyedEntities);
2232 LOG_TRACE(logger, " Models requested: %d", assetInstance->stats.frame.modelsRequested);
2233 LOG_TRACE(logger, "");
2234 }
2235 }
2236 liveAssetInstances.clear();
2237
2238 processPendingRequests(context, systemData.get(),
2239 context->modelManager,
2240 systemData->pendingRequests,
2241 systemData->inFlightRequests,
2242 (size_t)context->variables->get(maxModelsInFlightVariableName, kDefaultMaxModelsInFlight),
2243 (size_t)context->variables->get(maxModelLoadsPerFrameVariableName, kDefaultMaxModelLoadsPerFrame));
2244}
2245
2247{
2248 base::postUpdate(context);
2249}
2250
2252{
2253 ComponentHandle handle = base::createComponent();
2254
2255 // Ensure consistent AssetBounds.
2256 if (pool.size() == 1u) {
2257 assert(bounds == nullptr);
2258 bounds = std::make_unique<AssetBounds>(this);
2259 context->bounds->addBoundsExtension(bounds.get());
2260 }
2261
2262 return handle;
2263}
2264
2266{
2267 base::destroyComponent(component);
2268
2269 if (pool.size() == 0u && bounds) {
2270 context->bounds->removeBoundsExtension(bounds.get());
2271 bounds.reset();
2272 }
2273}
2274
2275const Cogs::Core::AssetInstanceStats * Cogs::Core::AssetSystem::getStats(AssetComponent * assetComponent) const
2276{
2277 if (!assetComponent) return nullptr;
2278
2279 auto & data = getData<AssetData>(assetComponent);
2280
2281 return data.instanceData ? &data.instanceData->stats : nullptr;
2282}
2283
2284const Cogs::Geometry::BoundingBox* Cogs::Core::AssetSystem::getLocalBounds(AssetComponent* assetComponent) const
2285{
2286 if (!assetComponent)
2287 return nullptr;
2288
2289 auto& data = getData<AssetData>(assetComponent);
2290 if (!data.instanceData) return nullptr;
2291 if (data.instanceData->scene.entities.empty()) return nullptr;
2292
2293 // Assuming Root: index=0
2294 return &data.instanceData->scene.getLocalBounds(data.instanceData->scene[0]);
2295}
2296
2297std::span<const Cogs::Core::AssetModelRequest* const> Cogs::Core::AssetSystem::getPendingRequests() const
2298{
2299 return systemData->pendingRequests;
2300}
2301
2302std::span<const Cogs::Core::AssetModelRequest* const> Cogs::Core::AssetSystem::getInFlightRequests() const
2303{
2304 return systemData->inFlightRequests;
2305}
2306
2307Cogs::Geometry::BoundingBox Cogs::Core::AssetSystem::getWorldBounds(AssetComponent* assetComponent, bool ignoreVisibility) const
2308{
2309 if (!ignoreVisibility) {
2310 auto sc = assetComponent->getComponent<SceneComponent>();
2311 if (sc && !sc->visible)
2312 return Cogs::Geometry::BoundingBox();
2313 }
2314
2315 auto bbox = getLocalBounds(assetComponent);
2316 if (bbox == nullptr || isEmpty(*bbox))
2317 return Cogs::Geometry::BoundingBox();
2318
2319 auto transformComponent = assetComponent->getComponent<TransformComponent>();
2320 if (transformComponent) {
2321 const auto& transform = context->transformSystem->getLocalToWorld(transformComponent);
2322 return Bounds::getTransformedBounds(*bbox, transform);
2323 }
2324
2325 return *bbox;
2326}
2327
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
void getBounds(Context *context, Cogs::Geometry::BoundingBox &bounds) override
Expand bounds including bounds of all entities in this system in world coordinates.
ComponentHandle createComponent() override
Create a new component instance.
void initialize(Context *context) override
Initialize the system.
void destroyComponent(ComponentHandle component) override
Destroy the component held by the given handle.
Sets up a clipping shape that can be used by multiple entities.
Component that attaches a ClipShape to an entity.
void postUpdate()
Perform post update logic in the system.
void update()
Updates the system state to that of the current frame.
Component system template with multiple parallel structures per component stored in separate pools si...
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class QualityService > qualityService
Quality service instance.
Definition: Context.h:201
class EntityStore * store
Entity store.
Definition: Context.h:231
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
std::unique_ptr< class Scene > scene
Scene structure.
Definition: Context.h:225
void removeChildren(ComponentModel::Entity *entity)
Removes all children from the given entity.
Base component for all rendering content.
Contains information on how the entity behaves in the scene.
bool visible
If the entity this component is a member of should be visible.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
void updateTransformData(const TransformComponent &component)
Force an update of the transform data associated with the given component.
Log implementation class.
Definition: LogManager.h:140
Base allocator implementation.
Definition: Allocator.h:30
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:226
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ InvertedCube
Clip the inside of a cube.
@ None
No clipping at all.
@ Cube
Clip the outside of a cube,.
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
Definition: EntityPtr.h:12
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
ModelLoadFlags
Model loading flags. May be combined with resource loading flags.
@ NoDefaultName
No default name.
@ SkipMaterials
Tell the loader it may skip creating material instances during loading.
@ Ready
All 6 baselayers are fully loaded, instance will begin rendering.
AssetFlags
Controls asset system's model instance behaviour.
@ InstantiateOnDemand
Dynamically calculate lod levels and only instantiate what is visible.
@ RelativePaths
Paths in asset file is relative to file's path.
@ OverrideMaterial
Override any model's material with material member of asset component.
@ CloneMaterial
Clone the assetComponent material over any loaded models's material and copy over properties and vari...
RenderLayers
Contains common render layers.
Contains geometry calculations and generation.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
ComponentIndex SizeType
Type used to track the size of pools.
Definition: Component.h:19
STL namespace.
Pool used to store elements of ElementType.
Definition: Pool.h:17
Handle to a Component instance.
Definition: Component.h:67
static ComponentHandle Empty()
Returns an empty, invalid handle. Will evaluate to false if tested against using operator bool().
Definition: Component.h:119
ComponentType * resolveComponent() const
Definition: Component.h:90
Instantiates an asset model into the scene.
Material * material
Material resource this MaterialInstance is created from.
MaterialOptions options
Material rendering options.
Definition: Material.h:383
uint32_t depth
Depth in the lod hierarchy, root is 1.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
Runtime control variable.
Definition: Variables.h:27