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 == nullptr || !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 float viewspaceDepth = std::numeric_limits<float>::max();
496 glm::vec3 worldspacePos;
497
498 bool didHit = false;
499 switch (poComp.pointSizing) {
501 if (rayPickFixedSize(poData, cameraData,
502 viewFromOcttreeFrame,
503 normPosition,
504 radius,
505 viewspaceRadius,
506 viewspaceDepth, worldspacePos, roundPoint, depthPoint))
507 {
508 didHit = true;
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 didHit = true;
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 didHit = true;
533 }
534 break;
535 default:
536 assert(false && "Illegal pointsizing enum value");
537 break;
538 }
539
540 if (didHit) {
541 const RayPicking::Ordinal ordinal = filter.getOrdinal(*renderComp);
542
543 glm::vec4 viewPos = cameraData.viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
544 float viewDist = -viewPos.z;
545
546 if (returnFlag == PicksReturned::Closest && !hits.empty()) {
547 if (hits[0].isBehind(ordinal, viewDist)) {
548 hits[0] = {*renderComp, returnChildEntity, worldspacePos, ordinal, viewDist};
549 hitSomething = true;
550 }
551 // else, the intersection we found is further, so don't do anything
552 }
553 else {
554 hits.emplace_back(*renderComp, returnChildEntity, worldspacePos, ordinal, viewDist);
555 hitSomething = true;
556 }
557 }
558 }
559
560 return hitSomething;
561}
562
564 const glm::vec3& startPos,
565 const glm::quat& rot,
566 float rayLength,
567 float radius,
568 PickingFlags pickingFlags,
569 PicksReturned returnFlag,
570 const RayPicking::RayPickFilter& filter,
571 std::vector<RayPicking::RayPickHit>& hits)
572{
573 const bool returnChildEntity = (pickingFlags & PickingFlags::ReturnChildEntity) == PickingFlags::ReturnChildEntity;
574
575 bool hitSomething = false;
576
577 for (const PotreeComponent& poComp : poSystem->pool) {
578 if (!poComp.supportPicking || !poComp.isPickable() || filter.isUnwantedType(poComp)) { continue; }
579
580 const RenderComponent* renderComp = poComp.getComponent<RenderComponent>();
581 if (renderComp && !renderComp->isVisibleInLayer(filter.layerMask)) { continue; }
582
583 PotreeDataHolder& poDataHolder = poSystem->getData(&poComp);
584 PotreeData* poData = poDataHolder.poData.get();
585 assert(poData);
586
587 const glm::vec3& tbmin = poData->octtreeFrame.tightBBoxMin;
588 const glm::vec3& tbmax = poData->octtreeFrame.tightBBoxMax;
589 if (poData->state != PotreeState::Running || (tbmax.x < tbmin.x)) { continue; }
590
591 bool roundPoint = false;
592 bool depthPoint = false;
593 switch (poComp.pointShape) {
595 roundPoint = false;
596 depthPoint = false;
597 break;
599 roundPoint = true;
600 depthPoint = false;
601 break;
603 roundPoint = false;
604 depthPoint = true;
605 break;
607 roundPoint = true;
608 depthPoint = true;
609 break;
610 default:
611 assert(false && "Invalid case");
612 break;
613 }
614
615 // Matrix that transforms a world position into the view space
616 glm::mat4 viewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
617 //glm::quat cgate = glm::conjugate(rot);
618
619 // Transform from octtree frame to world
620 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
621 const glm::mat4 viewFromOcttreeFrame = viewMatrix * worldFromOcttreeFrame;
622
623 const float modelSpaceRadius = poData->modelSpaceRadius;
624 const float viewSpaceRadius = std::cbrt(glm::determinant(glm::mat3(viewFromOcttreeFrame))) * modelSpaceRadius;
625
626 // already simplified some operations (2/right - left) == (1/right) given that right == -left
627 // term -(f+n)/(f-n); n == 0 => -f/f = -1.
628 const float size = radius + poData->modelSpaceRadius;
629 const glm::mat4 projectionMatrix(
630 1 / size, 0, 0, 0,
631 0, 1 / size, 0, 0,
632 0, 0, -2 / rayLength, 0,
633 0, 0, -1, 1
634 );
635
636 glm::vec2 query_pos_norm(0.0f, 0.0f);
637 CameraData cameraData = CameraData();
638 cameraData.rawProjectionMatrix = projectionMatrix;
639 cameraData.viewportSize = glm::vec2(8.f);
640 float tolerance = radius;
641 float tolerance_norm = 2.f * tolerance / cameraData.viewportSize.x;
642
643 float viewspaceDepth = rayLength;
644 glm::vec3 worldspacePos;
645
646 bool didHit = false;
647 switch (poComp.pointSizing) {
649 LOG_TRACE(logger, "PotreePointSizing::FixedSize is currently not supported for camera-less picking, no hits detected");
650 break;
652 if (rayPickDistanceScaled(poData, cameraData,
653 viewFromOcttreeFrame,
654 query_pos_norm,
655 tolerance,
656 tolerance_norm,
657 viewSpaceRadius,
658 viewspaceDepth, worldspacePos, roundPoint, depthPoint, false, false))
659 {
660 didHit = true;
661 }
662 break;
664 if (rayPickDistanceScaled(poData, cameraData,
665 viewFromOcttreeFrame,
666 query_pos_norm,
667 tolerance,
668 tolerance_norm,
669 viewSpaceRadius,
670 viewspaceDepth, worldspacePos, roundPoint, depthPoint, true, false))
671 {
672 didHit = true;
673 }
674 break;
675 default:
676 assert(false && "Illegal pointsizing enum value");
677 break;
678 }
679
680 if (didHit) {
681 const RayPicking::Ordinal ordinal = filter.getOrdinal(*renderComp);
682
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 (hits[0].isBehind(ordinal, viewDist)) {
687 hits[0] = {*renderComp, returnChildEntity, worldspacePos, ordinal, 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(*renderComp, returnChildEntity, worldspacePos, ordinal, viewDist);
694 hitSomething = true;
695 }
696 }
697 }
698
699 return hitSomething;
700}
701
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: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.
@ 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:181
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:130
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:202
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.
Definition: RayPick.h:62