mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-26 02:55:25 +02:00
475 lines
No EOL
18 KiB
C++
475 lines
No EOL
18 KiB
C++
//
|
|
// TriangleSet.cpp
|
|
// libraries/entities/src
|
|
//
|
|
// Created by Brad Hefta-Gaub on 3/2/2017.
|
|
// Copyright 2017 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
|
|
#include "TriangleSet.h"
|
|
|
|
#include "GLMHelpers.h"
|
|
|
|
#include <list>
|
|
|
|
void TriangleSet::insert(const Triangle& t) {
|
|
_isBalanced = false;
|
|
|
|
_triangles.push_back(t);
|
|
_bounds += t.v0;
|
|
_bounds += t.v1;
|
|
_bounds += t.v2;
|
|
}
|
|
|
|
void TriangleSet::clear() {
|
|
_triangles.clear();
|
|
_bounds.clear();
|
|
_isBalanced = false;
|
|
|
|
_triangleTree.clear();
|
|
}
|
|
|
|
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
|
if (!_bounds.contains(point)) {
|
|
return false;
|
|
}
|
|
|
|
bool insideMesh = true; // optimistic
|
|
for (const auto& triangle : _triangles) {
|
|
if (!isPointBehindTrianglesPlane(point, triangle.v0, triangle.v1, triangle.v2)) {
|
|
// it's not behind at least one so we bail
|
|
insideMesh = false;
|
|
break;
|
|
}
|
|
|
|
}
|
|
return insideMesh;
|
|
}
|
|
|
|
void TriangleSet::debugDump() {
|
|
qDebug() << __FUNCTION__;
|
|
qDebug() << "bounds:" << getBounds();
|
|
qDebug() << "triangles:" << size() << "at top level....";
|
|
qDebug() << "----- _triangleTree -----";
|
|
_triangleTree.debugDump();
|
|
}
|
|
|
|
void TriangleSet::balanceTree() {
|
|
_triangleTree.reset(_bounds);
|
|
|
|
// insert all the triangles
|
|
for (size_t i = 0; i < _triangles.size(); i++) {
|
|
_triangleTree.insert(i);
|
|
}
|
|
|
|
_isBalanced = true;
|
|
|
|
#if WANT_DEBUGGING
|
|
debugDump();
|
|
#endif
|
|
}
|
|
|
|
// With an octree: 8 ^ MAX_DEPTH = 4096 leaves
|
|
//static const int MAX_DEPTH = 4;
|
|
// With a k-d tree: 2 ^ MAX_DEPTH = 4096 leaves
|
|
static const int MAX_DEPTH = 12;
|
|
|
|
TriangleSet::TriangleTreeCell::TriangleTreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth) :
|
|
_allTriangles(allTriangles)
|
|
{
|
|
reset(bounds, depth);
|
|
}
|
|
|
|
void TriangleSet::TriangleTreeCell::clear() {
|
|
_population = 0;
|
|
_triangleIndices.clear();
|
|
_bounds.clear();
|
|
_children.first.reset();
|
|
_children.second.reset();
|
|
}
|
|
|
|
void TriangleSet::TriangleTreeCell::reset(const AABox& bounds, int depth) {
|
|
clear();
|
|
_bounds = bounds;
|
|
_depth = depth;
|
|
}
|
|
|
|
void TriangleSet::TriangleTreeCell::debugDump() {
|
|
qDebug() << __FUNCTION__;
|
|
qDebug() << " bounds:" << getBounds();
|
|
qDebug() << " depth:" << _depth;
|
|
qDebug() << " population:" << _population << "this level or below"
|
|
<< " ---- triangleIndices:" << _triangleIndices.size() << "in this cell";
|
|
|
|
int numChildren = 0;
|
|
if (_children.first) {
|
|
numChildren++;
|
|
} else if (_children.second) {
|
|
numChildren++;
|
|
}
|
|
qDebug() << "child cells:" << numChildren;
|
|
if (_depth < MAX_DEPTH) {
|
|
if (_children.first) {
|
|
qDebug() << "child: 0";
|
|
_children.first->debugDump();
|
|
}
|
|
if (_children.second) {
|
|
qDebug() << "child: 1";
|
|
_children.second->debugDump();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<AABox, AABox> TriangleSet::TriangleTreeCell::getTriangleTreeCellChildBounds() {
|
|
std::pair<AABox, AABox> toReturn;
|
|
int axis = 0;
|
|
// find biggest axis
|
|
glm::vec3 dimensions = _bounds.getDimensions();
|
|
for (int i = 0; i < 3; i++) {
|
|
if (dimensions[i] >= dimensions[(i + 1) % 3] && dimensions[i] >= dimensions[(i + 2) % 3]) {
|
|
axis = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// The new boxes are half the size in the largest dimension
|
|
glm::vec3 newDimensions = dimensions;
|
|
newDimensions[axis] *= 0.5f;
|
|
toReturn.first.setBox(_bounds.getCorner(), newDimensions);
|
|
glm::vec3 offset = glm::vec3(0.0f);
|
|
offset[axis] = newDimensions[axis];
|
|
toReturn.second.setBox(_bounds.getCorner() + offset, newDimensions);
|
|
return toReturn;
|
|
}
|
|
|
|
void TriangleSet::TriangleTreeCell::insert(size_t triangleIndex) {
|
|
_population++;
|
|
|
|
// if we're not yet at the max depth, then check which child the triangle fits in
|
|
if (_depth < MAX_DEPTH) {
|
|
const Triangle& triangle = _allTriangles[triangleIndex];
|
|
auto childBounds = getTriangleTreeCellChildBounds();
|
|
|
|
auto insertOperator = [&](const AABox& childBound, std::shared_ptr<TriangleTreeCell>& child) {
|
|
// if the child AABox would contain the triangle...
|
|
if (childBound.contains(triangle)) {
|
|
// if the child cell doesn't yet exist, create it...
|
|
if (!child) {
|
|
child = std::make_shared<TriangleTreeCell>(_allTriangles, childBound, _depth + 1);
|
|
}
|
|
|
|
// insert the triangleIndex in the child cell
|
|
child->insert(triangleIndex);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
if (insertOperator(childBounds.first, _children.first) || insertOperator(childBounds.second, _children.second)) {
|
|
return;
|
|
}
|
|
}
|
|
// either we're at max depth, or the triangle doesn't fit in one of our
|
|
// children and so we want to just record it here
|
|
_triangleIndices.push_back(triangleIndex);
|
|
}
|
|
|
|
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
|
BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
|
if (!_isBalanced) {
|
|
balanceTree();
|
|
}
|
|
|
|
float localDistance = distance;
|
|
int trianglesTouched = 0;
|
|
bool hit = _triangleTree.findRayIntersection(origin, direction, localDistance, face, triangle, precision, trianglesTouched, allowBackface);
|
|
if (hit) {
|
|
distance = localDistance;
|
|
}
|
|
|
|
#if WANT_DEBUGGING
|
|
if (precision) {
|
|
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size();
|
|
}
|
|
#endif
|
|
return hit;
|
|
}
|
|
|
|
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
|
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
|
bool TriangleSet::TriangleTreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
|
float& distance, BoxFace& face, Triangle& triangle, bool precision,
|
|
int& trianglesTouched, bool allowBackface) {
|
|
bool intersectedSomething = false;
|
|
float bestDistance = FLT_MAX;
|
|
Triangle bestTriangle;
|
|
|
|
if (precision) {
|
|
for (const auto& triangleIndex : _triangleIndices) {
|
|
const auto& thisTriangle = _allTriangles[triangleIndex];
|
|
float thisTriangleDistance;
|
|
trianglesTouched++;
|
|
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
|
|
if (thisTriangleDistance < bestDistance) {
|
|
bestDistance = thisTriangleDistance;
|
|
bestTriangle = thisTriangle;
|
|
intersectedSomething = true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
intersectedSomething = true;
|
|
bestDistance = distance;
|
|
}
|
|
|
|
if (intersectedSomething) {
|
|
distance = bestDistance;
|
|
triangle = bestTriangle;
|
|
}
|
|
|
|
return intersectedSomething;
|
|
}
|
|
|
|
bool TriangleSet::TriangleTreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
|
BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
|
|
bool allowBackface) {
|
|
if (_population < 1) {
|
|
return false; // no triangles below here, so we can't intersect
|
|
}
|
|
|
|
float bestLocalDistance = FLT_MAX;
|
|
BoxFace bestLocalFace;
|
|
Triangle bestLocalTriangle;
|
|
bool intersects = false;
|
|
|
|
// Check our local triangle set first
|
|
// The distance passed in here is the distance to our bounding box. If !precision, that distance is used
|
|
{
|
|
float internalDistance = distance;
|
|
BoxFace internalFace;
|
|
Triangle internalTriangle;
|
|
if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
|
bestLocalDistance = internalDistance;
|
|
bestLocalFace = internalFace;
|
|
bestLocalTriangle = internalTriangle;
|
|
intersects = true;
|
|
}
|
|
}
|
|
|
|
// if we're not yet at the max depth, then check our children
|
|
if (_depth < MAX_DEPTH) {
|
|
std::list<SortedTriangleCell> sortedTriangleCells;
|
|
auto sortingOperator = [&](std::shared_ptr<TriangleTreeCell>& child) {
|
|
if (child) {
|
|
float priority = FLT_MAX;
|
|
if (child->getBounds().contains(origin)) {
|
|
priority = 0.0f;
|
|
} else {
|
|
float childBoundDistance = FLT_MAX;
|
|
BoxFace childBoundFace;
|
|
glm::vec3 childBoundNormal;
|
|
if (child->getBounds().findRayIntersection(origin, direction, childBoundDistance, childBoundFace, childBoundNormal)) {
|
|
// We only need to add this cell if it's closer than the local triangle set intersection (if there was one)
|
|
if (childBoundDistance < bestLocalDistance) {
|
|
priority = childBoundDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (priority < FLT_MAX) {
|
|
if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) {
|
|
sortedTriangleCells.emplace_front(priority, child);
|
|
} else {
|
|
sortedTriangleCells.emplace_back(priority, child);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
sortingOperator(_children.first);
|
|
sortingOperator(_children.second);
|
|
|
|
for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) {
|
|
const SortedTriangleCell& sortedTriangleCell = *it;
|
|
float childDistance = sortedTriangleCell.first;
|
|
// We can exit once childDistance > bestLocalDistance
|
|
if (childDistance > bestLocalDistance) {
|
|
break;
|
|
}
|
|
// If we're inside the child cell and !precision, we need the actual distance to the cell bounds
|
|
if (!precision && childDistance < EPSILON) {
|
|
BoxFace childBoundFace;
|
|
glm::vec3 childBoundNormal;
|
|
sortedTriangleCell.second->getBounds().findRayIntersection(origin, direction, childDistance, childBoundFace, childBoundNormal);
|
|
}
|
|
BoxFace childFace;
|
|
Triangle childTriangle;
|
|
if (sortedTriangleCell.second->findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
|
if (childDistance < bestLocalDistance) {
|
|
bestLocalDistance = childDistance;
|
|
bestLocalFace = childFace;
|
|
bestLocalTriangle = childTriangle;
|
|
intersects = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (intersects) {
|
|
distance = bestLocalDistance;
|
|
face = bestLocalFace;
|
|
triangle = bestLocalTriangle;
|
|
}
|
|
return intersects;
|
|
}
|
|
|
|
bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
|
|
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
|
|
if (!_isBalanced) {
|
|
balanceTree();
|
|
}
|
|
|
|
float localDistance = parabolicDistance;
|
|
int trianglesTouched = 0;
|
|
bool hit = _triangleTree.findParabolaIntersection(origin, velocity, acceleration, localDistance, face, triangle, precision, trianglesTouched, allowBackface);
|
|
if (hit) {
|
|
parabolicDistance = localDistance;
|
|
}
|
|
|
|
#if WANT_DEBUGGING
|
|
if (precision) {
|
|
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleTree._population << "_triangles.size:" << _triangles.size();
|
|
}
|
|
#endif
|
|
return hit;
|
|
}
|
|
|
|
bool TriangleSet::TriangleTreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity,
|
|
const glm::vec3& acceleration, float& parabolicDistance,
|
|
BoxFace& face, Triangle& triangle, bool precision,
|
|
int& trianglesTouched, bool allowBackface) {
|
|
bool intersectedSomething = false;
|
|
float bestDistance = FLT_MAX;
|
|
Triangle bestTriangle;
|
|
|
|
if (precision) {
|
|
for (const auto& triangleIndex : _triangleIndices) {
|
|
const auto& thisTriangle = _allTriangles[triangleIndex];
|
|
float thisTriangleDistance;
|
|
trianglesTouched++;
|
|
if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) {
|
|
if (thisTriangleDistance < bestDistance) {
|
|
bestDistance = thisTriangleDistance;
|
|
bestTriangle = thisTriangle;
|
|
intersectedSomething = true;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
intersectedSomething = true;
|
|
bestDistance = parabolicDistance;
|
|
}
|
|
|
|
if (intersectedSomething) {
|
|
parabolicDistance = bestDistance;
|
|
triangle = bestTriangle;
|
|
}
|
|
|
|
return intersectedSomething;
|
|
}
|
|
|
|
bool TriangleSet::TriangleTreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
|
|
const glm::vec3& acceleration, float& parabolicDistance,
|
|
BoxFace& face, Triangle& triangle, bool precision,
|
|
int& trianglesTouched, bool allowBackface) {
|
|
if (_population < 1) {
|
|
return false; // no triangles below here, so we can't intersect
|
|
}
|
|
|
|
float bestLocalDistance = FLT_MAX;
|
|
BoxFace bestLocalFace;
|
|
Triangle bestLocalTriangle;
|
|
bool intersects = false;
|
|
|
|
// Check our local triangle set first
|
|
// The distance passed in here is the distance to our bounding box. If !precision, that distance is used
|
|
{
|
|
float internalDistance = parabolicDistance;
|
|
BoxFace internalFace;
|
|
Triangle internalTriangle;
|
|
if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
|
|
bestLocalDistance = internalDistance;
|
|
bestLocalFace = internalFace;
|
|
bestLocalTriangle = internalTriangle;
|
|
intersects = true;
|
|
}
|
|
}
|
|
|
|
// if we're not yet at the max depth, then check our children
|
|
if (_depth < MAX_DEPTH) {
|
|
std::list<SortedTriangleCell> sortedTriangleCells;
|
|
auto sortingOperator = [&](std::shared_ptr<TriangleTreeCell>& child) {
|
|
if (child) {
|
|
float priority = FLT_MAX;
|
|
if (child->getBounds().contains(origin)) {
|
|
priority = 0.0f;
|
|
} else {
|
|
float childBoundDistance = FLT_MAX;
|
|
BoxFace childBoundFace;
|
|
glm::vec3 childBoundNormal;
|
|
if (child->getBounds().findParabolaIntersection(origin, velocity, acceleration, childBoundDistance, childBoundFace, childBoundNormal)) {
|
|
// We only need to add this cell if it's closer than the local triangle set intersection (if there was one)
|
|
if (childBoundDistance < bestLocalDistance) {
|
|
priority = childBoundDistance;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (priority < FLT_MAX) {
|
|
if (sortedTriangleCells.size() > 0 && priority < sortedTriangleCells.front().first) {
|
|
sortedTriangleCells.emplace_front(priority, child);
|
|
} else {
|
|
sortedTriangleCells.emplace_back(priority, child);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
sortingOperator(_children.first);
|
|
sortingOperator(_children.second);
|
|
|
|
for (auto it = sortedTriangleCells.begin(); it != sortedTriangleCells.end(); ++it) {
|
|
const SortedTriangleCell& sortedTriangleCell = *it;
|
|
float childDistance = sortedTriangleCell.first;
|
|
// We can exit once childDistance > bestLocalDistance
|
|
if (childDistance > bestLocalDistance) {
|
|
break;
|
|
}
|
|
// If we're inside the child cell and !precision, we need the actual distance to the cell bounds
|
|
if (!precision && childDistance < EPSILON) {
|
|
BoxFace childBoundFace;
|
|
glm::vec3 childBoundNormal;
|
|
sortedTriangleCell.second->getBounds().findParabolaIntersection(origin, velocity, acceleration, childDistance, childBoundFace, childBoundNormal);
|
|
}
|
|
BoxFace childFace;
|
|
Triangle childTriangle;
|
|
if (sortedTriangleCell.second->findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
|
|
if (childDistance < bestLocalDistance) {
|
|
bestLocalDistance = childDistance;
|
|
bestLocalFace = childFace;
|
|
bestLocalTriangle = childTriangle;
|
|
intersects = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (intersects) {
|
|
parabolicDistance = bestLocalDistance;
|
|
face = bestLocalFace;
|
|
triangle = bestLocalTriangle;
|
|
}
|
|
return intersects;
|
|
} |