Cogs.Core
SubMeshRenderSystem.cpp
1#include "SubMeshRenderSystem.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/ClipShapeComponent.h"
9
10#include "Systems/Core/TransformSystem.h"
11#include "Systems/Core/CameraSystem.h"
12#include "Systems/Core/ClipShapeSystem.h"
13
14#include "Resources/Mesh.h"
15#include "Resources/MeshManager.h"
16
17#include "Services/Variables.h"
18#include "Services/TaskManager.h"
19
20#include "Utilities/Parallel.h"
21#include "Math/RayIntersection.h"
22
23#include "Scene/GetBounds.h"
24
25using namespace Cogs::Core;
26using namespace Cogs::Geometry;
27
29{
31
32 geometryGroup = context->taskManager->createGroup();
33}
34
36{
37 const bool workParallel = context->engine->workParallel();
38
39 context->taskManager->wait(geometryGroup);
40
41 needsPost = false;
42
43 auto updateComponent = [this, context](SubMeshRenderComponent & renderComponent, size_t)
44 {
45 needsPost |= renderComponent.hasChanged();
46
47 auto & data = this->getData<SubMeshRenderData>(&renderComponent);
48 auto & localBounds = getLocalBounds(&renderComponent);
49
50 if (!data.transformComponent) {
51 data.transformComponent = renderComponent.getComponentHandle<TransformComponent>();
52 data.meshComponent = renderComponent.getComponentHandle<MeshComponent>();
53 }
54
55 if (data.flags) return;
56
57 // Update local bounds if necessary.
58 {
59 auto meshComponent = data.meshComponent.resolveComponent<MeshComponent>();
60
61 if (!meshComponent->meshHandle) return;
62
63 auto mesh = meshComponent->meshHandle.resolve();
64
65 if (meshComponent->hasChanged() || data.meshBoundsGeneration != mesh->getGeneration() || mesh->boundsDirty()) {
66
67 if (mesh->boundsDirty()) {
68 mesh->boundingBox = calculateBounds(mesh);
69
70 if (!isEmpty(mesh->boundingBox) && !mesh->isInitialized()) {
71 mesh->setChanged();
72 }
73 }
74
75 localBounds = mesh->boundingBox;
76 data.meshBoundsGeneration = (uint8_t)mesh->getGeneration();
77 ++data.localBoundsGeneration;
78 }
79 }
80
81 // Check world bounds
82 {
83 auto transform = data.transformComponent.resolveComponent<TransformComponent>();
84
85 if (data.localBoundsGeneration != data.worldBoundsGeneration || context->transformSystem->hasChanged(transform)) {
86 data.localToWorld = context->transformSystem->getLocalToWorld(transform);
87
88 if (!isEmpty(localBounds)) {
89 getWorldBounds(&renderComponent) = Bounds::getTransformedBounds(localBounds, data.localToWorld);
90 } else {
91 getWorldBounds(&renderComponent) = localBounds;
92 }
93
94 data.worldBoundsGeneration = data.localBoundsGeneration;
95 }
96 }
97 };
98
99 if (workParallel) {
100 CpuInstrumentationScope(SCOPE_SYSTEMS, "SubMeshRenderSystem::update");
101
102 Parallel::processComponents(context, pool, "SubMeshRenderSystem::updateComponent", updateComponent, geometryGroup);
103
104 context->taskManager->wait(geometryGroup);
105 } else {
106 Serial::processComponents(pool, updateComponent);
107 }
108}
109
111{
112 if (needsPost) {
114 }
115}
116
118{
119 if (geometryGroup.isValid()) {
120 context->taskManager->destroy(geometryGroup);
121 }
122}
123
125{
126 ComponentHandle handle = base_type::createComponent();
127
128 // Ensure consistent Bounds.
129 if (pool.size() == 1u) {
130 assert(picker == nullptr);
131 picker = std::make_unique<SubMeshPicker>(this);
132 context->rayPicking->addPickable(picker.get());
133 }
134
135 return handle;
136}
138{
139 base_type::destroyComponent(component);
140
141 if (pool.size() == 0u) {
142 context->rayPicking->removePickable(picker.get());
143 picker.reset();
144 }
145}
146
147void Cogs::Core::SubMeshRenderSystem::initializeCulling(CullingSource * cullSource)
148{
149 const size_t offset = cullSource->count;
150 const size_t count = pool.size();
151 const size_t total = offset + count;
152
153 if (!count) return;
154
155 cullSource->count = total;
156 cullSource->bbMinWorld.resize(total);
157 cullSource->bbMaxWorld.resize(total);
158
159 if (count < 2048) {
160 for (SizeType i = 0; i < count; ++i) {
161 auto & meshData = this->getData<SubMeshRenderData>(&pool[i]);
162 SizeType j = (SizeType)offset + i;
163 meshData.cullingIndex = j;
164 cullSource->bbMinWorld[j] = getWorldBounds(&pool[i]).min;
165 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[i]).max;
166 }
167 }
168 else {
169 auto scope = Parallel::forEach(context, count, [this, offset, cullSource](size_t i) {
170 auto & meshData = this->getData<SubMeshRenderData>(&pool[(SizeType)i]);
171 SizeType j = (SizeType)offset + (SizeType)i;
172 meshData.cullingIndex = j;
173 cullSource->bbMinWorld[j] = getWorldBounds(&pool[(SizeType)i]).min;
174 cullSource->bbMaxWorld[j] = getWorldBounds(&pool[(SizeType)i]).max;
175 }, "SubMeshRenderSystem::initializeCulling");
176 scope.Wait();
177 }
178}
179
180
182 const glm::mat4& worldPickMatrix,
183 const glm::mat4& rawViewProjection,
184 const glm::mat4& viewMatrix,
185 const RayPicking::RayPickFilter& filter,
186 PickingFlags pickingFlags,
187 PicksReturned returnFlag,
188 std::vector<RayPicking::RayPickHit>& hits)
189{
190 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
191
192 bool hitSomething = false;
193
194 for (const SubMeshRenderComponent& comp : system->pool) {
195 // Filter out unwanted, invisible or disabled entities early to speed up queries.
196 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
197
198 // ForcePickable flag overrides visibility and flags.
200 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
201 continue;
202 }
203 }
204
205 const SubMeshRenderData& renderData = system->getData<SubMeshRenderData>(&comp);
206 if (!renderData.meshComponent) {
207 continue;
208 }
209
210 const Geometry::BoundingBox& bboxWorld = system->getWorldBounds(&comp);
211 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
212
213 if (mesh == nullptr)
214 continue;
215
216
217 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
218
219 // Note that the bbox test must be 'fuzzy' (including tolerance) for
220 // line picking not to prematurely fail in bbox test (bbox of single
221 // line is a single line).
222 if (isEmpty(bboxWorld)) {
223 continue;
224 }
225
226 if (frustumClassifyBoundingBox(worldPickMatrix, bboxWorld.min, bboxWorld.max) != FrustumPlanes::InsideAll) {
227 continue;
228 }
229
230 // Create transform from local frame to pick frustum, where pick ray
231 // is negative Z and ray fuzziness is +/- w.
232 glm::mat4 localPickMatrix = worldPickMatrix * renderData.localToWorld;
233
234 std::vector<RayIntersectionHit> meshHits;
235 std::vector<FrustumPlanes> scratch;
236 if (!intersectMesh(meshHits,
237 scratch,
238 localPickMatrix,
239 *mesh,
240 0,
241 static_cast<uint32_t>(-1),
242 comp.subMesh)) {
243 continue;
244 }
245
246 if (meshHits.empty()) {
247 continue;
248 }
249
250 for (const RayIntersectionHit& meshHit : meshHits) {
251 const glm::vec4 position = renderData.localToWorld * glm::vec4(meshHit.pos_, 1.f);
252
253 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
254 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
255 if (clippedByClipComp(clipData, position)) {
256 continue;
257 }
258 }
259
260 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
261 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
262
263 glm::vec4 viewPos = viewMatrix * position;
264 float viewDist = -viewPos.z;
265
266 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
267 if (viewDist < hits[0].distance) {
268 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
269 hits[0] = {comp, returnChildEntity, position, viewDist, textureCoords};
270 hitSomething = true;
271 }
272 // else, the intersection we found is further, so don't do anything
273 }
274 else {
275 glm::vec2 textureCoords = getTextureCoords(texcoordInfo, meshHit, pickingFlags);
276 hits.emplace_back(comp, returnChildEntity, position, viewDist, textureCoords);
277 hitSomething = true;
278 }
279 }
280 }
281 }
282
283 if (returnFlag == PicksReturned::AllSorted) {
284 std::sort(hits.begin(), hits.end());
285 }
286 return hitSomething;
287}
ComponentHandle getComponentHandle() const
Definition: Component.h:177
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
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.
Renders a mesh with flexible submesh usage.
void initialize(Context *context) override
Initialize the system.
void destroyComponent(ComponentHandle component) override
Destroy the component held by the given handle.
void cleanup(Context *context) override
Provided for custom cleanup logic in derived systems.
ComponentHandle createComponent() override
Create a new component instance.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
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.
ComponentIndex SizeType
Type used to track the size of pools.
Definition: Component.h:19
Handle to a Component instance.
Definition: Component.h:67
ComponentType * resolveComponent() const
Definition: Component.h:90
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
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.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
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 ...