Cogs.Core
PotreePicker.cpp
1#include "Services/Variables.h"
2#include "Systems/Core/CameraSystem.h"
3#include "Systems/Core/TransformSystem.h"
4#include "Utilities/FrustumClassification.h"
5#include "Context.h"
6
7#include "PotreeSystem.h"
8
9#include "Foundation/Logging/Logger.h"
10
11namespace {
12
13 Cogs::Logging::Log logger = Cogs::Logging::getLogger("PotreePicker");
14
15 using namespace Cogs::Core;
16
17 unsigned popcount(uint8_t x)
18 {
19 x = (x & 0x55u) + ((x >> 1) & 0x55u);
20 x = (x & 0x33u) + ((x >> 2) & 0x33u);
21 x = (x & 0x0Fu) + ((x >> 4) & 0x0Fu);
22 return x;
23 }
24
25 glm::vec3 euclidean(const glm::vec4& h)
26 {
27 return (1.f / h.w) * glm::vec3(h);
28 }
29
30 bool isBBoxInFrustum(const glm::mat4& pickFromLocal,
31 const glm::mat4& localFromPick,
32 const glm::vec3& min,
33 const glm::vec3& max)
34 {
35 unsigned mask = 0;
36 for (unsigned i = 0; i < 8; i++) {
37 glm::vec4 p = pickFromLocal * glm::vec4((i & 1) ? min.x : max.x,
38 (i & 2) ? min.y : max.y,
39 (i & 4) ? min.z : max.z,
40 1.f);
41 if (-p.w <= p.x) mask |= 0b000001;
42 if (-p.w <= p.y) mask |= 0b000010;
43 if (-p.w <= p.z) mask |= 0b000100;
44 if (+p.x <= p.w) mask |= 0b001000;
45 if (+p.y <= p.w) mask |= 0b010000;
46 if (+p.z <= p.w) mask |= 0b100000;
47 }
48 if (mask != 0b111111) return false;
49
50 // Inverse test as well to get even tighter estimate
51 mask = 0;
52 for (unsigned i = 0; i < 8; i++) {
53 glm::vec3 p = euclidean(localFromPick * glm::vec4((i & 1) ? -1.f : 1.f,
54 (i & 2) ? -1.f : 1.f,
55 (i & 4) ? -1.f : 1.f,
56 1.f));
57 if (min.x <= p.x) mask |= 0b000001;
58 if (min.y <= p.y) mask |= 0b000010;
59 if (min.z <= p.z) mask |= 0b000100;
60 if (p.x <= max.x) mask |= 0b001000;
61 if (p.y <= max.y) mask |= 0b010000;
62 if (p.z <= max.z) mask |= 0b100000;
63 }
64 return mask == 0b111111;
65 }
66
67 inline const glm::mat4 createPickMatrix(const CameraData& camData, const glm::vec2& pixelSize, const glm::vec2& query_pos_norm)
68 {
69 // Create a frustum around ray of size maxpointsize
70 const float sx = camData.viewportSize.x / pixelSize.x;
71 const float sy = camData.viewportSize.y / pixelSize.y;
72 return glm::mat4(sx, 0, 0, 0,
73 0, sy, 0, 0,
74 0, 0, 1, 0,
75 -sx * query_pos_norm.x, -sy * query_pos_norm.y, 0, 1);
76 }
77
78 // Create a ortho matrix that is sized to match a viewspace radius plus a guard band
79 inline const glm::mat4 createOrthoPickMatrix(const CameraData& camData, const float guard_norm, const float viewSpaceRadius, const glm::vec2& query_norm)
80 {
81 const glm::mat4 inverseRawProjectionMatrix = glm::inverse(camData.rawProjectionMatrix);
82 glm::vec3 viewCenterA = euclidean(inverseRawProjectionMatrix * glm::vec4(query_norm, -1.f, 1.f));
83 glm::vec3 viewCenterB = euclidean(inverseRawProjectionMatrix * glm::vec4(query_norm, 1.f, 1.f));
84
85 // Calculate size of guard band at front and back planes
86 glm::vec3 viewCenterA_guard = euclidean(inverseRawProjectionMatrix * glm::vec4(query_norm + guard_norm, -1.f, 1.f));
87 glm::vec3 viewCenterB_guard = euclidean(inverseRawProjectionMatrix * glm::vec4(query_norm + guard_norm, 1.f, 1.f));
88
89 const float g_n = viewCenterA_guard.x - viewCenterA.x; // extra guard at near plane
90 const float g_f = viewCenterB_guard.x - viewCenterB.x; // additional guard at far plane
91 const float r = viewSpaceRadius + g_n;
92 const float s = g_f - g_n;
93 const float d = -(viewCenterB.z - viewCenterA.z);
94 //const float a = (2.f * r + s) / d; //unused param
95
96
97 // Shear x and y so center line aligns with perspective direction
98 float shearX = -(viewCenterB.x - viewCenterA.x) / (viewCenterB.z - viewCenterA.z);
99 float shearY = -(viewCenterB.y - viewCenterA.y) / (viewCenterB.z - viewCenterA.z);
100 glm::mat4 orthoPickFromViewFrame =
101
102 // Orthogonal projection
103 glm::mat4(1.f, 0.f, 0.f, 0.f,
104 0.f, 1.f, 0.f, 0.f,
105 0.f, 0.f, -(s + 2.f * r) / d, -s / d,
106 0.f, 0.f, -r, r) *
107
108 // Shear
109 glm::mat4(1.f, 0.f, 0.f, 0.f,
110 0.f, 1.f, 0.f, 0.f,
111 shearX, shearY, 1.f, 0.f,
112 0.f, 0.f, 0.f, 1.f) *
113
114 // Translate so viewCenterA is at the origin
115 glm::mat4(1.f, 0.f, 0.f, 0.f,
116 0.f, 1.f, 0.f, 0.f,
117 0.f, 0.f, 1.f, 0.f,
118 -viewCenterA.x, -viewCenterA.y, -viewCenterA.z, 1.f);
119
120 return orthoPickFromViewFrame;
121 }
122
123 bool rayPickFixedSize(PotreeData* poData, const CameraData& camData,
124 const glm::mat4& viewFromOcttreeFrame,
125 const glm::vec2& query_norm,
126 const float guard_px,
127 const float viewspaceRadius,
128 float& viewspaceDepthBest, glm::vec3& worldspacePosBest, bool roundPoint, bool depthPoint)
129 {
130 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
131 bool anyHits = false;
132
133 // Perspective matrix that limits point size to max size
134 // this is in screen space, and thus we need a perspective matrix
135 glm::mat4 perspPickFromOcttreeFrame = (createPickMatrix(camData, glm::vec2(poData->pointSize.val) + guard_px, query_norm) *
136 camData.rawProjectionMatrix * viewFromOcttreeFrame);
137 glm::mat4 octtreeFrameFromPerspPick = glm::inverse(perspPickFromOcttreeFrame);
138
139 float viewspace_nearest = viewspaceDepthBest;
140
141 if (isBBoxInFrustum(perspPickFromOcttreeFrame,
142 octtreeFrameFromPerspPick,
143 poData->octtreeFrame.tightBBoxMin,
144 poData->octtreeFrame.tightBBoxMax))
145 {
146
147 // Run through all active cells
148 for (auto* cell : poData->activeCells) {
149 if (isBBoxInFrustum(perspPickFromOcttreeFrame,
150 octtreeFrameFromPerspPick,
151 cell->tbmin,
152 cell->tbmax))
153 {
154 bool hit = false;
155 size_t indexBest = 0;
156 const glm::vec3* points = cell->points.data();
157 const size_t points_N = cell->pointCount;
158 for (size_t i = 0; i < points_N; i++) {
159 const glm::vec3 p = points[i];
160
161
162 // Check against max point size-frustum
163 glm::vec4 maxSizePointCoord_h = (perspPickFromOcttreeFrame[0] * p.x +
164 perspPickFromOcttreeFrame[1] * p.y +
165 perspPickFromOcttreeFrame[2] * p.z +
166 perspPickFromOcttreeFrame[3]);
167 if ((maxSizePointCoord_h.x <= maxSizePointCoord_h.w) && (-maxSizePointCoord_h.w <= maxSizePointCoord_h.x) &&
168 (maxSizePointCoord_h.y <= maxSizePointCoord_h.w) && (-maxSizePointCoord_h.w <= maxSizePointCoord_h.y))
169 {
170 glm::vec3 maxSizePointCoord = (1.f / maxSizePointCoord_h.w) * glm::vec3(maxSizePointCoord_h);
171 float maxSizeRadiusSquared = maxSizePointCoord.x * maxSizePointCoord.x + maxSizePointCoord.y * maxSizePointCoord.y;
172 if (roundPoint && (1.f < maxSizeRadiusSquared)) continue;
173
174 // view space position of point
175 glm::vec4 viewSpacePos_h = (viewFromOcttreeFrame[0] * p.x +
176 viewFromOcttreeFrame[1] * p.y +
177 viewFromOcttreeFrame[2] * p.z +
178 viewFromOcttreeFrame[3]);
179 float viewspace_depth = -viewSpacePos_h.z / viewSpacePos_h.w; // flip sign due to looking along -Z.
180
181 if (depthPoint) {
182
183 float r = 0.f;
184 if (roundPoint) {
185 r = std::sqrt(maxSizeRadiusSquared);
186 }
187 else {
188 r = std::max(std::abs(maxSizePointCoord.x),
189 std::abs(maxSizePointCoord.y));
190 }
191
192
193 viewspace_depth += viewspaceRadius * r;
194 }
195
196 if (viewspace_nearest < viewspace_depth) continue;
197
198 viewspace_nearest = viewspace_depth;
199 hit = true;
200 indexBest = i;
201 }
202 if (hit) {
203 worldspacePosBest = euclidean(worldFromOcttreeFrame * glm::vec4(cell->points[indexBest], 1.f));
204 viewspaceDepthBest = viewspace_nearest;
205 anyHits = true;
206 }
207 }
208 }
209 }
210 }
211 return anyHits;
212 }
213
214
215 float getDensityScale(const PotreeData* poData, const glm::vec3& p)
216 {
217 const glm::vec3 invScale = glm::vec3(1.f) / (poData->octtreeFrame.fullBBoxMax - poData->octtreeFrame.fullBBoxMin);
218 const glm::vec3 invShift = 0.5f * (poData->octtreeFrame.tightBBoxMax - poData->octtreeFrame.tightBBoxMin);
219
220 glm::vec3 unitPos = glm::clamp(invScale * (p + invShift), glm::vec3(0.f), glm::vec3(1.f));
221
222 size_t offset = 0;
223 float scale = 1.f;
224 for (size_t l = 0; l < 30; l++) {
225 glm::vec3 t = 2.f * unitPos;
226 unitPos = fract(t);
227
228 size_t childIndex = ((1.0 <= t.x ? 4 : 0) +
229 (1.0 <= t.y ? 2 : 0) +
230 (1.0 <= t.z ? 1 : 0));
231 size_t childBit = 1 << childIndex;
232
233 glm::u8vec4 data = poData->octtreeTextureData[offset];
234 uint8_t childrenBitmask = data.r;
235
236 if ((childrenBitmask & childBit) == 0) {
237 // Scale value as the texture sampler returns it
238 float factor = (1.f / 255.f) * data.w;
239 // Unpack formula
240 float decoded = poData->densitySized.bias - ((poData->densitySized.scale * factor) + poData->densitySized.levelScale * float(l));
241 // Note: inverse exponent on purpose to replace division with multiplication further down
242 scale = glm::exp2(-glm::min(0.f, decoded));
243 break;
244 }
245 else {
246 size_t childrenOffset = (static_cast<size_t>(data.b) << 8) + data.g;
247 uint8_t lesserChildren = childrenBitmask & (childBit - 1u);
248 size_t childrenBeforeSelectedChild = popcount(lesserChildren);
249 offset = childrenOffset + childrenBeforeSelectedChild;
250 assert(offset < poData->octtreeTextureData.size());
251 }
252 }
253 return scale;
254 }
255
256 bool rayPickDistanceScaled(PotreeData* poData, const CameraData& camData,
257 const glm::mat4& viewFromOcttreeFrame,
258 const glm::vec2& query_norm,
259 const float guard_px,
260 const float guard_norm,
261 const float viewSpaceRadius,
262 float& viewspaceDepthBest, glm::vec3& worldspacePosBest, bool roundPoint, bool depthPoint, bool densityScale, bool hasViewport)
263 {
264 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
265 const float modelSpaceRadius = poData->modelSpaceRadius;
266
267 bool anyHits = false;
268
269 // Create an orthographic projection aligned with the perspective direction
270 // of the query position, sized by the view space radius of points
271 glm::mat4 orthoPickFromOcttreeFrame;
272 glm::mat4 octtreeFrameFromOrthoPick;
273
274 // Find start and end point for ray in view space.
275 glm::mat4 perspPickFromOcttreeFrame; // only referenced if hasViewport is true
276 glm::mat4 octtreeFrameFromPerspPick;
277 if(hasViewport) {
278 orthoPickFromOcttreeFrame = createOrthoPickMatrix(camData, guard_norm, viewSpaceRadius, query_norm) * viewFromOcttreeFrame;
279 octtreeFrameFromOrthoPick = glm::inverse(orthoPickFromOcttreeFrame);
280
281 // Perspective matrix that limits point size to max size
282 // this is in screen space, and thus we need a perspective matrix
283 perspPickFromOcttreeFrame =
284 createPickMatrix(camData, glm::vec2(poData->pointSize.max) + guard_px, query_norm) *
285 camData.rawProjectionMatrix * viewFromOcttreeFrame;
286 octtreeFrameFromPerspPick = glm::inverse(perspPickFromOcttreeFrame);
287
288 }
289 else {
290 orthoPickFromOcttreeFrame = camData.rawProjectionMatrix * viewFromOcttreeFrame;
291 octtreeFrameFromOrthoPick = glm::inverse(orthoPickFromOcttreeFrame);
292 }
293
294 // Check if bounding box of full point cloud intersects pick frustum
295 float viewspace_nearest = viewspaceDepthBest;
296 if (!isBBoxInFrustum(orthoPickFromOcttreeFrame,
297 octtreeFrameFromOrthoPick,
298 poData->octtreeFrame.tightBBoxMin - modelSpaceRadius,
299 poData->octtreeFrame.tightBBoxMax + modelSpaceRadius))
300 {
301 return false;
302 }
303
304 // Run through all active cells
305 for (auto* cell : poData->activeCells) {
306
307 // Perspective early-out-test is important when we are close to or inside the point cloud
308 // as this clamps the maximum point size, which will happen a lot in that case. This test
309 // only works with a viewport.
310 if ((!hasViewport || isBBoxInFrustum(perspPickFromOcttreeFrame, // Max-size test
311 octtreeFrameFromPerspPick,
312 cell->tbmin,
313 cell->tbmax)) &&
314 isBBoxInFrustum(orthoPickFromOcttreeFrame, // view-space-size test
315 octtreeFrameFromOrthoPick,
316 cell->tbmin - modelSpaceRadius,
317 cell->tbmax + modelSpaceRadius))
318 {
319 bool hit = false;
320 size_t indexBest = 0;
321 const glm::vec3* points = cell->points.data();
322 const size_t points_N = cell->pointCount;
323 for (size_t i = 0; i < points_N; i++) {
324 const glm::vec3 p = points[i];
325
326 // check against world-space point size frustum
327 glm::vec4 maxSizePointCoord_h;
328 if (hasViewport) {
329 maxSizePointCoord_h = (perspPickFromOcttreeFrame[0] * p.x +
330 perspPickFromOcttreeFrame[1] * p.y +
331 perspPickFromOcttreeFrame[2] * p.z +
332 perspPickFromOcttreeFrame[3]);
333 if ((maxSizePointCoord_h.w < maxSizePointCoord_h.x) || (maxSizePointCoord_h.x < -maxSizePointCoord_h.w) ||
334 (maxSizePointCoord_h.w < maxSizePointCoord_h.y) || (maxSizePointCoord_h.y < -maxSizePointCoord_h.w))
335 {
336 continue;
337 }
338 }
339 glm::vec4 worldSizePointCoord_h = (orthoPickFromOcttreeFrame[0] * p.x +
340 orthoPickFromOcttreeFrame[1] * p.y +
341 orthoPickFromOcttreeFrame[2] * p.z +
342 orthoPickFromOcttreeFrame[3]);
343 if ((worldSizePointCoord_h.w < worldSizePointCoord_h.x) || (worldSizePointCoord_h.x < -worldSizePointCoord_h.w) ||
344 (worldSizePointCoord_h.w < worldSizePointCoord_h.y) || (worldSizePointCoord_h.y < -worldSizePointCoord_h.w) ||
345 (worldSizePointCoord_h.w < worldSizePointCoord_h.z) || (worldSizePointCoord_h.z < -worldSizePointCoord_h.w))
346 {
347 continue;
348 }
349
350 // Check against world-space round point test
351 glm::vec3 worldSizePointCoord = (1.f / worldSizePointCoord_h.w) * glm::vec3(worldSizePointCoord_h);
352 float worldSizeRadiusSquared = worldSizePointCoord.x * worldSizePointCoord.x + worldSizePointCoord.y * worldSizePointCoord.y;
353 if (roundPoint && (1.f < worldSizeRadiusSquared)) {
354 continue;
355 }
356
357 // Check against max point size-frustum, only done when we have a viewport that can define a max size
358 float maxSizeRadiusSquared = worldSizeRadiusSquared;
359 glm::vec3 maxSizePointCoord = worldSizePointCoord;
360 if (hasViewport) {
361
362 // Check against max point-size round point test
363 if (roundPoint) {
364 maxSizePointCoord = (1.f / maxSizePointCoord_h.w) * glm::vec3(maxSizePointCoord_h);
365 maxSizeRadiusSquared = maxSizePointCoord.x * maxSizePointCoord.x + maxSizePointCoord.y * maxSizePointCoord.y;
366 if (1.f < maxSizeRadiusSquared) {
367 continue;
368 }
369 }
370 }
371
372 // Scale point size if density scale is enabled
373 if (densityScale) {
374 float scale = getDensityScale(poData, p);
375 worldSizePointCoord.x *= scale;
376 worldSizePointCoord.y *= scale;
377 worldSizeRadiusSquared *= scale * scale;
378 if (roundPoint) {
379 if (1.f < worldSizeRadiusSquared) continue;
380 }
381 else {
382 if (1.f < glm::abs(worldSizePointCoord.x) || 1.f < glm::abs(worldSizePointCoord.y)) continue;
383 }
384 }
385
386 // view space position of point
387 glm::vec4 viewSpacePos_h = (viewFromOcttreeFrame[0] * p.x +
388 viewFromOcttreeFrame[1] * p.y +
389 viewFromOcttreeFrame[2] * p.z +
390 viewFromOcttreeFrame[3]);
391 float viewspace_depth = -viewSpacePos_h.z / viewSpacePos_h.w; // flip sign due to looking along -Z.
392
393 if (depthPoint) {
394 //glm::vec2 viewSpacePointCoord = (1.f / worldSizePointCoord_h.w) * glm::vec2(worldSizePointCoord_h);
395
396 float r = 0.f;
397 if (roundPoint) {
398 r = std::sqrt(std::min(worldSizeRadiusSquared, maxSizeRadiusSquared));
399 }
400 else {
401 float rA = std::max(std::abs(worldSizePointCoord.x),
402 std::abs(worldSizePointCoord.y));
403 float rB = std::max(std::abs(maxSizePointCoord.x),
404 std::abs(maxSizePointCoord.y));
405 r = std::min(rA, rB);
406 }
407
408
409 viewspace_depth += viewSpaceRadius * r;
410 }
411
412 // See if we got a better hit
413 if (viewspace_nearest < viewspace_depth) continue;
414
415 // Yes, record
416 viewspace_nearest = viewspace_depth;
417 hit = true;
418 indexBest = i;
419 }
420
421 if (hit) {
422 worldspacePosBest = euclidean(worldFromOcttreeFrame * glm::vec4(cell->points[indexBest], 1.f));
423 viewspaceDepthBest = viewspace_nearest;
424 anyHits = true;
425 }
426 }
427 }
428 return anyHits;
429 }
430
431}
432
433
435 const CameraComponent& camera,
436 const glm::vec2& normPosition,
437 float /*rayLength*/,
438 float radius,
439 PickingFlags pickingFlags,
440 PicksReturned returnFlag,
441 const RayPicking::RayPickFilter& filter,
442 std::vector<RayPicking::RayPickHit>& hits)
443{
444 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
445
446 bool hitSomething = false;
447
448 const CameraData& cameraData = context->cameraSystem->getData(&camera);
449
450 // Increase tolerance for main camera picking so you don't need to precisely hit a point
451 float tolerance_norm = 2.f * radius / cameraData.viewportSize.x;
452
453 for (const PotreeComponent& poComp : poSystem->pool) {
454 if (!poComp.supportPicking || !poComp.isPickable() || filter.isUnwantedType(poComp)) { continue; }
455
456 const RenderComponent* renderComp = poComp.getComponent<RenderComponent>();
457 if (renderComp && !renderComp->isVisibleInLayer(filter.layerMask)) { continue; }
458
459 PotreeDataHolder& poDataHolder = poSystem->getData(&poComp);
460 PotreeData* poData = poDataHolder.poData.get();
461 assert(poData);
462
463 if (poData->state != PotreeState::Running || poData->octtreeFrame.tightBBoxMax.x < poData->octtreeFrame.tightBBoxMin.x) { continue; }
464
465 bool roundPoint = false;
466 bool depthPoint = false;
467 switch (poComp.pointShape) {
469 roundPoint = false;
470 depthPoint = false;
471 break;
473 roundPoint = true;
474 depthPoint = false;
475 break;
477 roundPoint = false;
478 depthPoint = true;
479 break;
481 roundPoint = true;
482 depthPoint = true;
483 break;
484 default:
485 assert(false && "Invalid case");
486 break;
487 }
488
489 // Transform from octtree frame to world
490 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
491 const glm::mat4 viewFromOcttreeFrame = cameraData.viewMatrix * worldFromOcttreeFrame;
492 const float modelSpaceRadius = poData->modelSpaceRadius;
493 const float viewspaceRadius = std::cbrt(glm::determinant(glm::mat3(viewFromOcttreeFrame))) * modelSpaceRadius;
494
495 const Component* poCompHit = nullptr;
496 float viewspaceDepth = std::numeric_limits<float>::max();
497 glm::vec3 worldspacePos;
498
499 switch (poComp.pointSizing) {
501 if (rayPickFixedSize(poData, cameraData,
502 viewFromOcttreeFrame,
503 normPosition,
504 radius,
505 viewspaceRadius,
506 viewspaceDepth, worldspacePos, roundPoint, depthPoint))
507 {
508 poCompHit = &poComp;
509 }
510 break;
512 if (rayPickDistanceScaled(poData, cameraData,
513 viewFromOcttreeFrame,
514 normPosition,
515 radius,
516 tolerance_norm,
517 viewspaceRadius,
518 viewspaceDepth, worldspacePos, roundPoint, depthPoint, false, true))
519 {
520 poCompHit = &poComp;
521 }
522 break;
524 if (rayPickDistanceScaled(poData, cameraData,
525 viewFromOcttreeFrame,
526 normPosition,
527 radius,
528 tolerance_norm,
529 viewspaceRadius,
530 viewspaceDepth, worldspacePos, roundPoint, depthPoint, true, true))
531 {
532 poCompHit = &poComp;
533 }
534 break;
535 default:
536 assert(false && "Illegal pointsizing enum value");
537 break;
538 }
539
540 if (poCompHit) {
541 glm::vec4 viewPos = cameraData.viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
542 float viewDist = -viewPos.z;
543
544 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
545 if (viewDist < hits[0].distance) {
546 hits[0] = {*poCompHit, returnChildEntity, worldspacePos, viewDist};
547 hitSomething = true;
548 }
549 // else, the intersection we found is further, so don't do anything
550 }
551 else {
552 hits.emplace_back(*poCompHit, returnChildEntity, worldspacePos, viewDist);
553 hitSomething = true;
554 }
555 }
556 }
557
558 // Sort hits based on distance
559 if (returnFlag == PicksReturned::AllSorted) {
560 std::sort(hits.begin(), hits.end());
561 }
562 return hitSomething;
563}
564
566 const glm::vec3& startPos,
567 const glm::quat& rot,
568 float rayLength,
569 float radius,
570 PickingFlags pickingFlags,
571 PicksReturned returnFlag,
572 const RayPicking::RayPickFilter& filter,
573 std::vector<RayPicking::RayPickHit>& hits)
574{
575 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
576
577 bool hitSomething = false;
578
579 for (const PotreeComponent& poComp : poSystem->pool) {
580 if (!poComp.supportPicking || !poComp.isPickable() || filter.isUnwantedType(poComp)) { continue; }
581
582 const RenderComponent* renderComp = poComp.getComponent<RenderComponent>();
583 if (renderComp && !renderComp->isVisibleInLayer(filter.layerMask)) { continue; }
584
585 PotreeDataHolder& poDataHolder = poSystem->getData(&poComp);
586 PotreeData* poData = poDataHolder.poData.get();
587 assert(poData);
588
589 const glm::vec3& tbmin = poData->octtreeFrame.tightBBoxMin;
590 const glm::vec3& tbmax = poData->octtreeFrame.tightBBoxMax;
591 if (poData->state != PotreeState::Running || (tbmax.x < tbmin.x)) { continue; }
592
593 bool roundPoint = false;
594 bool depthPoint = false;
595 switch (poComp.pointShape) {
597 roundPoint = false;
598 depthPoint = false;
599 break;
601 roundPoint = true;
602 depthPoint = false;
603 break;
605 roundPoint = false;
606 depthPoint = true;
607 break;
609 roundPoint = true;
610 depthPoint = true;
611 break;
612 default:
613 assert(false && "Invalid case");
614 break;
615 }
616
617 // Matrix that transforms a world position into the view space
618 glm::mat4 viewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
619 //glm::quat cgate = glm::conjugate(rot);
620
621 // Transform from octtree frame to world
622 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
623 const glm::mat4 viewFromOcttreeFrame = viewMatrix * worldFromOcttreeFrame;
624
625 const float modelSpaceRadius = poData->modelSpaceRadius;
626 const float viewSpaceRadius = std::cbrt(glm::determinant(glm::mat3(viewFromOcttreeFrame))) * modelSpaceRadius;
627
628 // already simplified some operations (2/right - left) == (1/right) given that right == -left
629 // term -(f+n)/(f-n); n == 0 => -f/f = -1.
630 const float size = radius + poData->modelSpaceRadius;
631 const glm::mat4 projectionMatrix(
632 1 / size, 0, 0, 0,
633 0, 1 / size, 0, 0,
634 0, 0, -2 / rayLength, 0,
635 0, 0, -1, 1
636 );
637
638 glm::vec2 query_pos_norm(0.0f, 0.0f);
639 CameraData cameraData = CameraData();
640 cameraData.rawProjectionMatrix = projectionMatrix;
641 cameraData.viewportSize = glm::vec2(8.f);
642 float tolerance = radius;
643 float tolerance_norm = 2.f * tolerance / cameraData.viewportSize.x;
644
645 const Component* poCompHit = nullptr;
646 float viewspaceDepth = rayLength;
647 glm::vec3 worldspacePos;
648
649 switch (poComp.pointSizing) {
651 LOG_TRACE(logger, "PotreePointSizing::FixedSize is currently not supported for camera-less picking, no hits detected");
652 break;
654 if (rayPickDistanceScaled(poData, cameraData,
655 viewFromOcttreeFrame,
656 query_pos_norm,
657 tolerance,
658 tolerance_norm,
659 viewSpaceRadius,
660 viewspaceDepth, worldspacePos, roundPoint, depthPoint, false, false))
661 {
662 poCompHit = &poComp;
663 }
664 break;
666 if (rayPickDistanceScaled(poData, cameraData,
667 viewFromOcttreeFrame,
668 query_pos_norm,
669 tolerance,
670 tolerance_norm,
671 viewSpaceRadius,
672 viewspaceDepth, worldspacePos, roundPoint, depthPoint, true, false))
673 {
674 poCompHit = &poComp;
675 }
676 break;
677 default:
678 assert(false && "Illegal pointsizing enum value");
679 break;
680 }
681
682 if (poCompHit) {
683 glm::vec4 viewPos = viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
684 float viewDist = -viewPos.z;
685 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
686 if (viewDist < hits[0].distance) {
687 hits[0] = {*poCompHit, returnChildEntity, worldspacePos, viewDist};
688 hitSomething = true;
689 }
690 // else, the intersection we found is further, so don't do anything
691 }
692 else {
693 hits.emplace_back(*poCompHit, returnChildEntity, worldspacePos, viewDist);
694 hitSomething = true;
695 }
696 }
697 }
698
699 // Sort hits based on distance
700 if (returnFlag == PicksReturned::AllSorted) {
701 std::sort(hits.begin(), hits.end());
702 }
703 return hitSomething;
704}
705
Base class for Component instances.
Definition: Component.h:143
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
Base component for all rendering content.
constexpr bool isVisibleInLayer(RenderLayers layerMask) const
Check if the entity should be visible in the given layer mask.
constexpr bool isPickable() const
Check if the entity is pickable or not.
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.
@ Disc
Each point is a flat disc.
@ Sphere
Each point is a disc with depth adjusted to a spherical shape, requires support for changing fragment...
@ Square
Each point is a flat square.
@ Paraboloid
Each point is a square with depth adjusted to a parabolic shape, requires support for changing fragme...
PickingFlags
Options for COGS picking.
Definition: PickingFlags.h:12
@ ReturnChildEntity
Return ID if sub-entity picked, not set: return root parent entity.
@ DistanceAndDensityScaled
Base point size scaled by camera distance and adjusted for local point density.
@ DistanceScaled
Base point size scaled by camera distance.
@ FixedSize
Base point size with no scaling.
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
glm::mat4 rawProjectionMatrix
Projection matrix that has not been adjusted by the renderer, and is thus appropriate for direct scre...
Definition: CameraSystem.h:129
Component for Point Cloud Display.
PotreePointShape pointShape
Specify point shape.
PotreePointSizing pointSizing
Specify point sizing strategy.
bool supportPicking
If true, an extra CPU side copy of the point positions are maintained to enable CPU picking.
float val
Current dpi-scaled and min-max clamped point-size.
Definition: PotreeSystem.h:227
float max
Current dpi-scaled maximum point-size.
Definition: PotreeSystem.h:229
bool pickRay(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 pickCamera(Context *context, const CameraComponent &camera, const glm::vec2 &normPosition, float, float radius, PickingFlags pickingFlags, PicksReturned returnFlag, const RayPicking::RayPickFilter &filter, std::vector< RayPicking::RayPickHit > &hits) override
Do a ray pick from a normalized screen space position in the camera direction 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