mirror of
https://github.com/Nexoniarz/Fry3DX.git
synced 2026-11-03 04:24:26 +00:00
Picking and seeing through a tree of boxes per object (made again only when it changes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
48a9dbe918
commit
ecd7c2cfda
5 changed files with 217 additions and 46 deletions
119
src/bvh.cpp
Normal file
119
src/bvh.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#include "bvh.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace fry {
|
||||
|
||||
std::optional<float> rayTriangle(const glm::vec3& origin, const glm::vec3& dir, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) {
|
||||
const glm::vec3 e1 = b - a, e2 = c - a, p = glm::cross(dir, e2);
|
||||
const float det = glm::dot(e1, p);
|
||||
if (std::abs(det) < 1e-12f) return std::nullopt;
|
||||
const float inv = 1.0f / det;
|
||||
const glm::vec3 s = origin - a;
|
||||
const float u = glm::dot(s, p) * inv;
|
||||
if (u < 0 || u > 1) return std::nullopt;
|
||||
const glm::vec3 q = glm::cross(s, e1);
|
||||
const float v = glm::dot(dir, q) * inv;
|
||||
if (v < 0 || u + v > 1) return std::nullopt;
|
||||
const float t = glm::dot(e2, q) * inv;
|
||||
return t > 1e-6f ? std::optional<float>(t) : std::nullopt;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Where the ray goes into the box and out, or nothing (in the ray's lengths).
|
||||
bool rayBox(const glm::vec3& o, const glm::vec3& inv, const glm::vec3& lo, const glm::vec3& hi, float maxT) {
|
||||
const glm::vec3 a = (lo - o) * inv, b = (hi - o) * inv;
|
||||
const glm::vec3 n = glm::min(a, b), f = glm::max(a, b);
|
||||
const float in = std::max(std::max(n.x, n.y), std::max(n.z, 0.0f)), out = std::min(std::min(f.x, f.y), std::min(f.z, maxT));
|
||||
return in <= out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TriangleTree::TriangleTree(const Mesh& m, const glm::mat4& matrix) {
|
||||
points_.resize(m.verts.size());
|
||||
for (size_t i = 0; i < points_.size(); ++i) points_[i] = glm::vec3(matrix * glm::vec4(m.verts[i], 1.0f));
|
||||
for (size_t f = 0; f < m.faces.size(); ++f) {
|
||||
const auto& fv = m.faces[f];
|
||||
if (fv.size() == 3) tris_.push_back({{fv[0], fv[1], fv[2]}, int(f)}); // (the usual ones, without triangulating)
|
||||
else if (fv.size() == 4 && glm::dot(glm::cross(m.verts[size_t(fv[1])] - m.verts[size_t(fv[0])], m.verts[size_t(fv[2])] - m.verts[size_t(fv[0])]),
|
||||
glm::cross(m.verts[size_t(fv[2])] - m.verts[size_t(fv[0])], m.verts[size_t(fv[3])] - m.verts[size_t(fv[0])])) > 0)
|
||||
tris_.push_back({{fv[0], fv[1], fv[2]}, int(f)}), tris_.push_back({{fv[0], fv[2], fv[3]}, int(f)}); // (a quad with no dent)
|
||||
else
|
||||
for (const auto& t : triangulate(m, int(f))) tris_.push_back({t, int(f)});
|
||||
}
|
||||
if (!tris_.empty()) nodes_.reserve(tris_.size() * 2 / 3 + 1), build(0, int(tris_.size()));
|
||||
}
|
||||
|
||||
int TriangleTree::build(int first, int count) {
|
||||
const int at = int(nodes_.size());
|
||||
nodes_.push_back({});
|
||||
glm::vec3 lo(1e30f), hi(-1e30f), clo(1e30f), chi(-1e30f);
|
||||
for (int i = first; i < first + count; ++i) {
|
||||
glm::vec3 c(0.0f);
|
||||
for (int k : tris_[size_t(i)].v) lo = glm::min(lo, points_[size_t(k)]), hi = glm::max(hi, points_[size_t(k)]), c += points_[size_t(k)];
|
||||
clo = glm::min(clo, c / 3.0f), chi = glm::max(chi, c / 3.0f);
|
||||
}
|
||||
nodes_[size_t(at)].lo = lo, nodes_[size_t(at)].hi = hi;
|
||||
if (count <= 4) {
|
||||
nodes_[size_t(at)].first = first, nodes_[size_t(at)].count = count;
|
||||
return at;
|
||||
}
|
||||
// Split down the middle of the widest way the triangles' middles spread.
|
||||
const glm::vec3 size = chi - clo;
|
||||
const int axis = size.x > size.y && size.x > size.z ? 0 : size.y > size.z ? 1 : 2;
|
||||
auto mid = [&](const Tri& t) { return points_[size_t(t.v[0])][axis] + points_[size_t(t.v[1])][axis] + points_[size_t(t.v[2])][axis]; };
|
||||
const int half = count / 2;
|
||||
std::nth_element(tris_.begin() + first, tris_.begin() + first + half, tris_.begin() + first + count, [&](const Tri& a, const Tri& b) { return mid(a) < mid(b); });
|
||||
build(first, half);
|
||||
const int right = build(first + half, count - half);
|
||||
nodes_[size_t(at)].first = right, nodes_[size_t(at)].count = 0;
|
||||
return at;
|
||||
}
|
||||
|
||||
std::optional<TriangleTree::Hit> TriangleTree::nearest(const glm::vec3& o, const glm::vec3& d, float maxT) const {
|
||||
if (nodes_.empty()) return std::nullopt;
|
||||
const glm::vec3 inv(1.0f / d.x, 1.0f / d.y, 1.0f / d.z);
|
||||
std::optional<Hit> best;
|
||||
int stack[128], n = 0;
|
||||
stack[n++] = 0;
|
||||
while (n) {
|
||||
const Node& node = nodes_[size_t(stack[--n])];
|
||||
if (!rayBox(o, inv, node.lo, node.hi, best ? best->t : maxT)) continue;
|
||||
if (node.count) {
|
||||
for (int i = node.first; i < node.first + node.count; ++i) {
|
||||
const Tri& t = tris_[size_t(i)];
|
||||
if (auto h = rayTriangle(o, d, points_[size_t(t.v[0])], points_[size_t(t.v[1])], points_[size_t(t.v[2])]); h && *h < (best ? best->t : maxT))
|
||||
best = Hit{*h, t.face, t.v};
|
||||
}
|
||||
} else if (n + 2 <= 128) {
|
||||
stack[n++] = node.first, stack[n++] = int(&node - nodes_.data()) + 1;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
bool TriangleTree::blocked(const glm::vec3& o, const glm::vec3& d, float maxT, int skip) const {
|
||||
if (nodes_.empty()) return false;
|
||||
const glm::vec3 inv(1.0f / d.x, 1.0f / d.y, 1.0f / d.z);
|
||||
int stack[128], n = 0;
|
||||
stack[n++] = 0;
|
||||
while (n) {
|
||||
const Node& node = nodes_[size_t(stack[--n])];
|
||||
if (!rayBox(o, inv, node.lo, node.hi, maxT)) continue;
|
||||
if (node.count) {
|
||||
for (int i = node.first; i < node.first + node.count; ++i) {
|
||||
const Tri& t = tris_[size_t(i)];
|
||||
if (t.v[0] == skip || t.v[1] == skip || t.v[2] == skip) continue;
|
||||
if (auto h = rayTriangle(o, d, points_[size_t(t.v[0])], points_[size_t(t.v[1])], points_[size_t(t.v[2])]); h && *h < maxT) return true;
|
||||
}
|
||||
} else if (n + 2 <= 128) {
|
||||
stack[n++] = node.first, stack[n++] = int(&node - nodes_.data()) + 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace fry
|
||||
Loading…
Reference in a new issue