Cogs.Core
MarkerPointSetPicker.cpp
1#include "MarkerPointSetPicker.h"
2#include "MarkerPointSetSystem.h"
3
4#include "Context.h"
5#include "Components/Core/SceneComponent.h"
6#include "Components/Core/TransformComponent.h"
7#include "Scene/RayPick.h"
8#include "Services/DPIService.h"
9#include "Resources/Mesh.h"
10#include "Systems/Core/CameraSystem.h"
11#include "Utilities/Math.h"
12
13#include "Rendering/IGraphicsDevice.h"
14#include "Rendering/ICapabilities.h"
15
16#include "Foundation/Logging/Logger.h"
17
18namespace
19{
20 using namespace Cogs::Core;
21
22 Cogs::Logging::Log logger = Cogs::Logging::getLogger("MarkerPointSetPicker");
23
24
25 struct RecordHit
26 {
27 std::vector<RayPicking::RayPickHit>& hits;
28 const MarkerPointSetComponent& comp;
29 RayPicking::Ordinal ordinal;
30 const RenderComponent& renderComp;
31 const glm::mat4& worldFromLocal;
32 const float rayFromLocalScale;
33 bool returnClosest;
34 bool returnChildEntity;
35 bool hitSomething = false;
36
37 void operator()(const glm::vec4& posLocal, const float distanceLocal, size_t index) {
38 glm::vec4 posWorld = worldFromLocal * posLocal;
39 const float distanceWorld = rayFromLocalScale * distanceLocal;
40 if (returnClosest && !hits.empty()) {
41 if (hits[0].isBehind(ordinal, distanceWorld)) {
42 hits[0] = { renderComp, returnChildEntity, glm::vec3(posWorld), ordinal, distanceWorld, glm::vec2(static_cast<float>(index)) };
43 hitSomething = true;
44 }
45 // else, the intersection we found is further, so don't do anything
46 }
47 else {
48 hits.emplace_back(renderComp, returnChildEntity, glm::vec3(posWorld), ordinal, distanceWorld, glm::vec2(static_cast<float>(index)));
49 hitSomething = true;
50 }
51 }
52
53 };
54
55 void pickFixedScreenSizeDisc(RecordHit& recordHit,
56 const MarkerPointSetComponent& comp,
57 const MarkerPointSetData& data,
58 const glm::vec3& rayOriginLocal,
59 const glm::vec3& rayUnitDirectionLocal,
60 const glm::mat4& clipFromLocal,
61 const glm::vec2& queryClip,
62 const glm::vec2 viewportSize,
63 const float rayPixelRadius,
64 const float maxDistanceLocal,
65 const float dpiScale)
66 {
67 const float nearDistance = comp.nearDistance;
68 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
69 const float radius = 0.5f * comp.pointSize;
70 const glm::vec4 viewPlaneLocal = data.viewPlaneLocal;
71 const glm::vec2 pickPosView = queryClip * viewportSize;
72
73 for (size_t i = 0; i < comp.positions.size(); i++) {
74 const glm::vec4 posLocal(comp.positions[i], 1.f);
75 float viewZ = glm::dot(viewPlaneLocal, posLocal);
76
77 const glm::vec3 mainCamViewDirLocal(data.viewPlaneLocal);
78 const float rcp = -1.f / glm::dot(mainCamViewDirLocal, rayUnitDirectionLocal);
79 const float mp = glm::dot(mainCamViewDirLocal, rayOriginLocal);
80 if (nearDistance <= viewZ && viewZ <= farDistance) {
81 glm::vec3 pointPosClipSpace = euclidean(clipFromLocal * posLocal);
82 glm::vec2 pointPosView = glm::vec2(pointPosClipSpace) * viewportSize;
83 const float t = rcp * (mp - glm::dot(mainCamViewDirLocal, glm::vec3(posLocal)));
84 float pickableRadius = 2.f * (radius + rayPixelRadius) * dpiScale; // factor 2 is because we multiply clip pos ([-1,1]) not normed pos ([0,1]).
85 if (glm::distance2(pickPosView, pointPosView) <= pickableRadius * pickableRadius) {
86 recordHit(posLocal, t, i);
87 }
88 }
89 }
90
91 }
92
93 void pickViewAlignedDisc(RecordHit& recordHit,
94 const MarkerPointSetComponent& comp,
95 const MarkerPointSetData& data,
96 const glm::vec3& rayOriginLocal,
97 const glm::vec3& rayUnitDirectionLocal,
98 const float rayRadiusBiasLocal,
99 const float rayRadiusSlopeLocal,
100 const float maxDistanceLocal)
101 {
102 const float nearDistance = comp.nearDistance;
103 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
104 const float radius = 0.5f * comp.pointSize + rayRadiusBiasLocal;
105 const glm::vec3 mainCamViewDirLocal(data.viewPlaneLocal);
106 const float rcp = -1.f / glm::dot(mainCamViewDirLocal, rayUnitDirectionLocal);
107 const float mp = glm::dot(mainCamViewDirLocal, rayOriginLocal);
108 if (std::isfinite(rcp)) {
109 for (size_t i = 0; i < comp.positions.size(); i++) {
110 const glm::vec4 posLocal(comp.positions[i], 1.f);
111 float viewZ = glm::dot(data.viewPlaneLocal, posLocal);
112 if (nearDistance <= viewZ && viewZ <= farDistance) {
113 const float t = rcp * (mp - glm::dot(mainCamViewDirLocal, glm::vec3(posLocal)));
114 glm::vec3 r = (rayOriginLocal - glm::vec3(posLocal)) + t * rayUnitDirectionLocal;
115 const float adjustedRadius = radius + t * rayRadiusSlopeLocal;
116 if (dot(r, r) <= adjustedRadius * adjustedRadius) {
117 recordHit(posLocal, t, i);
118 }
119 }
120 }
121 }
122 }
123
124 void pickObjectSpaceFloorDisc(RecordHit& recordHit,
125 const MarkerPointSetComponent& comp,
126 const MarkerPointSetData& data,
127 const glm::vec3& rayOriginLocal,
128 const glm::vec3& rayUnitDirectionLocal,
129 const float rayRadiusBiasLocal,
130 const float rayRadiusSlopeLocal,
131 const float maxDistanceLocal)
132 {
133 const float nearDistance = comp.nearDistance;
134 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
135 const float pointRadius = 0.5f * comp.pointSize;
136 const float rcp = -1.f / rayUnitDirectionLocal.z;
137 if (std::isfinite(rcp)) {
138 for (size_t i = 0; i < comp.positions.size(); i++) {
139 const glm::vec4 posLocal(comp.positions[i], 1.f);
140 float viewZ = glm::dot(data.viewPlaneLocal, posLocal);
141 if (nearDistance <= viewZ && viewZ <= farDistance) {
142
143 // Calculate ray radius at point
144 const glm::vec3 o3 = rayOriginLocal - glm::vec3(posLocal);
145 const float rayRadiusAtPoint = rayRadiusBiasLocal + rayRadiusSlopeLocal * glm::dot(rayUnitDirectionLocal, -o3);
146 const float testRadius = pointRadius + rayRadiusAtPoint;
147
148 if (rayRadiusAtPoint < 0.1f * pointRadius) {
149 // If ray radius is really small or zero, intersect directly with disc
150
151 const float t = rcp * (rayOriginLocal.z - posLocal.z);
152 const glm::vec2 r = (glm::vec2(rayOriginLocal) - glm::vec2(posLocal)) + t * glm::vec2(rayUnitDirectionLocal);
153
154 if (dot(r, r) <= testRadius * testRadius) {
155 recordHit(posLocal, t, i);
156 }
157 }
158 else {
159
160 // Otherwise we extrude the disc to a capped cylinder to make it easier to hit gracing discs.
161 const glm::vec2 r(rayUnitDirectionLocal);
162 const glm::vec2 o = glm::vec2(rayOriginLocal) - glm::vec2(posLocal);
163
164 // Intersect with infinite cylinder
165 const float a = glm::dot(r, r);
166 const float b = 2.f * glm::dot(o, r);
167 const float c = glm::dot(o, o) - testRadius * testRadius;
168 const float desc = b * b - 4.f * a * c;
169 if (desc < 0.f) {
170 continue; // No solutions
171 }
172
173 // We have a pair of solutions
174 const float r0 = (-b - std::copysign(std::sqrt(desc), b)) / (2.f * a);
175 const float r1 = c / (a * r0);
176 float t0 = std::min(r0, r1);
177 float t1 = std::max(r0, r1);
178
179 // Intersect with two planes that cap the infinite cylinder
180 if (float p_rcp = 1.f / rayUnitDirectionLocal.z; std::isfinite(p_rcp)) {
181 float p_r0 = -p_rcp * (o3.z - rayRadiusAtPoint);
182 float p_r1 = -p_rcp * (o3.z + rayRadiusAtPoint);
183 t0 = std::max(t0, std::min(p_r0, p_r1));
184 t1 = std::min(t1, std::max(p_r0, p_r1));
185 }
186 else if (o3.z < -rayRadiusAtPoint || rayRadiusAtPoint < o3.z) {
187 // Ray dir lies in cap plane and ray origin is outside of slab
188 continue;
189 }
190 if (t0 < t1) {
191 recordHit(posLocal, t0, i);
192 }
193 }
194 }
195 }
196 }
197 }
198
199}
200
201
203 const CameraComponent& camera,
204 const glm::vec2& queryClip,
205 float rayLength,
206 float rayPixelRadius,
207 PickingFlags pickingFlags,
208 PicksReturned returnFlag,
209 const RayPicking::RayPickFilter& filter,
210 std::vector<RayPicking::RayPickHit>& hits)
211{
212 const CameraData& cameraData = context->cameraSystem->getData(&camera);
213 const float dpiScale = context->getDefaultView()->dpiService->getScaleFactor();
214
215 bool hitSomething = false;
216 for (const MarkerPointSetComponent& comp : system->pool) {
217 if (filter.isUnwantedType(comp)) {
218 continue;
219 }
220
221 const SceneComponent* sceneComp = comp.getComponent<SceneComponent>();
222 if (sceneComp && (!sceneComp->visible || !sceneComp->pickable)) {
223 continue;
224 }
225
226 const RenderComponent* renderComp = comp.getComponent<RenderComponent>();
227 if (renderComp == nullptr || !renderComp->isVisibleInLayer(filter.layerMask)) {
228 continue;
229 }
230
231 const MarkerPointSetData& data = system->getData(&comp);
232
233 const TransformComponent* transformComp = comp.getComponent<TransformComponent>();
234 const glm::mat4 worldFromLocal = context->transformSystem->getLocalToWorld(transformComp);
235 const glm::mat4 clipFromLocal = cameraData.rawViewProjection * worldFromLocal;
236 const glm::mat4 localFromClip = glm::inverse(clipFromLocal);
237 const glm::vec2 sigma = glm::vec2(2.f * rayPixelRadius) / cameraData.viewportSize;
238 const glm::vec3 rayOriginLocal = euclidean(localFromClip * glm::vec4(queryClip, -1.f, 1.f));
239 const glm::vec3 rayOriginFarLocal = euclidean(localFromClip * glm::vec4(queryClip, 1.f, 1.f));
240 const glm::vec3 rayDirLocal = rayOriginFarLocal - rayOriginLocal;
241 const glm::vec3 rayOriginSigmaLocal = euclidean(localFromClip * glm::vec4(queryClip + sigma, -1.f, 1.f));
242 const glm::vec3 rayOriginSigmaFarLocal = euclidean(localFromClip * glm::vec4(queryClip + sigma, 1.f, 1.f));
243 const float rayNearSpread = glm::distance(rayOriginLocal, rayOriginSigmaLocal);
244 const float rayFarSpread = glm::distance(rayOriginFarLocal, rayOriginSigmaFarLocal);
245 const float rayLengthRcp = 1.f / glm::length(rayDirLocal);
246 const float rayRadiusBiasLocal = rayNearSpread;
247 const float rayRadiusSlopeLocal = rayLengthRcp * (rayFarSpread - rayNearSpread);
248 const glm::vec3 rayUnitDirLocal = rayLengthRcp * rayDirLocal;
249 const float worldFromLocalScale = glm::determinant(glm::mat3(worldFromLocal));
250
251 // The min below is to work-around local scale becoming infinity if raylength is max_float.
252 const float maxDistanceLocal = std::min(std::numeric_limits<float>::max(), 2.f * worldFromLocalScale * rayLength);
253
254 RecordHit recordHit{
255 .hits = hits,
256 .comp = comp,
257 .ordinal = filter.getOrdinal(*renderComp),
258 .renderComp = *renderComp,
259 .worldFromLocal = worldFromLocal,
260 .rayFromLocalScale = worldFromLocalScale,
261 .returnClosest = returnFlag == PicksReturned::Closest,
262 .returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity
263 };
264
265 switch (comp.shape) {
267 pickViewAlignedDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
268 break;
269
271 pickObjectSpaceFloorDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
272 break;
273
275 pickFixedScreenSizeDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, clipFromLocal, queryClip, cameraData.viewportSize, rayPixelRadius, maxDistanceLocal, dpiScale);
276 break;
277
278 default:
279 assert(false && "Illegal enum value");
280 }
281
282 hitSomething |= recordHit.hitSomething;
283 }
284
285 return hitSomething;
286}
287
289 const glm::vec3& startPos,
290 const glm::quat& rot,
291 float rayLength,
292 float rayRadius,
293 PickingFlags pickingFlags,
294 PicksReturned returnFlag,
295 const RayPicking::RayPickFilter& filter,
296 std::vector<RayPicking::RayPickHit>& hits)
297{
298 glm::mat4 worldFromRay = glm::mat4(1.f, 0.f, 0.f, 0.f,
299 0.f, 1.f, 0.f, 0.f,
300 0.f, 0.f, 1.f, 0.f,
301 startPos.x, startPos.y, startPos.z, 1.f) * glm::toMat4(rot);
302
303 bool hitSomething = false;
304 for (const MarkerPointSetComponent& comp : system->pool) {
305 if (filter.isUnwantedType(comp)) {
306 continue;
307 }
308
309 const SceneComponent* sceneComp = comp.getComponent<SceneComponent>();
310 if (sceneComp && (!sceneComp->visible || !sceneComp->pickable)) {
311 continue;
312 }
313
314 const RenderComponent* renderComp = comp.getComponent<RenderComponent>();
315 if (renderComp && !renderComp->isVisibleInLayer(filter.layerMask)) {
316 continue;
317 }
318
319 const MarkerPointSetData& data = system->getData(&comp);
320
321 const TransformComponent* transformComp = comp.getComponent<TransformComponent>();
322 const glm::mat4 worldFromLocal = context->transformSystem->getLocalToWorld(transformComp);
323 const glm::mat4 localFromWorld = glm::inverse(worldFromLocal);
324 const float worldFromLocalScale = glm::determinant(glm::mat3(worldFromLocal));
325
326 const glm::vec3 rayOriginLocal = euclidean(localFromWorld * (worldFromRay * glm::vec4(0, 0, 0, 1)));
327 const glm::vec3 rayOriginFarLocal = euclidean(localFromWorld * (worldFromRay * glm::vec4(0, 0, -1, 1)));
328 const glm::vec3 rayUnitDirLocal = normalize(rayOriginFarLocal - rayOriginLocal);
329 const float rayRadiusBiasLocal = rayRadius / worldFromLocalScale;
330 const float rayRadiusSlopeLocal = 0.f;
331
332 // The min below is to work-around local scale becoming infinity if raylength is max_float.
333 const float maxDistanceLocal = std::min(std::numeric_limits<float>::max(), 2.f * worldFromLocalScale * rayLength);
334
335 RecordHit recordHit{
336 .hits = hits,
337 .comp = comp,
338 .ordinal = filter.getOrdinal(*renderComp),
339 .renderComp = *renderComp,
340 .worldFromLocal = worldFromLocal,
341 .rayFromLocalScale = worldFromLocalScale,
342 .returnClosest = returnFlag == PicksReturned::Closest,
343 .returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity
344 };
345
346 switch (comp.shape) {
348 pickViewAlignedDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
349 break;
350
352 pickObjectSpaceFloorDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
353 break;
354
356 break;
357
358 default:
359 assert(false && "Illegal enum value");
360 }
361
362 hitSomething |= recordHit.hitSomething;
363}
364
365 return hitSomething;
366}
ComponentType * getComponent() const
Definition: Component.h:159
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
! Generate a set of points for the given position array.
float farDistance
Don't display points further than this distance from the camera.
MarkerPointSetShape shape
Specify the shape of the points.
float nearDistance
Don't display points closer than this distance from the camera.
std::vector< glm::vec3 > positions
Marker points positions.
Base component for all rendering content.
constexpr bool isVisibleInLayer(RenderLayers layerMask) const
Check if the entity should be visible in the given layer mask.
Contains information on how the entity behaves in the scene.
bool visible
If the entity this component is a member of should be visible.
bool pickable
If the entity this component is a member of should be pickable.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
std::unique_ptr< class DPIService > dpiService
DPI service instance.
Definition: ViewContext.h:71
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.
PickingFlags
Options for COGS picking.
Definition: PickingFlags.h:12
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
@ ViewAlignedDisc
Point is a flat disc aligned with the main camera view.
@ ObjectSpaceFloorDisc
Point is a flat disc on the XY-plane, point size is object-space diameter of disc.
@ FixedScreenSizeDisc
Point is a screen-space disc, point size is screen-space diameter of disc, won't work with multiple c...
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
Definition: CameraSystem.h:67
bool pickCamera(Context *context, const CameraComponent &camera, const glm::vec2 &queryClip, float, float rayRadius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
bool pickRay(Context *context, const glm::vec3 &startPos, const glm::quat &rot, float rayLength, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
Do a ray pick from a position and orientation in world space and return all hits.
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