qzdoom/src/gl/dynlights/gl_aabbtree.cpp

289 lines
9.1 KiB
C++
Raw Normal View History

2017-03-01 01:46:03 +00:00
//
//---------------------------------------------------------------------------
// AABB-tree used for ray testing
2017-03-01 01:46:03 +00:00
// Copyright(C) 2017 Magnus Norddahl
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
#include "gl/system/gl_system.h"
#include "gl/shaders/gl_shader.h"
#include "gl/dynlights/gl_aabbtree.h"
2017-03-01 01:46:03 +00:00
#include "gl/system/gl_interface.h"
#include "r_state.h"
#include "g_levellocals.h"
2017-03-01 01:46:03 +00:00
LevelAABBTree::LevelAABBTree()
{
// Calculate the center of all lines
TArray<FVector2> centroids;
for (unsigned int i = 0; i < level.lines.Size(); i++)
{
FVector2 v1 = { (float)level.lines[i].v1->fX(), (float)level.lines[i].v1->fY() };
FVector2 v2 = { (float)level.lines[i].v2->fX(), (float)level.lines[i].v2->fY() };
centroids.Push((v1 + v2) * 0.5f);
}
// Create a list of level lines we want to add:
TArray<int> line_elements;
for (unsigned int i = 0; i < level.lines.Size(); i++)
{
if (!level.lines[i].backsector)
{
line_elements.Push(i);
}
}
// GenerateTreeNode needs a buffer where it can store line indices temporarily when sorting lines into the left and right child AABB buckets
TArray<int> work_buffer;
2017-03-07 23:34:08 +00:00
work_buffer.Resize(line_elements.Size() * 2);
// Generate the AABB tree
GenerateTreeNode(&line_elements[0], (int)line_elements.Size(), &centroids[0], &work_buffer[0]);
// Add the lines referenced by the leaf nodes
2017-03-07 23:34:08 +00:00
lines.Resize(level.lines.Size());
for (unsigned int i = 0; i < level.lines.Size(); i++)
{
const auto &line = level.lines[i];
auto &treeline = lines[i];
2017-03-07 23:34:08 +00:00
treeline.x = (float)line.v1->fX();
treeline.y = (float)line.v1->fY();
treeline.dx = (float)line.v2->fX() - treeline.x;
treeline.dy = (float)line.v2->fY() - treeline.y;
2017-03-07 23:34:08 +00:00
}
}
double LevelAABBTree::RayTest(const DVector3 &ray_start, const DVector3 &ray_end)
2017-03-07 23:34:08 +00:00
{
// Precalculate some of the variables used by the ray/line intersection test
2017-03-07 23:34:08 +00:00
DVector2 raydelta = ray_end - ray_start;
double raydist2 = raydelta | raydelta;
DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X);
double rayd = raynormal | ray_start;
if (raydist2 < 1.0)
return 1.0f;
double hit_fraction = 1.0;
2017-03-07 23:34:08 +00:00
// Walk the tree nodes
2017-03-07 23:34:08 +00:00
int stack[16];
int stack_pos = 1;
stack[0] = nodes.Size() - 1; // root node is the last node in the list
2017-03-07 23:34:08 +00:00
while (stack_pos > 0)
{
int node_index = stack[stack_pos - 1];
if (!OverlapRayAABB(ray_start, ray_end, nodes[node_index]))
{
// If the ray doesn't overlap this node's AABB we're done for this subtree
2017-03-07 23:34:08 +00:00
stack_pos--;
}
else if (nodes[node_index].line_index != -1) // isLeaf(node_index)
{
// We reached a leaf node. Do a ray/line intersection test to see if we hit the line.
hit_fraction = MIN(IntersectRayLine(ray_start, ray_end, nodes[node_index].line_index, raydelta, rayd, raydist2), hit_fraction);
2017-03-07 23:34:08 +00:00
stack_pos--;
}
else if (stack_pos == 16)
{
stack_pos--; // stack overflow - tree is too deep!
2017-03-07 23:34:08 +00:00
}
else
{
// The ray overlaps the node's AABB. Examine its child nodes.
stack[stack_pos - 1] = nodes[node_index].left_node;
stack[stack_pos] = nodes[node_index].right_node;
2017-03-07 23:34:08 +00:00
stack_pos++;
}
}
return hit_fraction;
2017-03-07 23:34:08 +00:00
}
bool LevelAABBTree::OverlapRayAABB(const DVector2 &ray_start2d, const DVector2 &ray_end2d, const AABBTreeNode &node)
2017-03-07 23:34:08 +00:00
{
// To do: simplify test to use a 2D test
DVector3 ray_start = DVector3(ray_start2d, 0.0);
DVector3 ray_end = DVector3(ray_end2d, 0.0);
DVector3 aabb_min = DVector3(node.aabb_left, node.aabb_top, -1.0);
DVector3 aabb_max = DVector3(node.aabb_right, node.aabb_bottom, 1.0);
// Standard 3D ray/AABB overlapping test.
// The details for the math here can be found in Real-Time Rendering, 3rd Edition.
// We could use a 2D test here instead, which would probably simplify the math.
2017-03-07 23:34:08 +00:00
DVector3 c = (ray_start + ray_end) * 0.5f;
DVector3 w = ray_end - c;
DVector3 h = (aabb_max - aabb_min) * 0.5f; // aabb.extents();
c -= (aabb_max + aabb_min) * 0.5f; // aabb.center();
Fixed compilation warnings reported by Clang src/gl/scene/gl_clipper.h:150:23: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/gl/dynlights/gl_aabbtree.cpp:137:24: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:34: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:44: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:30: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:54: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:142:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:143:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:144:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:167:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_shadowmap.cpp:163:31: warning: '&&' within '||' [-Wlogical-op-parentheses] src/p_saveg.cpp:367:16: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare] src/p_saveg.cpp:402:60: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/p_setup.cpp:1553:39: warning: format specifies type 'ptrdiff_t' (aka 'long') but the argument has type 'int' [-Wformat] src/scripting/zscript/zcc_compile.cpp:293:74: warning: field 'AST' will be initialized after field 'mVersion' [-Wreorder] src/swrenderer/drawers/r_thread.cpp:113:21: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]
2017-03-18 12:59:23 +00:00
DVector3 v = DVector3(fabs(w.X), fabs(w.Y), fabs(w.Z));
2017-03-07 23:34:08 +00:00
Fixed compilation warnings reported by Clang src/gl/scene/gl_clipper.h:150:23: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/gl/dynlights/gl_aabbtree.cpp:137:24: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:34: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:44: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:30: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:54: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:142:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:143:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:144:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:167:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_shadowmap.cpp:163:31: warning: '&&' within '||' [-Wlogical-op-parentheses] src/p_saveg.cpp:367:16: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare] src/p_saveg.cpp:402:60: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/p_setup.cpp:1553:39: warning: format specifies type 'ptrdiff_t' (aka 'long') but the argument has type 'int' [-Wformat] src/scripting/zscript/zcc_compile.cpp:293:74: warning: field 'AST' will be initialized after field 'mVersion' [-Wreorder] src/swrenderer/drawers/r_thread.cpp:113:21: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]
2017-03-18 12:59:23 +00:00
if (fabs(c.X) > v.X + h.X || fabs(c.Y) > v.Y + h.Y || fabs(c.Z) > v.Z + h.Z)
2017-03-07 23:34:08 +00:00
return false; // disjoint;
Fixed compilation warnings reported by Clang src/gl/scene/gl_clipper.h:150:23: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/gl/dynlights/gl_aabbtree.cpp:137:24: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:34: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:44: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:30: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:54: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:142:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:143:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:144:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:167:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_shadowmap.cpp:163:31: warning: '&&' within '||' [-Wlogical-op-parentheses] src/p_saveg.cpp:367:16: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare] src/p_saveg.cpp:402:60: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/p_setup.cpp:1553:39: warning: format specifies type 'ptrdiff_t' (aka 'long') but the argument has type 'int' [-Wformat] src/scripting/zscript/zcc_compile.cpp:293:74: warning: field 'AST' will be initialized after field 'mVersion' [-Wreorder] src/swrenderer/drawers/r_thread.cpp:113:21: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]
2017-03-18 12:59:23 +00:00
if (fabs(c.Y * w.Z - c.Z * w.Y) > h.Y * v.Z + h.Z * v.Y ||
fabs(c.X * w.Z - c.Z * w.X) > h.X * v.Z + h.Z * v.X ||
fabs(c.X * w.Y - c.Y * w.X) > h.X * v.Y + h.Y * v.X)
2017-03-07 23:34:08 +00:00
return false; // disjoint;
return true; // overlap;
}
double LevelAABBTree::IntersectRayLine(const DVector2 &ray_start, const DVector2 &ray_end, int line_index, const DVector2 &raydelta, double rayd, double raydist2)
2017-03-07 23:34:08 +00:00
{
// Check if two line segments intersects (the ray and the line).
// The math below does this by first finding the fractional hit for an infinitely long ray line.
// If that hit is within the line segment (0 to 1 range) then it calculates the fractional hit for where the ray would hit.
//
// This algorithm is homemade - I would not be surprised if there's a much faster method out there.
2017-03-07 23:34:08 +00:00
const double epsilon = 0.0000001;
const AABBTreeLine &line = lines[line_index];
2017-03-07 23:34:08 +00:00
DVector2 raynormal = DVector2(raydelta.Y, -raydelta.X);
DVector2 line_pos(line.x, line.y);
DVector2 line_delta(line.dx, line.dy);
double den = raynormal | line_delta;
Fixed compilation warnings reported by Clang src/gl/scene/gl_clipper.h:150:23: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/gl/dynlights/gl_aabbtree.cpp:137:24: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:34: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:137:44: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:30: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:139:54: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:142:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:143:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:144:3: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_aabbtree.cpp:167:6: warning: using integer absolute value function 'abs' when argument is of floating point type [-Wabsolute-value] src/gl/dynlights/gl_shadowmap.cpp:163:31: warning: '&&' within '||' [-Wlogical-op-parentheses] src/p_saveg.cpp:367:16: warning: comparison of integers of different signs: 'unsigned int' and 'int' [-Wsign-compare] src/p_saveg.cpp:402:60: warning: comparison of integers of different signs: 'int' and 'unsigned int' [-Wsign-compare] src/p_setup.cpp:1553:39: warning: format specifies type 'ptrdiff_t' (aka 'long') but the argument has type 'int' [-Wformat] src/scripting/zscript/zcc_compile.cpp:293:74: warning: field 'AST' will be initialized after field 'mVersion' [-Wreorder] src/swrenderer/drawers/r_thread.cpp:113:21: warning: comparison of integers of different signs: 'int' and 'size_t' (aka 'unsigned long') [-Wsign-compare]
2017-03-18 12:59:23 +00:00
if (fabs(den) > epsilon)
2017-03-07 23:34:08 +00:00
{
double t_line = (rayd - (raynormal | line_pos)) / den;
if (t_line >= 0.0 && t_line <= 1.0)
{
DVector2 linehitdelta = line_pos + line_delta * t_line - ray_start;
double t = (raydelta | linehitdelta) / raydist2;
return t > 0.0 ? t : 1.0;
}
}
2017-03-07 23:34:08 +00:00
return 1.0;
}
int LevelAABBTree::GenerateTreeNode(int *lines, int num_lines, const FVector2 *centroids, int *work_buffer)
{
if (num_lines == 0)
return -1;
// Find bounding box and median of the lines
FVector2 median = FVector2(0.0f, 0.0f);
FVector2 aabb_min, aabb_max;
aabb_min.X = (float)level.lines[lines[0]].v1->fX();
aabb_min.Y = (float)level.lines[lines[0]].v1->fY();
aabb_max = aabb_min;
for (int i = 0; i < num_lines; i++)
{
float x1 = (float)level.lines[lines[i]].v1->fX();
float y1 = (float)level.lines[lines[i]].v1->fY();
float x2 = (float)level.lines[lines[i]].v2->fX();
float y2 = (float)level.lines[lines[i]].v2->fY();
aabb_min.X = MIN(aabb_min.X, x1);
aabb_min.X = MIN(aabb_min.X, x2);
aabb_min.Y = MIN(aabb_min.Y, y1);
aabb_min.Y = MIN(aabb_min.Y, y2);
aabb_max.X = MAX(aabb_max.X, x1);
aabb_max.X = MAX(aabb_max.X, x2);
aabb_max.Y = MAX(aabb_max.Y, y1);
aabb_max.Y = MAX(aabb_max.Y, y2);
median += centroids[lines[i]];
}
median /= (float)num_lines;
if (num_lines == 1) // Leaf node
{
nodes.Push(AABBTreeNode(aabb_min, aabb_max, lines[0]));
return (int)nodes.Size() - 1;
}
// Find the longest axis
float axis_lengths[2] =
{
aabb_max.X - aabb_min.X,
aabb_max.Y - aabb_min.Y
};
int axis_order[2] = { 0, 1 };
FVector2 axis_plane[2] = { FVector2(1.0f, 0.0f), FVector2(0.0f, 1.0f) };
std::sort(axis_order, axis_order + 2, [&](int a, int b) { return axis_lengths[a] > axis_lengths[b]; });
// Try sort at longest axis, then if that fails then the other one.
// We place the sorted lines into work_buffer and then move the result back to the lines list when done.
int left_count, right_count;
FVector2 axis;
for (int attempt = 0; attempt < 2; attempt++)
{
// Find the sort plane for axis
FVector2 axis = axis_plane[axis_order[attempt]];
FVector3 plane(axis, -(median | axis));
// Sort lines into two based ib whether the line center is on the front or back side of a plane
left_count = 0;
right_count = 0;
for (int i = 0; i < num_lines; i++)
{
int line_index = lines[i];
float side = FVector3(centroids[lines[i]], 1.0f) | plane;
if (side >= 0.0f)
{
work_buffer[left_count] = line_index;
left_count++;
}
else
{
work_buffer[num_lines + right_count] = line_index;
right_count++;
}
}
if (left_count != 0 && right_count != 0)
break;
}
// Check if something went wrong when sorting and do a random sort instead
if (left_count == 0 || right_count == 0)
2017-03-01 01:46:03 +00:00
{
left_count = num_lines / 2;
right_count = num_lines - left_count;
2017-03-01 01:46:03 +00:00
}
else
{
// Move result back into lines list:
for (int i = 0; i < left_count; i++)
lines[i] = work_buffer[i];
for (int i = 0; i < right_count; i++)
lines[i + left_count] = work_buffer[num_lines + i];
}
// Create child nodes:
int left_index = -1;
int right_index = -1;
if (left_count > 0)
left_index = GenerateTreeNode(lines, left_count, centroids, work_buffer);
if (right_count > 0)
right_index = GenerateTreeNode(lines + left_count, right_count, centroids, work_buffer);
// Store resulting node and return its index
nodes.Push(AABBTreeNode(aabb_min, aabb_max, left_index, right_index));
return (int)nodes.Size() - 1;
2017-03-01 01:46:03 +00:00
}