// //--------------------------------------------------------------------------- // AABB-tree used for ray testing // 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 "doom_aabbtree.h" #include "g_levellocals.h" using namespace hwrenderer; DoomLevelAABBTree::DoomLevelAABBTree(FLevelLocals *lev) { Level = lev; // Calculate the center of all lines TArray 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 the static subtree if (!GenerateTree(¢roids[0], false)) return; int staticroot = nodes.Size() - 1; dynamicStartNode = nodes.Size(); dynamicStartLine = treelines.Size(); // Create the dynamic subtree if (GenerateTree(¢roids[0], true)) { int dynamicroot = nodes.Size() - 1; // Create a shared root node FVector2 aabb_min, aabb_max; const auto &left = nodes[staticroot]; const auto &right = nodes[dynamicroot]; aabb_min.X = min(left.aabb_left, right.aabb_left); aabb_min.Y = min(left.aabb_top, right.aabb_top); aabb_max.X = max(left.aabb_right, right.aabb_right); aabb_max.Y = max(left.aabb_bottom, right.aabb_bottom); nodes.Push({ aabb_min, aabb_max, staticroot, dynamicroot }); } // Add the lines referenced by the leaf nodes treelines.Resize(mapLines.Size()); for (unsigned int i = 0; i < mapLines.Size(); i++) { const auto &line = Level->lines[mapLines[i]]; auto &treeline = treelines[i]; 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; } } bool DoomLevelAABBTree::GenerateTree(const FVector2 *centroids, bool dynamicsubtree) { // Create a list of level lines we want to add: TArray line_elements; auto &maplines = Level->lines; for (unsigned int i = 0; i < maplines.Size(); i++) { if (!maplines[i].backsector) { bool isPolyLine = maplines[i].sidedef[0] && (maplines[i].sidedef[0]->Flags & WALLF_POLYOBJ); if (isPolyLine && dynamicsubtree) { line_elements.Push(mapLines.Size()); mapLines.Push(i); } else if (!isPolyLine && !dynamicsubtree) { line_elements.Push(mapLines.Size()); mapLines.Push(i); } } } if (line_elements.Size() == 0) return false; // GenerateTreeNode needs a buffer where it can store line indices temporarily when sorting lines into the left and right child AABB buckets TArray work_buffer; work_buffer.Resize(line_elements.Size() * 2); // Generate the AABB tree GenerateTreeNode(&line_elements[0], (int)line_elements.Size(), centroids, &work_buffer[0]); return true; } bool DoomLevelAABBTree::Update() { bool modified = false; for (unsigned int i = dynamicStartLine; i < mapLines.Size(); i++) { const auto &line = Level->lines[mapLines[i]]; AABBTreeLine treeline; 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; if (memcmp(&treelines[i], &treeline, sizeof(AABBTreeLine))) { TArray path = FindNodePath(i, nodes.Size() - 1); if (path.Size()) { float x1 = (float)line.v1->fX(); float y1 = (float)line.v1->fY(); float x2 = (float)line.v2->fX(); float y2 = (float)line.v2->fY(); int nodeIndex = path[0]; nodes[nodeIndex].aabb_left = min(x1, x2); nodes[nodeIndex].aabb_right = max(x1, x2); nodes[nodeIndex].aabb_top = min(y1, y2); nodes[nodeIndex].aabb_bottom = max(y1, y2); for (unsigned int j = 1; j < path.Size(); j++) { auto &cur = nodes[path[j]]; const auto &left = nodes[cur.left_node]; const auto &right = nodes[cur.right_node]; cur.aabb_left = min(left.aabb_left, right.aabb_left); cur.aabb_top = min(left.aabb_top, right.aabb_top); cur.aabb_right = max(left.aabb_right, right.aabb_right); cur.aabb_bottom = max(left.aabb_bottom, right.aabb_bottom); } treelines[i] = treeline; modified = true; } } } return modified; } int DoomLevelAABBTree::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; auto &maplines = Level->lines; aabb_min.X = (float)maplines[mapLines[lines[0]]].v1->fX(); aabb_min.Y = (float)maplines[mapLines[lines[0]]].v1->fY(); aabb_max = aabb_min; for (int i = 0; i < num_lines; i++) { float x1 = (float)maplines[mapLines[lines[i]]].v1->fX(); float y1 = (float)maplines[mapLines[lines[i]]].v1->fY(); float x2 = (float)maplines[mapLines[lines[i]]].v2->fX(); float y2 = (float)maplines[mapLines[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[mapLines[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; 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[mapLines[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) { left_count = num_lines / 2; right_count = num_lines - left_count; } 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; }