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 const glm::mat4& worldFromLocal;
30 const float rayFromLocalScale;
31 bool returnClosest;
32 bool returnChildEntity;
33 bool hitSomething = false;
34
35 void operator()(const glm::vec4& posLocal, const float distanceLocal, size_t index) {
36 glm::vec4 posWorld = worldFromLocal * posLocal;
37 const float distanceWorld = rayFromLocalScale * distanceLocal;
38 if (returnClosest && !hits.empty()) {
39 if (distanceWorld < hits[0].distance) {
40 hits[0] = { comp, returnChildEntity, glm::vec3(posWorld), distanceWorld, glm::vec2(static_cast<float>(index)) };
41 hitSomething = true;
42 }
43 // else, the intersection we found is further, so don't do anything
44 }
45 else {
46 hits.emplace_back(comp, returnChildEntity, glm::vec3(posWorld), distanceWorld, glm::vec2(static_cast<float>(index)));
47 hitSomething = true;
48 }
49 }
50
51 };
52
53 void pickFixedScreenSizeDisc(RecordHit& recordHit,
54 const MarkerPointSetComponent& comp,
55 const MarkerPointSetData& data,
56 const glm::vec3& rayOriginLocal,
57 const glm::vec3& rayUnitDirectionLocal,
58 const glm::mat4& clipFromLocal,
59 const glm::vec2& queryClip,
60 const glm::vec2 viewportSize,
61 const float rayPixelRadius,
62 const float maxDistanceLocal,
63 const float dpiScale)
64 {
65 const float nearDistance = comp.nearDistance;
66 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
67 const float radius = 0.5f * comp.pointSize;
68 const glm::vec4 viewPlaneLocal = data.viewPlaneLocal;
69 const glm::vec2 pickPosView = queryClip * viewportSize;
70
71 for (size_t i = 0; i < comp.positions.size(); i++) {
72 const glm::vec4 posLocal(comp.positions[i], 1.f);
73 float viewZ = glm::dot(viewPlaneLocal, posLocal);
74
75 const glm::vec3 mainCamViewDirLocal(data.viewPlaneLocal);
76 const float rcp = -1.f / glm::dot(mainCamViewDirLocal, rayUnitDirectionLocal);
77 const float mp = glm::dot(mainCamViewDirLocal, rayOriginLocal);
78 if (nearDistance <= viewZ && viewZ <= farDistance) {
79 glm::vec3 pointPosClipSpace = euclidean(clipFromLocal * posLocal);
80 glm::vec2 pointPosView = glm::vec2(pointPosClipSpace) * viewportSize;
81 const float t = rcp * (mp - glm::dot(mainCamViewDirLocal, glm::vec3(posLocal)));
82 float pickableRadius = 2.f * (radius + rayPixelRadius) * dpiScale; // factor 2 is because we multiply clip pos ([-1,1]) not normed pos ([0,1]).
83 if (glm::distance2(pickPosView, pointPosView) <= pickableRadius * pickableRadius) {
84 recordHit(posLocal, t, i);
85 }
86 }
87 }
88
89 }
90
91 void pickViewAlignedDisc(RecordHit& recordHit,
92 const MarkerPointSetComponent& comp,
93 const MarkerPointSetData& data,
94 const glm::vec3& rayOriginLocal,
95 const glm::vec3& rayUnitDirectionLocal,
96 const float rayRadiusBiasLocal,
97 const float rayRadiusSlopeLocal,
98 const float maxDistanceLocal)
99 {
100 const float nearDistance = comp.nearDistance;
101 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
102 const float radius = 0.5f * comp.pointSize + rayRadiusBiasLocal;
103 const glm::vec3 mainCamViewDirLocal(data.viewPlaneLocal);
104 const float rcp = -1.f / glm::dot(mainCamViewDirLocal, rayUnitDirectionLocal);
105 const float mp = glm::dot(mainCamViewDirLocal, rayOriginLocal);
106 if (std::isfinite(rcp)) {
107 for (size_t i = 0; i < comp.positions.size(); i++) {
108 const glm::vec4 posLocal(comp.positions[i], 1.f);
109 float viewZ = glm::dot(data.viewPlaneLocal, posLocal);
110 if (nearDistance <= viewZ && viewZ <= farDistance) {
111 const float t = rcp * (mp - glm::dot(mainCamViewDirLocal, glm::vec3(posLocal)));
112 glm::vec3 r = (rayOriginLocal - glm::vec3(posLocal)) + t * rayUnitDirectionLocal;
113 const float adjustedRadius = radius + t * rayRadiusSlopeLocal;
114 if (dot(r, r) <= adjustedRadius * adjustedRadius) {
115 recordHit(posLocal, t, i);
116 }
117 }
118 }
119 }
120 }
121
122 void pickObjectSpaceFloorDisc(RecordHit& recordHit,
123 const MarkerPointSetComponent& comp,
124 const MarkerPointSetData& data,
125 const glm::vec3& rayOriginLocal,
126 const glm::vec3& rayUnitDirectionLocal,
127 const float rayRadiusBiasLocal,
128 const float rayRadiusSlopeLocal,
129 const float maxDistanceLocal)
130 {
131 const float nearDistance = comp.nearDistance;
132 const float farDistance = std::min(comp.farDistance, maxDistanceLocal);
133 const float pointRadius = 0.5f * comp.pointSize;
134 const float rcp = -1.f / rayUnitDirectionLocal.z;
135 if (std::isfinite(rcp)) {
136 for (size_t i = 0; i < comp.positions.size(); i++) {
137 const glm::vec4 posLocal(comp.positions[i], 1.f);
138 float viewZ = glm::dot(data.viewPlaneLocal, posLocal);
139 if (nearDistance <= viewZ && viewZ <= farDistance) {
140
141 // Calculate ray radius at point
142 const glm::vec3 o3 = rayOriginLocal - glm::vec3(posLocal);
143 const float rayRadiusAtPoint = rayRadiusBiasLocal + rayRadiusSlopeLocal * glm::dot(rayUnitDirectionLocal, -o3);
144 const float testRadius = pointRadius + rayRadiusAtPoint;
145
146 if (rayRadiusAtPoint < 0.1f * pointRadius) {
147 // If ray radius is really small or zero, intersect directly with disc
148
149 const float t = rcp * (rayOriginLocal.z - posLocal.z);
150 const glm::vec2 r = (glm::vec2(rayOriginLocal) - glm::vec2(posLocal)) + t * glm::vec2(rayUnitDirectionLocal);
151
152 if (dot(r, r) <= testRadius * testRadius) {
153 recordHit(posLocal, t, i);
154 }
155 }
156 else {
157
158 // Otherwise we extrude the disc to a capped cylinder to make it easier to hit gracing discs.
159 const glm::vec2 r(rayUnitDirectionLocal);
160 const glm::vec2 o = glm::vec2(rayOriginLocal) - glm::vec2(posLocal);
161
162 // Intersect with infinite cylinder
163 const float a = glm::dot(r, r);
164 const float b = 2.f * glm::dot(o, r);
165 const float c = glm::dot(o, o) - testRadius * testRadius;
166 const float desc = b * b - 4.f * a * c;
167 if (desc < 0.f) {
168 continue; // No solutions
169 }
170
171 // We have a pair of solutions
172 const float r0 = (-b - std::copysign(std::sqrt(desc), b)) / (2.f * a);
173 const float r1 = c / (a * r0);
174 float t0 = std::min(r0, r1);
175 float t1 = std::max(r0, r1);
176
177 // Intersect with two planes that cap the infinite cylinder
178 if (float p_rcp = 1.f / rayUnitDirectionLocal.z; std::isfinite(p_rcp)) {
179 float p_r0 = -p_rcp * (o3.z - rayRadiusAtPoint);
180 float p_r1 = -p_rcp * (o3.z + rayRadiusAtPoint);
181 t0 = std::max(t0, std::min(p_r0, p_r1));
182 t1 = std::min(t1, std::max(p_r0, p_r1));
183 }
184 else if (o3.z < -rayRadiusAtPoint || rayRadiusAtPoint < o3.z) {
185 // Ray dir lies in cap plane and ray origin is outside of slab
186 continue;
187 }
188 if (t0 < t1) {
189 recordHit(posLocal, t0, i);
190 }
191 }
192 }
193 }
194 }
195 }
196
197}
198
199
201 const CameraComponent& camera,
202 const glm::vec2& queryClip,
203 float rayLength,
204 float rayPixelRadius,
205 PickingFlags pickingFlags,
206 PicksReturned returnFlag,
207 const RayPicking::RayPickFilter& filter,
208 std::vector<RayPicking::RayPickHit>& hits)
209{
210 const CameraData& cameraData = context->cameraSystem->getData(&camera);
211 const float dpiScale = context->getDefaultView()->dpiService->getScaleFactor();
212
213 bool hitSomething = false;
214 for (const MarkerPointSetComponent& comp : system->pool) {
215 if (filter.isUnwantedType(comp)) {
216 continue;
217 }
218
219 const SceneComponent* sceneComp = comp.getComponent<SceneComponent>();
220 if (sceneComp && (!sceneComp->visible || !sceneComp->pickable)) {
221 continue;
222 }
223
224 const RenderComponent* renderComp = comp.getComponent<RenderComponent>();
225 if (renderComp && !renderComp->isVisibleInLayer(filter.layerMask)) {
226 continue;
227 }
228
229 const MarkerPointSetData& data = system->getData(&comp);
230
231 const TransformComponent* transformComp = comp.getComponent<TransformComponent>();
232 const glm::mat4 worldFromLocal = context->transformSystem->getLocalToWorld(transformComp);
233 const glm::mat4 clipFromLocal = cameraData.rawViewProjection * worldFromLocal;
234 const glm::mat4 localFromClip = glm::inverse(clipFromLocal);
235 const glm::vec2 sigma = glm::vec2(2.f * rayPixelRadius) / cameraData.viewportSize;
236 const glm::vec3 rayOriginLocal = euclidean(localFromClip * glm::vec4(queryClip, -1.f, 1.f));
237 const glm::vec3 rayOriginFarLocal = euclidean(localFromClip * glm::vec4(queryClip, 1.f, 1.f));
238 const glm::vec3 rayDirLocal = rayOriginFarLocal - rayOriginLocal;
239 const glm::vec3 rayOriginSigmaLocal = euclidean(localFromClip * glm::vec4(queryClip + sigma, -1.f, 1.f));
240 const glm::vec3 rayOriginSigmaFarLocal = euclidean(localFromClip * glm::vec4(queryClip + sigma, 1.f, 1.f));
241 const float rayNearSpread = glm::distance(rayOriginLocal, rayOriginSigmaLocal);
242 const float rayFarSpread = glm::distance(rayOriginFarLocal, rayOriginSigmaFarLocal);
243 const float rayLengthRcp = 1.f / glm::length(rayDirLocal);
244 const float rayRadiusBiasLocal = rayNearSpread;
245 const float rayRadiusSlopeLocal = rayLengthRcp * (rayFarSpread - rayNearSpread);
246 const glm::vec3 rayUnitDirLocal = rayLengthRcp * rayDirLocal;
247 const float worldFromLocalScale = glm::determinant(glm::mat3(worldFromLocal));
248
249 // The min below is to work-around local scale becoming infinity if raylength is max_float.
250 const float maxDistanceLocal = std::min(std::numeric_limits<float>::max(), 2.f * worldFromLocalScale * rayLength);
251
252 RecordHit recordHit{
253 .hits = hits,
254 .comp = comp,
255 .worldFromLocal = worldFromLocal,
256 .rayFromLocalScale = worldFromLocalScale,
257 .returnClosest = returnFlag == PicksReturned::Closest,
258 .returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity
259 };
260
261 switch (comp.shape) {
263 pickViewAlignedDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
264 break;
265
267 pickObjectSpaceFloorDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
268 break;
269
271 pickFixedScreenSizeDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, clipFromLocal, queryClip, cameraData.viewportSize, rayPixelRadius, maxDistanceLocal, dpiScale);
272 break;
273
274 default:
275 assert(false && "Illegal enum value");
276 }
277
278 hitSomething |= recordHit.hitSomething;
279 }
280
281 if (returnFlag == PicksReturned::AllSorted) {
282 std::sort(hits.begin(), hits.end());
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 .worldFromLocal = worldFromLocal,
339 .rayFromLocalScale = worldFromLocalScale,
340 .returnClosest = returnFlag == PicksReturned::Closest,
341 .returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity
342 };
343
344 switch (comp.shape) {
346 pickViewAlignedDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
347 break;
348
350 pickObjectSpaceFloorDisc(recordHit, comp, data, rayOriginLocal, rayUnitDirLocal, rayRadiusBiasLocal, rayRadiusSlopeLocal, maxDistanceLocal);
351 break;
352
354 break;
355
356 default:
357 assert(false && "Illegal enum value");
358 }
359
360 hitSomething |= recordHit.hitSomething;
361}
362
363 if (returnFlag == PicksReturned::AllSorted) {
364 std::sort(hits.begin(), hits.end());
365 }
366
367 return hitSomething;
368}
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: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.
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:180
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:187
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:70