/*
* Copyright (C) 2023 Christopher J. Howard
*
* This file is part of Antkeeper source code.
*
* Antkeeper source code is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Antkeeper source code 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Antkeeper source code. If not, see .
*/
#include
#include
#include
#include
namespace geom {
mesh_accelerator::mesh_accelerator()
{}
void mesh_accelerator::build(const mesh& mesh)
{
// Clear octree and face map
octree.clear();
face_map.clear();
// Calculate mesh dimensions
box bounds = calculate_bounds(mesh);
float3 mesh_dimensions = bounds.max - bounds.min;
center_offset = mesh_dimensions * 0.5f - (bounds.min + bounds.max) * 0.5f;
// Calculate node dimensions at each octree depth
for (auto i = 0; i <= octree_type::max_depth; ++i)
{
node_dimensions[i] = mesh_dimensions * static_cast((1.0f / std::pow(2, i)));
}
// Add faces to octree
for (mesh::face* face: mesh.get_faces())
{
// Calculate face bounds
float3 min_point = reinterpret_cast(face->edge->vertex->position);
float3 max_point = min_point;
mesh::edge* edge = face->edge;
do
{
const auto& position = edge->vertex->position;
for (int i = 0; i < 3; ++i)
{
min_point[i] = std::min(min_point[i], position[i]);
max_point[i] = std::max(max_point[i], position[i]);
}
edge = edge->next;
}
while (edge != face->edge);
// 1. Find max depth node of aabb min
// 2. Find max depth node of aabb max
// 3. Find common ancestor of the two nodes--that's the containing node.
typename octree_type::node_type min_node = find_node(min_point);
typename octree_type::node_type max_node = find_node(max_point);
typename octree_type::node_type containing_node = octree_type::common_ancestor(min_node, max_node);
// Insert containing node into octree
octree.insert(containing_node);
// Add face to face map
face_map[containing_node].push_back(face);
}
}
std::optional mesh_accelerator::query_nearest(const ray& ray) const
{
ray_query_result result;
result.t = std::numeric_limits::infinity();
result.face = nullptr;
query_nearest_recursive(result.t, result.face, octree.root, ray);
if (result.face)
return std::optional{result};
return std::nullopt;
}
void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray& ray) const
{
// Get node bounds
const box node_bounds = get_node_bounds(node);
// If ray passed through this node
if (intersection(ray, node_bounds))
{
// Test all triangles in the node
if (auto it = face_map.find(node); it != face_map.end())
{
const std::list& faces = it->second;
for (mesh::face* face: faces)
{
// Get triangle coordinates
const float3& a = reinterpret_cast(face->edge->vertex->position);
const float3& b = reinterpret_cast(face->edge->next->vertex->position);
const float3& c = reinterpret_cast(face->edge->previous->vertex->position);
// Test for intersection with triangle
auto triangle_intersection = intersection(ray, a, b, c);
if (triangle_intersection)
{
const float t = std::get<0>(*triangle_intersection);
if (t < nearest_t)
{
nearest_t = t;
nearest_face = face;
}
}
}
}
// Test all child nodes
if (!octree.is_leaf(node))
{
for (int i = 0; i < 8; ++i)
{
query_nearest_recursive(nearest_t, nearest_face, octree.child(node, i), ray);
}
}
}
}
box mesh_accelerator::get_node_bounds(typename octree_type::node_type node) const
{
// Decode Morton location of node
std::uint32_t x, y, z;
morton::decode(octree_type::location(node), x, y, z);
float3 node_location = float3{static_cast(x), static_cast(y), static_cast(z)};
// Get node dimensions at node depth
const float3& dimensions = node_dimensions[octree_type::depth(node)];
// Calculate AABB
float3 min_point = (node_location * dimensions) - center_offset;
return box{min_point, min_point + dimensions};
}
typename mesh_accelerator::octree_type::node_type mesh_accelerator::find_node(const float3& point) const
{
// Transform point to octree space
float3 transformed_point = (point + center_offset);
// Account for floating-point tolerance
const float epsilon = 0.00001f;
transformed_point.x() = std::max(0.0f, std::min(node_dimensions[0].x() - epsilon, transformed_point.x()));
transformed_point.y() = std::max(0.0f, std::min(node_dimensions[0].y() - epsilon, transformed_point.y()));
transformed_point.z() = std::max(0.0f, std::min(node_dimensions[0].z() - epsilon, transformed_point.z()));
// Transform point to max-depth node space
transformed_point = transformed_point / node_dimensions[octree_type::max_depth];
// Encode transformed point as a Morton location code
std::uint32_t location = morton::encode(
static_cast(transformed_point.x()),
static_cast(transformed_point.y()),
static_cast(transformed_point.z()));
// Return max depth node at the determined location
return octree_type::node(octree_type::max_depth, location);
}
} // namespace geom