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