Cogs.Core
TwinCadModelRayPickExtension.cpp
1#include "Systems/Core/RenderSystem.h"
2#include "Systems/Core/ClipShapeSystem.h"
3
4#include "Components/Core/MeshComponent.h"
5
6#include "Services/TaskManager.h"
7
8#include "Utilities/FrustumClassification.h"
9
10#include "Resources/Mesh.h"
11
12#include "Math/RayIntersection.h"
13
14#include "Foundation/Logging/Logger.h"
15#include "Foundation/Platform/Threads.h"
16#include "Foundation/Geometry/BoundingBox.hpp"
17
18#include "Context.h"
19
20#include "TwinCadModelSystem.h"
21
22namespace {
23
24 Cogs::Logging::Log logger = Cogs::Logging::getLogger("TwinCadModelRayPick");
25
26}
27
28Cogs::Core::TwinCadModelRayPickExtension::TwinCadModelRayPickExtension(RenderSystem* renderSystem, TwinCadModelSystem* cadModelSystem)
29: renderSystem(renderSystem),
30 cadModelSystem(cadModelSystem)
31{}
32
33
35 const glm::mat4& worldPickMatrix,
36 const glm::mat4& rawViewProjection,
37 const glm::mat4& viewMatrix,
38 const RayPicking::RayPickFilter& filter,
39 PickingFlags pickingFlags,
40 PicksReturned returnFlag,
41 std::vector<RayPicking::RayPickHit>& hits)
42{
43 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
44 const bool addInstanceTexcoords = (pickingFlags & PickingFlags::AddInstanceTexcoords) == PickingFlags::AddInstanceTexcoords;
45
46 const size_t hitsBefore = hits.size();
47
48 const Cogs::SizeType itemCount = renderSystem->pool.size();
49 const Cogs::SizeType batchSize = 1024;
50
51 TaskId group = context->taskManager->createGroup();
52 Mutex batchLock;
53
54 for (Cogs::SizeType start = 0; start < itemCount; start += batchSize) {
55 const Cogs::SizeType end = std::min(start + batchSize, itemCount);
56
57 context->taskManager->enqueueChild(group,
58 [&, start, end]() {
59 CpuInstrumentationScope(SCOPE_RAYPICK, "executePickingQueryTask");
60
61 std::vector<RayPicking::RayPickHit> batchPickResults;
62 if (returnFlag != PicksReturned::Closest) {
63 batchPickResults.reserve(10); // Reserve space for 10 hits in the current batch based purely on guessing.
64 }
65
66 for (Cogs::SizeType i = start; i < end; i++) {
67 const MeshRenderComponent& comp = renderSystem->pool[i];
68
69 // Here, we just let the custom handling pass.
70 if (!comp.customRayPickHandling) { continue; }
71
73 if (cadModelComp == nullptr) continue;
74
75 const TwinCadModelData& cadModelData = cadModelSystem->getData(cadModelComp);
76
77 // Filter out unwanted, invisible or disabled entities early to speed up queries.
78 if (filter.isUnwantedType(comp) || comp.lod.currentLod != comp.lod.selectedLod) { continue; }
79
80 // ForcePickable flag overrides visibility and flags.
82 if (!comp.isVisible() || !comp.isVisibleInLayer(filter.layerMask) || !comp.isPickable()) {
83 continue;
84 }
85 }
86
87 const RayPicking::Ordinal ordinal = filter.getOrdinal(comp);
88
89 const MeshRenderData& renderData = renderSystem->getData<MeshRenderData>(&comp);
90 if (!renderData.meshComponent) {
91 continue;
92 }
93
94 Geometry::BoundingBox bboxWorld = renderSystem->getWorldBounds(&comp);
95 const Mesh* mesh = renderData.meshComponent.resolveComponent<MeshComponent>()->meshHandle.resolve();
96 if (!mesh) {
97 continue;
98 }
99 TextureCoordinateInfo texcoordInfo = getTextureCoordinateInfo(*mesh);
100
101 if (cadModelComp->allowClipping) {
102 bboxWorld.min = glm::max(bboxWorld.min, cadModelComp->minClipping);
103 bboxWorld.max = glm::min(bboxWorld.max, cadModelComp->maxClipping);
104 }
105
106 // Note that the bbox test must be 'fuzzy' (including tolerance) for
107 // line picking not to prematurely fail in bbox test (bbox of single
108 // line is a single line).
109 if (Geometry::isEmpty(bboxWorld)) {
110 continue;
111 }
112
113 if (frustumClassifyBoundingBox(worldPickMatrix, bboxWorld.min, bboxWorld.max) != FrustumPlanes::InsideAll) {
114 continue;
115 }
116
117 // Create transform from local frame to pick frustum, where pick ray
118 // is negative Z and ray fuzziness is +/- w.
119 glm::mat4 localPickMatrix = worldPickMatrix * renderData.localToWorld;
120
121 std::vector<RayIntersectionHit> meshHits;
122 std::vector<FrustumPlanes> scratch;
123 if (!intersectMesh(meshHits,
124 scratch,
125 localPickMatrix,
126 *mesh,
127 comp.startIndex,
128 comp.vertexCount,
129 static_cast<uint32_t>(-1))) {
130 continue;
131 }
132
133 if (meshHits.empty()) {
134 continue;
135 }
136
137 for (const RayIntersectionHit& meshHit : meshHits) {
138
139 uint32_t objectId = 0;
140 switch (texcoordInfo.format) {
141 case Cogs::DataFormat::R32_FLOAT:
142 objectId = static_cast<uint32_t>( * reinterpret_cast<const float*>(texcoordInfo.ptr + texcoordInfo.stride * meshHit.index.x));
143 break;
144 case Cogs::DataFormat::R32G32_FLOAT:
145 objectId = static_cast<uint32_t>( * reinterpret_cast<const float*>(texcoordInfo.ptr + texcoordInfo.stride * meshHit.index.x));
146 break;
147 case Cogs::DataFormat::R8_UINT:
148 objectId = * reinterpret_cast<const uint8_t *>(texcoordInfo.ptr + texcoordInfo.stride * meshHit.index.x);
149 break;
150 case Cogs::DataFormat::R16_UINT:
151 objectId = * reinterpret_cast<const uint16_t *>(texcoordInfo.ptr + texcoordInfo.stride * meshHit.index.x);
152 break;
153 case Cogs::DataFormat::R32_UINT:
154 objectId = * reinterpret_cast<const uint32_t *>(texcoordInfo.ptr + texcoordInfo.stride * meshHit.index.x);
155 break;
156 case Cogs::DataFormat::Unknown: // No texcoords
157 break;
158 default:
159 LOG_WARNING_ONCE(logger, "Unhandled texcoord format 0x%x", static_cast<uint32_t>(texcoordInfo.format));
160 break;
161 }
162 if (addInstanceTexcoords && texcoordInfo.hasIdOffset) {
163 objectId += texcoordInfo.idOffset;
164 }
165
166 // Skip hidden object Ids
167 size_t objectIdIx = 4 * objectId + 3;
168 if ((cadModelData.attributeMapData.size() >= objectIdIx) && ((cadModelData.attributeMapData[objectIdIx] >> 2u) == 3u)) {
169 continue;
170 }
171
172 const glm::vec4 position = renderData.localToWorld * glm::vec4(meshHit.pos_, 1.f);
173
174 if (cadModelComp->allowClipping) {
175 // We assume localToWorld is affine, i.e. position.w is 1.
176 const glm::vec3 minClipping = cadModelComp->minClipping;
177 const glm::vec3 maxClipping = cadModelComp->maxClipping;
178 if ((position.x < minClipping.x) || (maxClipping.x < position.x) ||
179 (position.y < minClipping.y) || (maxClipping.y < position.y) ||
180 (position.z < minClipping.z) || (maxClipping.z < position.z))
181 {
182 continue;
183 }
184 }
185
186 if (const ClipShapeComponent* clipComp = comp.clipShapeComponent.resolveComponent<ClipShapeComponent>(); clipComp) {
187 const ClipShapeData& clipData = context->clipShapeSystem->getData(clipComp);
188 if (clippedByClipComp(clipData, position)) {
189 continue;
190 }
191 }
192
193 const glm::vec4 clipPos = rawViewProjection * glm::vec4(position.x, position.y, position.z, 1.f);
194 // use clip space to see if the point falls within the view frustum
195 if ((-clipPos.w < clipPos.z) && (clipPos.z < clipPos.w)) {
196
197 glm::vec4 viewPos = viewMatrix * position;
198 float viewDist = -viewPos.z;
199
200 if (returnFlag == PicksReturned::Closest && !batchPickResults.empty()) {
201 if (batchPickResults[0].isBehind(ordinal, viewDist)) {
202 batchPickResults[0] = {comp, returnChildEntity, position, ordinal, viewDist, glm::vec2(objectId, objectId)};
203 }
204 // else, the intersection we found is further, so don't do anything
205 }
206 else {
207 batchPickResults.emplace_back(comp, returnChildEntity, position, ordinal, viewDist, glm::vec2(objectId, objectId));
208 }
209 }
210 }
211 }
212
213 if (!batchPickResults.empty()) {
214 LockGuard guard(batchLock);
215 if (returnFlag == PicksReturned::Closest) {
216 if (hits.empty()) {
217 hits = std::move(batchPickResults);
218 }
219 else if (batchPickResults[0] < hits[0]) {
220 hits[0] = batchPickResults[0];
221 }
222 //else don't insert anything because we found a hit further away than what we already had
223 }
224 else {
225 hits.insert(hits.end(),
226 std::make_move_iterator(batchPickResults.begin()),
227 std::make_move_iterator(batchPickResults.end()));
228 }
229 }
230 }
231 );
232 }
233 context->taskManager->destroy(group);
234
235 return hitsBefore != hits.size();
236}
Sets up a clipping shape that can be used by multiple entities.
T & getData(const ComponentType *component)
Get the data stored for the given component from the data pool storing objects of the specified templ...
ComponentPool< ComponentType > pool
Pool of components managed by the system.
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
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
Renders the contents of a MeshComponent using the given materials.
uint32_t startIndex
Start vertex index to render from.
uint32_t vertexCount
Number of vertexes to draw.
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.
Log implementation class.
Definition: LogManager.h:140
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.
@ AddInstanceTexcoords
If mesh has an offset to IDs encoded in a per-instance texcoords stream, add this to the result.
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
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:202
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:62
uint8_t currentLod
The assigned LoD of the current component.
uint8_t selectedLod
The selected LoD of the composite entity.
Task id struct used to identify unique Task instances.
Definition: TaskManager.h:20
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 ...