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);
560 std::sort(hits.begin(), hits.end());
566 const glm::vec3& startPos,
567 const glm::quat& rot,
573 std::vector<RayPicking::RayPickHit>& hits)
577 bool hitSomething =
false;
586 PotreeData* poData = poDataHolder.poData.get();
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; }
593 bool roundPoint =
false;
594 bool depthPoint =
false;
613 assert(
false &&
"Invalid case");
618 glm::mat4 viewMatrix = glm::toMat4(glm::conjugate(rot)) * glm::translate(glm::mat4(1.f), -startPos);
622 const glm::mat4& worldFromOcttreeFrame = poData->worldFromOcttreeFrame;
623 const glm::mat4 viewFromOcttreeFrame = viewMatrix * worldFromOcttreeFrame;
625 const float modelSpaceRadius = poData->modelSpaceRadius;
626 const float viewSpaceRadius = std::cbrt(glm::determinant(glm::mat3(viewFromOcttreeFrame))) * modelSpaceRadius;
630 const float size = radius + poData->modelSpaceRadius;
631 const glm::mat4 projectionMatrix(
634 0, 0, -2 / rayLength, 0,
638 glm::vec2 query_pos_norm(0.0f, 0.0f);
641 cameraData.viewportSize = glm::vec2(8.f);
642 float tolerance = radius;
643 float tolerance_norm = 2.f * tolerance / cameraData.viewportSize.x;
646 float viewspaceDepth = rayLength;
647 glm::vec3 worldspacePos;
651 LOG_TRACE(logger,
"PotreePointSizing::FixedSize is currently not supported for camera-less picking, no hits detected");
654 if (rayPickDistanceScaled(poData, cameraData,
655 viewFromOcttreeFrame,
660 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
false,
false))
666 if (rayPickDistanceScaled(poData, cameraData,
667 viewFromOcttreeFrame,
672 viewspaceDepth, worldspacePos, roundPoint, depthPoint,
true,
false))
678 assert(
false &&
"Illegal pointsizing enum value");
683 glm::vec4 viewPos = viewMatrix * glm::vec4(glm::vec3(worldspacePos), 1.f);
684 float viewDist = -viewPos.z;
686 if (viewDist < hits[0].distance) {
687 hits[0] = {*poCompHit, returnChildEntity, worldspacePos, viewDist};
693 hits.emplace_back(*poCompHit, returnChildEntity, worldspacePos, viewDist);
701 std::sort(hits.begin(), hits.end());
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.
@ 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.
@ 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.