11#include <unordered_set>
25 : _app(app), _depth(depth)
32 _width = std::max(1,
width);
33 _height = std::max(1,
height);
40 _layers = layers.empty() ? (_camera ? _camera->layers() : std::vector<int>{}) : layers;
42 _candidateIndex.clear();
44 if (!_camera || !_camera->camera() || !_camera->entity()) {
48 const Vector3 cameraPos = _camera->entity()->position();
51 if (!renderComponent || !renderComponent->enabled()) {
55 if (!isLayerAllowed(renderComponent->layers())) {
59 for (
auto* meshInstance : renderComponent->meshInstances()) {
60 if (!meshInstance || !meshInstance->node()) {
68 std::array<Vector3, 8> corners = {{
79 bool anyProjected =
false;
80 float minX = std::numeric_limits<float>::max();
81 float minY = std::numeric_limits<float>::max();
82 float maxX = std::numeric_limits<float>::lowest();
83 float maxY = std::numeric_limits<float>::lowest();
85 for (
const auto& corner : corners) {
88 if (!projectPoint(corner, sx, sy)) {
92 minX = std::min(minX, sx);
93 minY = std::min(minY, sy);
94 maxX = std::max(maxX, sx);
95 maxY = std::max(maxY, sy);
103 candidate.meshInstance = meshInstance;
104 candidate.minX = minX;
105 candidate.minY = minY;
106 candidate.maxX = maxX;
107 candidate.maxY = maxY;
108 const Vector3 delta = center - cameraPos;
112 _candidateIndex[candidate.meshInstance] = _candidates.size();
113 _candidates.push_back(candidate);
120 std::vector<MeshInstance*> selection;
125 const Rect rect = sanitizeRect(x, y,
width,
height);
126 const float rectMaxX =
static_cast<float>(rect.x + rect.width);
127 const float rectMaxY =
static_cast<float>(rect.y + rect.height);
129 std::unordered_set<MeshInstance*> seen;
130 for (
const auto& candidate : _candidates) {
131 if (!candidate.meshInstance) {
135 if (candidate.maxX <
static_cast<float>(rect.x) || candidate.minX > rectMaxX ||
136 candidate.maxY <
static_cast<float>(rect.y) || candidate.minY > rectMaxY) {
140 if (seen.insert(candidate.meshInstance).second) {
141 selection.push_back(candidate.meshInstance);
146 const auto ita = _candidateIndex.find(const_cast<MeshInstance*>(a));
147 const auto itb = _candidateIndex.find(const_cast<MeshInstance*>(b));
148 if (ita == _candidateIndex.end() || itb == _candidateIndex.end()) {
151 return _candidates[ita->second].distanceSq < _candidates[itb->second].distanceSq;
160 return selection.empty() ? nullptr : selection.front();
165 if (!_depth || !_camera) {
171 if (!buildRay(x, y, rayOrigin, rayDirection)) {
176 if (selected.empty()) {
180 Ray ray(rayOrigin, rayDirection);
181 float nearestDistanceSq = std::numeric_limits<float>::max();
182 std::optional<Vector3> nearestPoint;
184 for (
auto* meshInstance : selected) {
185 const auto it = _candidateIndex.find(meshInstance);
186 if (it == _candidateIndex.end()) {
191 if (!_candidates[it->second].bounds.intersectsRay(ray, &hitPoint)) {
195 const float hitDistanceSq = (hitPoint - rayOrigin).lengthSquared();
196 if (hitDistanceSq < nearestDistanceSq) {
197 nearestDistanceSq = hitDistanceSq;
198 nearestPoint = hitPoint;
205 bool Picker::projectPoint(
const Vector3& worldPos,
float& outX,
float& outY)
const
207 if (!_camera || !_camera->camera() || !_camera->entity()) {
211 const Matrix4 viewMatrix = _camera->entity()->worldTransform().inverse();
212 const Matrix4& projMatrix = _camera->camera()->projectionMatrix();
214 const Vector3 viewPos = viewMatrix.transformPoint(worldPos);
215 if (viewPos.getZ() >= 0.0f) {
219 const Vector4 clipPos = projMatrix * Vector4(viewPos.getX(), viewPos.getY(), viewPos.getZ(), 1.0f);
220 if (std::abs(clipPos.getW()) < 1e-6f) {
224 const float ndcX = clipPos.getX() / clipPos.getW();
225 const float ndcY = clipPos.getY() / clipPos.getW();
227 outX = (ndcX * 0.5f + 0.5f) *
static_cast<float>(_width);
228 outY = (1.0f - (ndcY * 0.5f + 0.5f)) *
static_cast<float>(_height);
232 bool Picker::buildRay(
const int x,
const int y, Vector3& outOrigin, Vector3& outDirection)
const
234 if (!_camera || !_camera->camera() || !_camera->entity()) {
238 const Rect rect = sanitizeRect(x, y, 1, 1);
239 const float px =
static_cast<float>(rect.x);
240 const float py =
static_cast<float>(rect.y);
242 const float ndcX = (px /
static_cast<float>(_width)) * 2.0f - 1.0f;
243 const float ndcY = 1.0f - (py /
static_cast<float>(_height)) * 2.0f;
245 const Matrix4 viewMatrix = _camera->entity()->worldTransform().inverse();
246 const Matrix4 viewProjection = _camera->camera()->projectionMatrix() * viewMatrix;
247 const Matrix4 invViewProjection = viewProjection.inverse();
249 Vector4 nearClip(ndcX, ndcY, 1.0f, 1.0f);
250 Vector4 farClip(ndcX, ndcY, 0.0f, 1.0f);
252 nearClip = invViewProjection * nearClip;
253 farClip = invViewProjection * farClip;
255 if (std::abs(nearClip.getW()) < 1e-6f || std::abs(farClip.getW()) < 1e-6f) {
259 const Vector3 nearWorld(
260 nearClip.getX() / nearClip.getW(),
261 nearClip.getY() / nearClip.getW(),
262 nearClip.getZ() / nearClip.getW()
264 const Vector3 farWorld(
265 farClip.getX() / farClip.getW(),
266 farClip.getY() / farClip.getW(),
267 farClip.getZ() / farClip.getW()
270 const Vector3 dir = farWorld - nearWorld;
271 if (dir.lengthSquared() < 1e-10f) {
275 outOrigin = nearWorld;
276 outDirection = dir.normalized();
280 Picker::Rect Picker::sanitizeRect(
int x,
int y,
int width,
int height)
const
282 x = std::clamp(x, 0, std::max(0, _width - 1));
283 y = std::clamp(y, 0, std::max(0, _height - 1));
284 width = std::max(1, width);
285 height = std::max(1, height);
286 width = std::min(width, _width - x);
287 height = std::min(height, _height - y);
288 return Rect{ x, y, width, height };
291 bool Picker::isLayerAllowed(
const std::vector<int>& objectLayers)
const
293 if (_layers.empty()) {
297 return std::any_of(objectLayers.begin(), objectLayers.end(), [&](
const int layer) {
298 return std::find(_layers.begin(), _layers.end(), layer) != _layers.end();
Axis-Aligned Bounding Box defined by center and half-extents.
const Vector3 & center() const
const Vector3 & halfExtents() const
Bounding sphere defined by center and radius for intersection and containment tests.
Central application orchestrator managing scenes, rendering, input, and resource loading.
Renderable instance of a Mesh with its own material, transform node, and optional GPU instancing.
void prepare(CameraComponent *camera, Scene *scene, const std::vector< int > &layers={})
MeshInstance * getSelectionSingle(int x, int y) const
Picker(Engine *app, int width, int height, bool depth=false)
std::vector< MeshInstance * > getSelection(int x, int y, int width=1, int height=1) const
std::optional< Vector3 > getWorldPoint(int x, int y) const
void resize(int width, int height)
Infinite ray defined by origin and direction for raycasting and picking.
static const std::vector< RenderComponent * > & instances()
Container for the scene graph, lighting environment, fog, skybox, and layer composition.
3D vector for positions, directions, and normals with multi-backend SIMD acceleration.
float lengthSquared() const