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
31#include <array>
32
33using namespace Cogs::Core;
34using namespace Cogs::Geometry;
35
36namespace
37{
38 Cogs::Logging::Log logger = Cogs::Logging::getLogger("InstancedMeshRendererLogger");
39
40 Cogs::Geometry::BoundingBox getTransformedBounds(const Cogs::Geometry::BoundingBox& bbox, const glm::mat4 & m)
41 {
42 std::array<glm::vec3, 8> points;
43 Cogs::Geometry::corners(bbox, points);
44
45 for (glm::vec3& p : points) {
46 p = Cogs::Geometry::transform(p, m);
47 }
48
49 return Cogs::Geometry::computeBoundingBox(points.begin(), points.end());
50 }
51}
52
54{
56
57 geometryGroup = context->taskManager->createGroup();
58}
59
61{
62 const bool workParallel = context->engine->workParallel();
63
64 context->taskManager->wait(geometryGroup);
65
66 needsPost = false;
67
68 auto updateComponent = [this](InstancedMeshRenderComponent & renderComponent, size_t)
69 {
70 needsPost |= renderComponent.hasChanged();
71
72 InstancedMeshRenderData& data = this->getData<InstancedMeshRenderData>(&renderComponent);
73 Cogs::Geometry::BoundingBox& localBounds = getLocalBounds(&renderComponent);
74
75 if (!data.transformComponent) {
76 data.transformComponent = renderComponent.getComponentHandle<TransformComponent>();
77 data.meshComponent = renderComponent.getComponentHandle<MeshComponent>();
78 }
79
80 // Update local bounds if necessary.
81 {
82 if ((data.flags & MeshRenderDataFlags::LocalBoundsOverride) == 0) {
83 const MeshComponent* meshComponent = data.meshComponent.resolveComponent<MeshComponent>();
84
85 if (!meshComponent->meshHandle) return;
86 if (!renderComponent.instanceMesh) return;
87
88 Mesh* mesh = meshComponent->meshHandle.resolve();
89 Mesh* instanceMesh = renderComponent.instanceMesh.resolve();
90
91 if (meshComponent->hasChanged() ||
92 data.meshBoundsGeneration != mesh->getGeneration() ||
93 mesh->boundsDirty() ||
94 renderComponent.hasChanged() ||
95 data.instanceMeshBoundsGeneration != instanceMesh->getGeneration() ||
96 instanceMesh->boundsDirty()) {
97 if (mesh->boundsDirty() || (isEmpty(mesh->boundingBox) && mesh->getCount())) {
98 mesh->boundingBox = calculateBounds(mesh);
99
100 if (!isEmpty(mesh->boundingBox) && !mesh->isInitialized()) {
101 mesh->setChanged();
102 }
103 }
104
105 Geometry::BoundingBox instanceBounds;
106
107 if (mesh->boundingBox.min.x <= mesh->boundingBox.max.x) {
108 instanceBounds = mesh->boundingBox;
109 } else {
110 instanceBounds = calculateBounds(mesh, renderComponent.startIndex, renderComponent.vertexCount);
111 }
112
113 localBounds = Cogs::Geometry::BoundingBox();
114
115 if (isEmpty(instanceMesh->boundingBox)) {
116 auto[pPos, pCount, pStride] = instanceMesh->getSemanticStream(ElementSemantic::InstanceVector, DataFormat::X32Y32Z32_FLOAT);
117 const uint32_t instanceCount = InstancedMeshRenderComponent::getRenderCount(renderComponent.startInstance, renderComponent.instanceCount, pCount);
118
119 for (size_t i = 0; i < instanceCount; ++i) {
120 const glm::vec3 & instancePosition = *reinterpret_cast<glm::vec3 *>(pPos + pStride * (renderComponent.startInstance + i));
121
122 localBounds.min = glm::min(localBounds.min, instanceBounds.min + instancePosition);
123 localBounds.max = glm::max(localBounds.max, instanceBounds.max + instancePosition);
124 }
125
126 } else {
127 localBounds.min = glm::min(localBounds.min, instanceBounds.min + instanceMesh->boundingBox.min);
128 localBounds.max = glm::max(localBounds.max, instanceBounds.max + instanceMesh->boundingBox.max);
129 }
130
131 data.meshBoundsGeneration = static_cast<uint8_t>(mesh->getGeneration());
132 data.instanceMeshBoundsGeneration = static_cast<uint8_t>(instanceMesh->getGeneration());
133 ++data.localBoundsGeneration;
134 }
135 }
136 }
137
138 // Check world bounds
139 {
140 const TransformComponent* transform = data.transformComponent.resolveComponent<TransformComponent>();
141
142 if (data.worldBoundsGeneration != data.localBoundsGeneration || transformSystem->hasChanged(transform)) {
143 data.localToWorld = transformSystem->getLocalToWorld(transform);
144
145 if (!isEmpty(localBounds)) {
146 getWorldBounds(&renderComponent) = getTransformedBounds(localBounds, data.localToWorld);
147 } else {
148 getWorldBounds(&renderComponent) = localBounds;
149 }
150
151 if (data.worldBoundsGeneration != data.localBoundsGeneration) {
152 data.worldBoundsGeneration = data.localBoundsGeneration;
153 //FIXME: If the local bounding box has changed the current frame, the world bounds used for culling will lag
154 // a frame behind, potentially being empty or invalid. To ensure the object is not culled we reset the
155 // culling index, disable culling. The culling index will be set to a valid slot the next frame.
156 // This behavior will result in sub-optimal performance for e.g dynamic geometry out of frame.
157 data.cullingIndex = NoCullingIndex;
158 }
159 }
160 }
161 };
162
163 if (workParallel) {
164 CpuInstrumentationScope(SCOPE_SYSTEMS, "InstancedMeshRenderSystem::update");
165
166 Parallel::processComponents(context, pool, "InstancedMeshRenderSystem::updateComponent", updateComponent, geometryGroup);
167
168 context->taskManager->wait(geometryGroup);
169 } else {
170 Serial::processComponents(pool, updateComponent);
171 }
172
173 ++generation;
174}
175
177{
178 if (needsPost) {
180 }
181}
182
184{
185 if (geometryGroup.isValid()) {
186 context->taskManager->destroy(geometryGroup);
187 }
188}
189
190
192{
193 ComponentHandle handle = base_type::createComponent();
194
195 // Ensure consistent Bounds.
196 if (pool.size() == 1u) {
197 assert(bounds == nullptr);
198 bounds = std::make_unique<InstancedMeshRenderBounds>(this);
199 context->bounds->addBoundsExtension(bounds.get());
200
201 assert(picker == nullptr);
202 picker = std::make_unique<InstancedMeshPicker>(this);
203 context->rayPicking->addPickable(picker.get());
204 }
205
206 return handle;
207}
209{
210 base_type::destroyComponent(component);
211
212 if (pool.size() == 0u) {
213 if (bounds) {
214 context->bounds->removeBoundsExtension(bounds.get());
215 bounds.reset();
216 }
217
218 if (picker) {
219 context->rayPicking->removePickable(picker.get());
220 picker.reset();
221 }
222 }
223}
224
225void Cogs::Core::InstancedMeshRenderSystem::initializeCulling(CullingSource* cullSource)
226{
227 const size_t offset = cullSource->count;
228 const size_t count = pool.size();
229 const size_t total = offset + count;
230 if (!count) return;
231
232 cullSource->count = total;
233 cullSource->bbMinWorld.resize(total);
234 cullSource->bbMaxWorld.resize(total);
235
236 if (count < 2048) {
237 for (SizeType i = 0; i < count; ++i) {
238 InstancedMeshRenderData& meshData = this->getData<InstancedMeshRenderData>(&pool[i]);
239 SizeType j = (SizeType)offset + i;
240 meshData.cullingIndex = j;
241 cullSource->bbMinWorld[j] = getWorldBounds(&pool[i]).min;
242 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[i]).max;
243 }
244 }
245 else {
246 auto scope = Parallel::forEach(context, count, [this, offset, cullSource](size_t i) {
247 InstancedMeshRenderData& meshData = this->getData<InstancedMeshRenderData>(&pool[(SizeType)i]);
248 SizeType j = (SizeType)offset + (SizeType)i;
249 meshData.cullingIndex = j;
250 cullSource->bbMinWorld[j] = getWorldBounds(&pool[(SizeType)i]).min;
251 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[(SizeType)i]).max;
252 }, "InstancedMeshRenderSystem::initializeCulling");
253 scope.Wait();
254 }
255}
256
257// ----
258
259
260void Cogs::Core::InstancedMeshRenderBounds::getBounds(Context* /*context*/, Cogs::Geometry::BoundingBox& bounds)
261{
262 for (InstancedMeshRenderComponent& component : system->pool) {
263 const Cogs::Geometry::BoundingBox bbox = system->getWorldBounds(&component);
264 if (!isEmpty(bbox))
265 bounds += bbox;
266 }
267}
268
269bool Cogs::Core::InstancedMeshRenderBounds::getBounds(Context* /*context*/, const ComponentModel::Entity* entity, Cogs::Geometry::BoundingBox& bounds, bool ignoreVisibility) const
270{
272 if (!component) return false;
273
274 if (!ignoreVisibility) {
275 const SceneComponent* sc = component->getComponent<SceneComponent>();
276 if (sc && !sc->visible)
277 return false;
278 }
279
280 const Cogs::Geometry::BoundingBox bbox = system->getWorldBounds(component);
281 if (isEmpty(bbox)) {
282 return false;
283 }
284 else {
285 bounds = bbox;
286 return true;
287 }
288}
289
290
291// ----
293 const glm::mat4& worldPickMatrix,
294 const glm::mat4& rawViewProjection,
295 const glm::mat4& viewMatrix,
296 const RayPicking::RayPickFilter& filter,
297 PickingFlags pickingFlags,
298 PicksReturned returnFlag,
299 std::vector<RayPicking::RayPickHit>& hits)
300{
301 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
302
303 bool hitSomething = false;
304
305 for (const InstancedMeshRenderComponent& comp : system->pool) {
306 // Filter out unwanted, invisible or disabled entities early to speed up queries.
307 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
308
309 // ForcePickable flag overrides visibility and flags.
311 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
312 continue;
313 }
314 }
315
316 const InstancedMeshRenderData& renderData = system->getData<InstancedMeshRenderData>(&comp);
317 if (!renderData.meshComponent) { continue; }
318
319
320 Mesh* instanceMesh = comp.instanceMesh.resolve();
321 if (!instanceMesh || !instanceMesh->isActive()) { continue; }
322
323 const auto [pPos, pCount, pStride] = instanceMesh->getSemanticStream(ElementSemantic::InstanceVector, DataFormat::X32Y32Z32_FLOAT);
324 const uint32_t instanceCount = InstancedMeshRenderComponent::getRenderCount(comp.startInstance, comp.instanceCount, pCount);
325
326 const Geometry::BoundingBox& bboxWorld = system->getWorldBounds(&comp);
327 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
328 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
329
330 for (size_t i = 0; i < instanceCount; ++i) {
331 const glm::vec3& instancePosition = *reinterpret_cast<const glm::vec3*>(pPos + pStride * (comp.startInstance + i));
332 const glm::mat4 instanceTransform = renderData.localToWorld * glm::translate(glm::mat4(1.0f), instancePosition);
333
334 // Note that the bbox test must be 'fuzzy' (including tolerance) for
335 // line picking not to prematurely fail in bbox test (bbox of single
336 // line is a single line).
337 if (isEmpty(bboxWorld)) {
338 continue;
339 }
340
341 if (frustumClassifyBoundingBox(worldPickMatrix, bboxWorld.min, bboxWorld.max) != FrustumPlanes::InsideAll) {
342 continue;
343 }
344
345 // Create transform from local frame to pick frustum, where pick ray
346 // is negative Z and ray fuzziness is +/- w.
347 glm::mat4 localPickMatrix = worldPickMatrix * instanceTransform;
348
349 std::vector<RayIntersectionHit> meshHits;
350 std::vector<FrustumPlanes> scratch;
351 if (!intersectMesh(meshHits,
352 scratch,
353 localPickMatrix,
354 *mesh,
355 comp.startIndex,
356 comp.vertexCount,
357 static_cast<uint32_t>(-1))) {
358 continue;
359 }
360
361 if (meshHits.empty()) {
362 continue;
363 }
364
365 for (const RayIntersectionHit& meshHit : meshHits) {
366 const glm::vec4 position = instanceTransform * glm::vec4(meshHit.pos_, 1.f);
367
368 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
369 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
370 if (clippedByClipComp(clipData, position)) {
371 continue;
372 }
373 }
374
375 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
376 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
377
378 glm::vec4 viewPos = viewMatrix * position;
379 float viewDist = -viewPos.z;
380
381 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
382 if (viewDist < hits[0].distance) {
383 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
384 hits[0] = {comp, returnChildEntity, position, viewDist, textureCoords};
385 hitSomething = true;
386 }
387 // else, the intersection we found is further, so don't do anything
388 }
389 else {
390 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
391 hits.emplace_back(comp, returnChildEntity, position, viewDist, textureCoords);
392 hitSomething = true;
393 }
394 }
395 }
396 }
397 }
398
399 if (returnFlag == PicksReturned::AllSorted) {
400 std::sort(hits.begin(), hits.end());
401 }
402 return hitSomething;
403}
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.
MeshHandle instanceMesh
Mesh containing instancing data.
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.
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:139
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.
@ AllSorted
Return all hits sorted based on distance from the ray start, closest first.
@ 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:180
ComponentIndex SizeType
Type used to track the size of pools.
Definition: Component.h:19
@ 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 ...
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:187
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:70
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.