1#include "Services/Variables.h"
2#include "Systems/Core/CameraSystem.h"
3#include "Systems/Core/TransformSystem.h"
4#include "Utilities/FrustumClassification.h"
7#include "PotreeSystem.h"
9#include "Foundation/Logging/Logger.h"
17 unsigned popcount(uint8_t x)
19 x = (x & 0x55u) + ((x >> 1) & 0x55u);
20 x = (x & 0x33u) + ((x >> 2) & 0x33u);
21 x = (x & 0x0Fu) + ((x >> 4) & 0x0Fu);
25 glm::vec3 euclidean(
const glm::vec4& h)
27 return (1.f / h.w) * glm::vec3(h);
30 bool isBBoxInFrustum(
const glm::mat4& pickFromLocal,
31 const glm::mat4& localFromPick,
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,
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;
48 if (mask != 0b111111)
return false;
52 for (
unsigned i = 0; i < 8; i++) {
53 glm::vec3 p = euclidean(localFromPick * glm::vec4((i & 1) ? -1.f : 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;
64 return mask == 0b111111;
67 inline const glm::mat4 createPickMatrix(
const CameraData& camData,
const glm::vec2& pixelSize,
const glm::vec2& query_pos_norm)
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,
75 -sx * query_pos_norm.x, -sy * query_pos_norm.y, 0, 1);
79 inline const glm::mat4 createOrthoPickMatrix(
const CameraData& camData,
const float guard_norm,
const float viewSpaceRadius,
const glm::vec2& query_norm)
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));
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));
89 const float g_n = viewCenterA_guard.x - viewCenterA.x;
90 const float g_f = viewCenterB_guard.x - viewCenterB.x;
91 const float r = viewSpaceRadius + g_n;
92 const float s = g_f - g_n;
93 const float d = -(viewCenterB.z - viewCenterA.z);
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 =
103 glm::mat4(1.f, 0.f, 0.f, 0.f,
105 0.f, 0.f, -(s + 2.f * r) / d, -s / d,
109 glm::mat4(1.f, 0.f, 0.f, 0.f,
111 shearX, shearY, 1.f, 0.f,
112 0.f, 0.f, 0.f, 1.f) *
115 glm::mat4(1.f, 0.f, 0.f, 0.f,
118 -viewCenterA.x, -viewCenterA.y, -viewCenterA.z, 1.f);
120 return orthoPickFromViewFrame;
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)
130 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
131 bool anyHits =
false;
135 glm::mat4 perspPickFromOcttreeFrame = (createPickMatrix(camData, glm::vec2(poData->pointSize.
val) + guard_px, query_norm) *
137 glm::mat4 octtreeFrameFromPerspPick = glm::inverse(perspPickFromOcttreeFrame);
139 float viewspace_nearest = viewspaceDepthBest;
141 if (isBBoxInFrustum(perspPickFromOcttreeFrame,
142 octtreeFrameFromPerspPick,
143 poData->octtreeFrame.tightBBoxMin,
144 poData->octtreeFrame.tightBBoxMax))
148 for (
auto* cell : poData->activeCells) {
149 if (isBBoxInFrustum(perspPickFromOcttreeFrame,
150 octtreeFrameFromPerspPick,
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];
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))
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;
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;
185 r = std::sqrt(maxSizeRadiusSquared);
188 r = std::max(std::abs(maxSizePointCoord.x),
189 std::abs(maxSizePointCoord.y));
193 viewspace_depth += viewspaceRadius * r;
196 if (viewspace_nearest < viewspace_depth)
continue;
198 viewspace_nearest = viewspace_depth;
203 worldspacePosBest = euclidean(worldFromOcttreeFrame * glm::vec4(cell->points[indexBest], 1.f));
204 viewspaceDepthBest = viewspace_nearest;
215 float getDensityScale(
const PotreeData* poData,
const glm::vec3& p)
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);
220 glm::vec3 unitPos = glm::clamp(invScale * (p + invShift), glm::vec3(0.f), glm::vec3(1.f));
224 for (
size_t l = 0; l < 30; l++) {
225 glm::vec3 t = 2.f * unitPos;
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;
233 glm::u8vec4 data = poData->octtreeTextureData[offset];
234 uint8_t childrenBitmask = data.r;
236 if ((childrenBitmask & childBit) == 0) {
238 float factor = (1.f / 255.f) * data.w;
240 float decoded = poData->densitySized.bias - ((poData->densitySized.scale * factor) + poData->densitySized.levelScale * float(l));
242 scale = glm::exp2(-glm::min(0.f, decoded));
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());
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)
264 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
265 const float modelSpaceRadius = poData->modelSpaceRadius;
267 bool anyHits =
false;
271 glm::mat4 orthoPickFromOcttreeFrame;
272 glm::mat4 octtreeFrameFromOrthoPick;
275 glm::mat4 perspPickFromOcttreeFrame;
276 glm::mat4 octtreeFrameFromPerspPick;
278 orthoPickFromOcttreeFrame = createOrthoPickMatrix(camData, guard_norm, viewSpaceRadius, query_norm) * viewFromOcttreeFrame;
279 octtreeFrameFromOrthoPick = glm::inverse(orthoPickFromOcttreeFrame);
283 perspPickFromOcttreeFrame =
284 createPickMatrix(camData, glm::vec2(poData->pointSize.
max) + guard_px, query_norm) *
286 octtreeFrameFromPerspPick = glm::inverse(perspPickFromOcttreeFrame);
291 octtreeFrameFromOrthoPick = glm::inverse(orthoPickFromOcttreeFrame);
295 float viewspace_nearest = viewspaceDepthBest;
296 if (!isBBoxInFrustum(orthoPickFromOcttreeFrame,
297 octtreeFrameFromOrthoPick,
298 poData->octtreeFrame.tightBBoxMin - modelSpaceRadius,
299 poData->octtreeFrame.tightBBoxMax + modelSpaceRadius))
305 for (
auto* cell : poData->activeCells) {
310 if ((!hasViewport || isBBoxInFrustum(perspPickFromOcttreeFrame,
311 octtreeFrameFromPerspPick,
314 isBBoxInFrustum(orthoPickFromOcttreeFrame,
315 octtreeFrameFromOrthoPick,
316 cell->tbmin - modelSpaceRadius,
317 cell->tbmax + modelSpaceRadius))
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];
327 glm::vec4 maxSizePointCoord_h;
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))
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))
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)) {
358 float maxSizeRadiusSquared = worldSizeRadiusSquared;
359 glm::vec3 maxSizePointCoord = worldSizePointCoord;
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) {
374 float scale = getDensityScale(poData, p);
375 worldSizePointCoord.x *= scale;
376 worldSizePointCoord.y *= scale;
377 worldSizeRadiusSquared *= scale * scale;
379 if (1.f < worldSizeRadiusSquared)
continue;
382 if (1.f < glm::abs(worldSizePointCoord.x) || 1.f < glm::abs(worldSizePointCoord.y))
continue;
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;
398 r = std::sqrt(std::min(worldSizeRadiusSquared, maxSizeRadiusSquared));
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);
409 viewspace_depth += viewSpaceRadius * r;
413 if (viewspace_nearest < viewspace_depth)
continue;
416 viewspace_nearest = viewspace_depth;
422 worldspacePosBest = euclidean(worldFromOcttreeFrame * glm::vec4(cell->points[indexBest], 1.f));
423 viewspaceDepthBest = viewspace_nearest;
436 const glm::vec2& normPosition,
442 std::vector<RayPicking::RayPickHit>& hits)
446 bool hitSomething =
false;
448 const CameraData& cameraData = context->cameraSystem->getData(&camera);
451 float tolerance_norm = 2.f * radius / cameraData.viewportSize.x;
460 PotreeData* poData = poDataHolder.poData.get();
463 if (poData->state != PotreeState::Running || poData->octtreeFrame.tightBBoxMax.x < poData->octtreeFrame.tightBBoxMin.x) {
continue; }
465 bool roundPoint =
false;
466 bool depthPoint =
false;
485 assert(
false &&
"Invalid case");
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;
496 float viewspaceDepth = std::numeric_limits<float>::max();
497 glm::vec3 worldspacePos;
501 if (rayPickFixedSize(poData, cameraData,
502 viewFromOcttreeFrame,
506 viewspaceDepth, worldspacePos, roundPoint, depthPoint))
512 if (rayPickDistanceScaled(poData, cameraData,
513 viewFromOcttreeFrame,
518 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
false,
true))
524 if (rayPickDistanceScaled(poData, cameraData,
525 viewFromOcttreeFrame,
530 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
true,
true))
536 assert(
false &&
"Illegal pointsizing enum value");
541 glm::vec4 viewPos = cameraData.viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
542 float viewDist = -viewPos.z;
545 if (viewDist < hits[0].distance) {
546 hits[0] = {*poCompHit, returnChildEntity, worldspacePos, viewDist};
552 hits.emplace_back(*poCompHit, returnChildEntity, worldspacePos, viewDist);
562 const glm::vec3& startPos,
563 const glm::quat& rot,
569 std::vector<RayPicking::RayPickHit>& hits)
573 bool hitSomething =
false;
582 PotreeData* poData = poDataHolder.poData.get();
585 const glm::vec3& tbmin = poData->octtreeFrame.tightBBoxMin;
586 const glm::vec3& tbmax = poData->octtreeFrame.tightBBoxMax;
587 if (poData->state != PotreeState::Running || (tbmax.x < tbmin.x)) {
continue; }
589 bool roundPoint =
false;
590 bool depthPoint =
false;
609 assert(
false &&
"Invalid case");
614 glm::mat4 viewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
618 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
619 const glm::mat4 viewFromOcttreeFrame = viewMatrix * worldFromOcttreeFrame;
621 const float modelSpaceRadius = poData->modelSpaceRadius;
622 const float viewSpaceRadius = std::cbrt(glm::determinant(glm::mat3(viewFromOcttreeFrame))) * modelSpaceRadius;
626 const float size = radius + poData->modelSpaceRadius;
627 const glm::mat4 projectionMatrix(
630 0, 0, -2 / rayLength, 0,
634 glm::vec2 query_pos_norm(0.0f, 0.0f);
637 cameraData.viewportSize = glm::vec2(8.f);
638 float tolerance = radius;
639 float tolerance_norm = 2.f * tolerance / cameraData.viewportSize.x;
642 float viewspaceDepth = rayLength;
643 glm::vec3 worldspacePos;
647 LOG_TRACE(logger,
"PotreePointSizing::FixedSize is currently not supported for camera-less picking, no hits detected");
650 if (rayPickDistanceScaled(poData, cameraData,
651 viewFromOcttreeFrame,
656 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
false,
false))
662 if (rayPickDistanceScaled(poData, cameraData,
663 viewFromOcttreeFrame,
668 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
true,
false))
674 assert(
false &&
"Illegal pointsizing enum value");
679 glm::vec4 viewPos = viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
680 float viewDist = -viewPos.z;
682 if (viewDist < hits[0].distance) {
683 hits[0] = {*poCompHit, returnChildEntity, worldspacePos, viewDist};
689 hits.emplace_back(*poCompHit, returnChildEntity, worldspacePos, viewDist);
Base class for Component instances.
ComponentType * getComponent() const
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.
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.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
PicksReturned
* Options for returning picking hits.
@ 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.
@ 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
Contains data describing a Camera instance and its derived data structured such as matrix data and vi...
glm::mat4 rawProjectionMatrix
Projection matrix that has not been adjusted by the renderer, and is thus appropriate for direct scre...
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.
float max
Current dpi-scaled maximum point-size.
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.
RenderLayers layerMask
Limit picking to the specified render layers. Pick all layers by default.