Cogs.Core
InstancedMeshRenderSystem.cpp
1#include "InstancedMeshRenderSystem.h"
2
3#include "Context.h"
4
5#include "Components/Core/TransformComponent.h"
6#include "Components/Core/MeshComponent.h"
7#include "Components/Core/NearLimitComponent.h"
8#include "Components/Core/SceneComponent.h"
9#include "Components/Core/ClipShapeComponent.h"
10
11#include "Scene/PickingFlags.h"
12#include "Scene/RayPick.h"
13#include "Systems/Core/RenderSystem.h"
14#include "Systems/Core/TransformSystem.h"
15#include "Systems/Core/CameraSystem.h"
16#include "Systems/Core/ClipShapeSystem.h"
17
18#include "Resources/Mesh.h"
19#include "Resources/MeshManager.h"
20#include "Resources/Buffer.h"
21
22#include "Services/Variables.h"
23#include "Services/TaskManager.h"
24
25#include "Utilities/Parallel.h"
26#include "Math/RayIntersection.h"
27
28#include "Foundation/Geometry/Glm.hpp"
29#include "Foundation/Logging/Logger.h"
30
31using namespace Cogs::Core;
32using namespace Cogs::Geometry;
33
34namespace
35{
36 Cogs::Logging::Log logger = Cogs::Logging::getLogger("InstancedMeshRendererLogger");
37
38 Cogs::Geometry::BoundingBox getTransformedBounds(const Cogs::Geometry::BoundingBox& bbox, const glm::mat4 & m)
39 {
40 const glm::vec3 c000 = glm::vec3(m * glm::vec4(bbox.min.x, bbox.min.y, bbox.min.z, 1.f));
41 const glm::vec3 c001 = glm::vec3(m * glm::vec4(bbox.min.x, bbox.min.y, bbox.max.z, 1.f));
42 const glm::vec3 c010 = glm::vec3(m * glm::vec4(bbox.min.x, bbox.max.y, bbox.min.z, 1.f));
43 const glm::vec3 c011 = glm::vec3(m * glm::vec4(bbox.min.x, bbox.max.y, bbox.max.z, 1.f));
44 const glm::vec3 c100 = glm::vec3(m * glm::vec4(bbox.max.x, bbox.min.y, bbox.min.z, 1.f));
45 const glm::vec3 c101 = glm::vec3(m * glm::vec4(bbox.max.x, bbox.min.y, bbox.max.z, 1.f));
46 const glm::vec3 c110 = glm::vec3(m * glm::vec4(bbox.max.x, bbox.max.y, bbox.min.z, 1.f));
47 const glm::vec3 c111 = glm::vec3(m * glm::vec4(bbox.max.x, bbox.max.y, bbox.max.z, 1.f));
48 return Cogs::Geometry::BoundingBox {
49 glm::min(glm::min(glm::min(c000, c001), glm::min(c010, c011)),
50 glm::min(glm::min(c100, c101), glm::min(c110, c111))),
51 glm::max(glm::max(glm::max(c000, c001), glm::max(c010, c011)),
52 glm::max(glm::max(c100, c101), glm::max(c110, c111)))
53 };
54 }
55}
56
58{
60
61 geometryGroup = context->taskManager->createGroup();
62}
63
65{
66 const bool workParallel = context->engine->workParallel();
67
68 context->taskManager->wait(geometryGroup);
69
70 needsPost = false;
71
72 auto updateComponent = [this](InstancedMeshRenderComponent & renderComponent, size_t)
73 {
74 needsPost |= renderComponent.hasChanged();
75
76 InstancedMeshRenderData& data = this->getData<InstancedMeshRenderData>(&renderComponent);
77 Cogs::Geometry::BoundingBox& localBounds = getLocalBounds(&renderComponent);
78
79 if (!data.transformComponent) {
80 data.transformComponent = renderComponent.getComponentHandle<TransformComponent>();
81 data.meshComponent = renderComponent.getComponentHandle<MeshComponent>();
82 }
83
84 // Update local bounds if necessary.
85 {
86 if ((data.flags & MeshRenderDataFlags::LocalBoundsOverride) == 0) {
87 const MeshComponent* meshComponent = data.meshComponent.resolveComponent<MeshComponent>();
88
89 if (!meshComponent->meshHandle) return;
90 if (!renderComponent.instanceMesh) return;
91
92 Mesh* mesh = meshComponent->meshHandle.resolve();
93 Mesh* instanceMesh = renderComponent.instanceMesh.resolve();
94
95 if (meshComponent->hasChanged() ||
96 data.meshBoundsGeneration != mesh->getGeneration() ||
97 mesh->boundsDirty() ||
98 renderComponent.hasChanged() ||
99 data.instanceMeshBoundsGeneration != instanceMesh->getGeneration() ||
100 instanceMesh->boundsDirty()) {
101 if (mesh->boundsDirty() || (isEmpty(mesh->boundingBox) && mesh->getCount())) {
102 mesh->boundingBox = calculateBounds(mesh);
103
104 if (!isEmpty(mesh->boundingBox) && !mesh->isInitialized()) {
105 mesh->setChanged();
106 }
107 }
108
109 Geometry::BoundingBox instanceBounds;
110
111 if (mesh->boundingBox.min.x <= mesh->boundingBox.max.x) {
112 instanceBounds = mesh->boundingBox;
113 } else {
114 instanceBounds = calculateBounds(mesh, renderComponent.startIndex, renderComponent.vertexCount);
115 }
116
117 localBounds = Cogs::Geometry::BoundingBox();
118
119 if (isEmpty(instanceMesh->boundingBox)) {
120
121 if (Cogs::Core::Mesh::StreamReference instancedMatrix = instanceMesh->getSemanticStream(ElementSemantic::InstanceMatrix, DataFormat::MAT4X4_FLOAT); instancedMatrix.ptr) {
122 uint32_t instanceCount = InstancedMeshRenderComponent::getRenderCount(renderComponent.startInstance, renderComponent.instanceCount, std::min(instancedMatrix.count, instanceMesh->getCount()));
123 for (size_t i = 0; i < instanceCount; ++i) {
124 Cogs::Geometry::BoundingBox b = getTransformedBounds(instanceBounds, instancedMatrix.getDataAt<const glm::mat4>(renderComponent.startInstance + i));
125 localBounds.min = glm::min(localBounds.min, b.min);
126 localBounds.max = glm::max(localBounds.max, b.max);
127 }
128 }
129
130 else if (Cogs::Core::Mesh::StreamReference instancedPosition = instanceMesh->getSemanticStream(ElementSemantic::InstanceVector, DataFormat::X32Y32Z32_FLOAT); instancedPosition.ptr) {
131 uint32_t instanceCount = InstancedMeshRenderComponent::getRenderCount(renderComponent.startInstance, renderComponent.instanceCount, std::min(instancedPosition.count, instanceMesh->getCount()));
132 for (size_t i = 0; i < instanceCount; ++i) {
133 const glm::vec3& pos = instancedPosition.getDataAt<const glm::vec3>(renderComponent.startInstance + i);
134 localBounds.min = glm::min(localBounds.min, instanceBounds.min + pos);
135 localBounds.max = glm::max(localBounds.max, instanceBounds.max + pos);
136 }
137 }
138
139 else {
140 return;
141 }
142
143 } else {
144 localBounds.min = glm::min(localBounds.min, instanceBounds.min + instanceMesh->boundingBox.min);
145 localBounds.max = glm::max(localBounds.max, instanceBounds.max + instanceMesh->boundingBox.max);
146 }
147
148 data.meshBoundsGeneration = static_cast<uint8_t>(mesh->getGeneration());
149 data.instanceMeshBoundsGeneration = static_cast<uint8_t>(instanceMesh->getGeneration());
150 ++data.localBoundsGeneration;
151 }
152 }
153 }
154
155 // Check world bounds
156 {
157 const TransformComponent* transform = data.transformComponent.resolveComponent<TransformComponent>();
158
159 if (data.worldBoundsGeneration != data.localBoundsGeneration || transformSystem->hasChanged(transform)) {
160 data.localToWorld = transformSystem->getLocalToWorld(transform);
161
162 if (!isEmpty(localBounds)) {
163 getWorldBounds(&renderComponent) = getTransformedBounds(localBounds, data.localToWorld);
164 } else {
165 getWorldBounds(&renderComponent) = localBounds;
166 }
167
168 if (data.worldBoundsGeneration != data.localBoundsGeneration) {
169 data.worldBoundsGeneration = data.localBoundsGeneration;
170 //FIXME: If the local bounding box has changed the current frame, the world bounds used for culling will lag
171 // a frame behind, potentially being empty or invalid. To ensure the object is not culled we reset the
172 // culling index, disable culling. The culling index will be set to a valid slot the next frame.
173 // This behavior will result in sub-optimal performance for e.g dynamic geometry out of frame.
174 data.cullingIndex = NoCullingIndex;
175 }
176 }
177 }
178 };
179
180 if (workParallel) {
181 CpuInstrumentationScope(SCOPE_SYSTEMS, "InstancedMeshRenderSystem::update");
182
183 Parallel::processComponents(context, pool, "InstancedMeshRenderSystem::updateComponent", updateComponent, geometryGroup);
184
185 context->taskManager->wait(geometryGroup);
186 } else {
187 Serial::processComponents(pool, updateComponent);
188 }
189
190 ++generation;
191}
192
194{
195 if (needsPost) {
197 }
198}
199
201{
202 if (geometryGroup.isValid()) {
203 context->taskManager->destroy(geometryGroup);
204 }
205}
206
207
209{
210 ComponentHandle handle = base_type::createComponent();
211
212 // Ensure consistent Bounds.
213 if (pool.size() == 1u) {
214 assert(bounds == nullptr);
215 bounds = std::make_unique<InstancedMeshRenderBounds>(this);
216 context->bounds->addBoundsExtension(bounds.get());
217
218 assert(picker == nullptr);
219 picker = std::make_unique<InstancedMeshPicker>(this);
220 context->rayPicking->addPickable(picker.get());
221 }
222
223 return handle;
224}
226{
227 base_type::destroyComponent(component);
228
229 if (pool.size() == 0u) {
230 if (bounds) {
231 context->bounds->removeBoundsExtension(bounds.get());
232 bounds.reset();
233 }
234
235 if (picker) {
236 context->rayPicking->removePickable(picker.get());
237 picker.reset();
238 }
239 }
240}
241
242void Cogs::Core::InstancedMeshRenderSystem::initializeCulling(CullingSource* cullSource)
243{
244 const size_t offset = cullSource->count;
245 const size_t count = pool.size();
246 const size_t total = offset + count;
247 if (!count) return;
248
249 cullSource->count = total;
250 cullSource->bbMinWorld.resize(total);
251 cullSource->bbMaxWorld.resize(total);
252
253 if (count < 2048) {
254 for (SizeType i = 0; i < count; ++i) {
255 InstancedMeshRenderData& meshData = this->getData<InstancedMeshRenderData>(&pool[i]);
256 SizeType j = (SizeType)offset + i;
257 meshData.cullingIndex = j;
258 cullSource->bbMinWorld[j] = getWorldBounds(&pool[i]).min;
259 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[i]).max;
260 }
261 }
262 else {
263 auto scope = Parallel::forEach(context, count, [this, offset, cullSource](size_t i) {
264 InstancedMeshRenderData& meshData = this->getData<InstancedMeshRenderData>(&pool[(SizeType)i]);
265 SizeType j = (SizeType)offset + (SizeType)i;
266 meshData.cullingIndex = j;
267 cullSource->bbMinWorld[j] = getWorldBounds(&pool[(SizeType)i]).min;
268 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[(SizeType)i]).max;
269 }, "InstancedMeshRenderSystem::initializeCulling");
270 scope.Wait();
271 }
272}
273
274// ----
275
276
277void Cogs::Core::InstancedMeshRenderBounds::getBounds(Context* /*context*/, Cogs::Geometry::BoundingBox& bounds)
278{
279 for (InstancedMeshRenderComponent& component : system->pool) {
280 const Cogs::Geometry::BoundingBox bbox = system->getWorldBounds(&component);
281 if (!isEmpty(bbox))
282 bounds += bbox;
283 }
284}
285
286bool Cogs::Core::InstancedMeshRenderBounds::getBounds(Context* /*context*/, const ComponentModel::Entity* entity, Cogs::Geometry::BoundingBox& bounds, bool ignoreVisibility) const
287{
289 if (!component) return false;
290
291 if (!ignoreVisibility) {
292 const SceneComponent* sc = component->getComponent<SceneComponent>();
293 if (sc && !sc->visible)
294 return false;
295 }
296
297 const Cogs::Geometry::BoundingBox bbox = system->getWorldBounds(component);
298 if (isEmpty(bbox)) {
299 return false;
300 }
301 else {
302 bounds = bbox;
303 return true;
304 }
305}
306
307
308// ----
310 const glm::mat4& worldPickMatrix,
311 const glm::mat4& rawViewProjection,
312 const glm::mat4& viewMatrix,
313 const RayPicking::RayPickFilter& filter,
314 PickingFlags pickingFlags,
315 PicksReturned returnFlag,
316 std::vector<RayPicking::RayPickHit>& hits)
317{
318 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
319
320 bool hitSomething = false;
321
322 for (const InstancedMeshRenderComponent& comp : system->pool) {
323
324 // If set, rayPicking handled by elsewhere by some dedicated system
325 if (comp.customRayPickHandling) { continue; }
326
327 // Filter out unwanted, invisible or disabled entities early to speed up queries.
328 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
329
330 // ForcePickable flag overrides visibility and flags.
332 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
333 continue;
334 }
335 }
336
337 const InstancedMeshRenderData& renderData = system->getData<InstancedMeshRenderData>(&comp);
338 if (!renderData.meshComponent) { continue; }
339
340
341 Mesh* instanceMesh = comp.instanceMesh.resolve();
342 if (!instanceMesh || !instanceMesh->isActive()) { continue; }
343
344 const Geometry::BoundingBox& bboxWorld = system->getWorldBounds(&comp);
345
346 // Note that the bbox test must be 'fuzzy' (including tolerance) for
347 // line picking not to prematurely fail in bbox test (bbox of single
348 // line is a single line).
349 if (isEmpty(bboxWorld)) {
350 continue;
351 }
352
353 if (boundingBoxOutsideFrustum(worldPickMatrix, bboxWorld.min, bboxWorld.max)) {
354 continue;
355 }
356
357
358 uint32_t instanceCount = 0;
359 Cogs::Core::Mesh::StreamReference instancedMatrix{};
360 Cogs::Core::Mesh::StreamReference instancedPosition{};
361 if (instancedMatrix = instanceMesh->getSemanticStream(ElementSemantic::InstanceMatrix, DataFormat::MAT4X4_FLOAT); instancedMatrix.ptr) {
362 instanceCount = InstancedMeshRenderComponent::getRenderCount(comp.startInstance, comp.instanceCount, std::min(instancedMatrix.count, instanceMesh->getCount()));
363 }
364 else if (instancedPosition = instanceMesh->getSemanticStream(ElementSemantic::InstanceVector, DataFormat::X32Y32Z32_FLOAT); instancedPosition.ptr) {
365 instanceCount = InstancedMeshRenderComponent::getRenderCount(comp.startInstance, comp.instanceCount, std::min(instancedPosition.count, instanceMesh->getCount()));
366 }
367 else { continue; }
368
369 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
370 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
371 const uint32_t instanceIdMultiplier = comp.instanceIdMultiplier;
372
373 for (size_t i = 0; i < instanceCount; ++i) {
374
375 glm::mat4 instanceTransform;
376 if (instancedMatrix.ptr) {
377 const glm::mat4& mat = instancedMatrix.getDataAt<const glm::mat4>(comp.startInstance + i);
378 instanceTransform = renderData.localToWorld * mat;
379 }
380 else if (instancedPosition.ptr) {
381 const glm::vec3& pos = instancedPosition.getDataAt<const glm::vec3>(comp.startInstance + i);
382 instanceTransform = renderData.localToWorld * glm::translate(glm::mat4(1.0f), pos);
383 }
384 else {
385 assert(false);
386 continue;
387 }
388
389 // Create transform from local frame to pick frustum, where pick ray
390 // is negative Z and ray fuzziness is +/- w.
391 glm::mat4 localPickMatrix = worldPickMatrix * instanceTransform;
392
393 // Per-instance bbox reject, avoids running full mesh intersection for instances outside the pick frustum.
394 if (!isEmpty(mesh->boundingBox) && boundingBoxOutsideFrustum(localPickMatrix, mesh->boundingBox.min, mesh->boundingBox.max)) {
395 continue;
396 }
397
398 const RayPicking::Ordinal ordinal = filter.getOrdinal(comp);
399
400 std::vector<RayIntersectionHit> meshHits;
401 std::vector<FrustumPlanes> scratch;
402 if (!intersectMesh(meshHits,
403 scratch,
404 localPickMatrix,
405 *mesh,
406 comp.startIndex,
407 comp.vertexCount,
408 static_cast<uint32_t>(-1))) {
409 continue;
410 }
411
412 if (meshHits.empty()) {
413 continue;
414 }
415
416 for (const RayIntersectionHit& meshHit : meshHits) {
417 const glm::vec4 position = instanceTransform * glm::vec4(meshHit.pos_, 1.f);
418
419 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
420 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
421 if (clippedByClipComp(clipData, position)) {
422 continue;
423 }
424 }
425
426 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
427 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
428
429 glm::vec4 viewPos = viewMatrix * position;
430 float viewDist = -viewPos.z;
431
432 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
433 if (hits[0].isBehind(ordinal, viewDist)) {
434 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
435 if (instanceIdMultiplier) {
436 textureCoords.x = static_cast<float>(static_cast<uint32_t>(textureCoords.x) + instanceIdMultiplier * i);
437 }
438 hits[0] = {comp, returnChildEntity, position, ordinal, viewDist, textureCoords};
439 hitSomething = true;
440 }
441 // else, the intersection we found is further, so don't do anything
442 }
443 else {
444 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
445 if (instanceIdMultiplier) {
446 textureCoords.x = static_cast<float>(static_cast<uint32_t>(textureCoords.x) + instanceIdMultiplier * i);
447 }
448 hits.emplace_back(comp, returnChildEntity, position, ordinal, viewDist, textureCoords);
449 hitSomething = true;
450 }
451 }
452 }
453 }
454 }
455
456 return hitSomething;
457}
ComponentType * getComponent() const
Definition: Component.h:159
ComponentHandle getComponentHandle() const
Definition: Component.h:177
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
Sets up a clipping shape that can be used by multiple entities.
Context * context
Pointer to the Context instance the system lives in.
void postUpdate()
Perform post update logic in the system.
virtual void initialize(Context *context)
Initialize the system.
void update()
Updates the system state to that of the current frame.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
virtual void getBounds(Context *context, Cogs::Geometry::BoundingBox &bounds) override
Expand bounds including bounds of all entities in this system in world coordinates.
uint32_t startIndex
Start vertex index to render from.
uint32_t vertexCount
Number of vertexes to draw. uint32_t(-1) to draw all remaining vertices.
uint32_t instanceCount
Instance count. uint32_t(-1) to draw all remaining instances.
static uint32_t getRenderCount(uint32_t startIndex, uint32_t instanceCount, uint32_t meshCount)
Get number of instanced Meshes to render.
ComponentHandle createComponent() override
Create a new component instance.
void destroyComponent(ComponentHandle component) override
Destroy the component held by the given handle.
void initialize(Context *context) override
Initialize the system.
void cleanup(Context *context) override
Provided for custom cleanup logic in derived systems.
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
ComponentModel::ComponentHandle clipShapeComponent
Handle to the currently active clip component, if any.
constexpr bool isVisibleInLayer(RenderLayers layerMask) const
Check if the entity should be visible in the given layer mask.
ComponentModel::ComponentHandle customRayPickHandling
Handle to the component that manages custom raypicking.
constexpr bool isVisible() const
Check if the entity is visible or not.
constexpr bool isRenderFlagSet(RenderFlags flag) const
Check if the given flag is currently set.
constexpr bool isPickable() const
Check if the entity is pickable or not.
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.
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
PicksReturned
  * Options for returning picking hits.
Definition: PickingFlags.h:40
@ Closest
Return just the closest hit.
@ ForcePickable
Ensure component is pickable though it is not rendered.
PickingFlags
Options for COGS picking.
Definition: PickingFlags.h:12
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
Cogs::Geometry::BoundingBox COGSCORE_DLL_API calculateBounds(Mesh *mesh)
Calculate a bounding box for the given mesh.
Definition: MeshHelper.cpp:283
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
@ InstanceMatrix
Instance matrix semantic.
@ InstanceVector
Instance vector semantic.
Handle to a Component instance.
Definition: Component.h:67
ComponentType * resolveComponent() const
Definition: Component.h:90
bool pickImpl(Context *context, const glm::mat4 &worldPickMatrix, const glm::mat4 &rawViewProjection, const glm::mat4 &viewMatrix, const RayPicking::RayPickFilter &filter, PickingFlags pickingFlags, PicksReturned returnFlag, std::vector< RayPicking::RayPickHit > &hits) override
Each mesh rendering system should implement this function that goes through all components and calls ...
Utility structure containing reference to a data stream in a mesh.
Definition: Mesh.h:960
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
StreamReference getSemanticStream(ElementSemantic semantic, DataFormat format)
Get the data of the stream containing data with the given semantic, format and minimum element size.
Definition: Mesh.cpp:144
bool boundsDirty() const
Gets if the mesh bounds need to be updated before use.
Definition: Mesh.h:692
uint32_t getCount() const
Get the vertex count of the mesh.
Definition: Mesh.h:1012
bool isUnwantedType(const ComponentModel::Component &comp) const
Helper function used to determine if a given component belongs to an accepted entity type.
Definition: RayPick.cpp:202
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:64
uint8_t currentLod
The assigned LoD of the current component.
uint8_t selectedLod
The selected LoD of the composite entity.
uint32_t getGeneration() const
Get the generation count.
Definition: ResourceBase.h:380
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.