Files
kraken/kraken/KROctreeNode.cpp

281 lines
8.2 KiB
C++
Executable File

//
// KROctreeNode.cpp
// Kraken Engine
//
// Copyright 2025 Kearwood Gilbert. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of Kearwood Gilbert.
//
#include "KROctreeNode.h"
#include "nodes/KRNode.h"
#include "nodes/KRCollider.h"
using namespace hydra;
KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds) : m_bounds(bounds)
{
m_parent = parent;
for (int i = 0; i < 8; i++) {
m_children[i] = NULL;
}
}
KROctreeNode::KROctreeNode(KROctreeNode* parent, const AABB& bounds, int iChild, KROctreeNode* pChild) : m_bounds(bounds)
{
// This constructor is used when expanding the octree and replacing the root node with a new root that encapsulates it
m_parent = parent;
for (int i = 0; i < 8; i++) {
m_children[i] = NULL;
}
m_children[iChild] = pChild;
pChild->m_parent = this;
}
KROctreeNode::~KROctreeNode()
{
for (int i = 0; i < 8; i++) {
if (m_children[i] != NULL) {
delete m_children[i];
}
}
}
AABB KROctreeNode::getBounds()
{
return m_bounds;
}
void KROctreeNode::add(KRNode* pNode)
{
int iChild = getChildIndex(pNode);
if (iChild == -1) {
m_sceneNodes.insert(pNode);
pNode->addToOctreeNode(this);
} else {
if (m_children[iChild] == NULL) {
m_children[iChild] = new KROctreeNode(this, getChildBounds(iChild));
}
m_children[iChild]->add(pNode);
}
}
AABB KROctreeNode::getChildBounds(int iChild)
{
Vector3 center = m_bounds.center();
return AABB::Create(
Vector3::Create(
(iChild & 1) == 0 ? m_bounds.min.x : center.x,
(iChild & 2) == 0 ? m_bounds.min.y : center.y,
(iChild & 4) == 0 ? m_bounds.min.z : center.z),
Vector3::Create(
(iChild & 1) == 0 ? center.x : m_bounds.max.x,
(iChild & 2) == 0 ? center.y : m_bounds.max.y,
(iChild & 4) == 0 ? center.z : m_bounds.max.z)
);
}
int KROctreeNode::getChildIndex(KRNode* pNode)
{
for (int iChild = 0; iChild < 8; iChild++) {
if (getChildBounds(iChild).contains(pNode->getBounds())) {
return iChild;
}
}
return -1;
}
void KROctreeNode::trim()
{
for (int iChild = 0; iChild < 8; iChild++) {
if (m_children[iChild]) {
if (m_children[iChild]->isEmpty()) {
delete m_children[iChild];
m_children[iChild] = NULL;
}
}
}
}
void KROctreeNode::remove(KRNode* pNode)
{
m_sceneNodes.erase(pNode);
}
void KROctreeNode::update(KRNode* pNode)
{
}
bool KROctreeNode::isEmpty() const
{
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
return false;
}
}
return m_sceneNodes.empty();
}
bool KROctreeNode::canShrinkRoot() const
{
int cChildren = 0;
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
cChildren++;
}
}
return cChildren <= 1 && m_sceneNodes.empty();
}
KROctreeNode* KROctreeNode::stripChild()
{
// Return the first found child and update its reference to NULL so that the destructor will not free it. This is used for shrinking the octree
// NOTE: The caller of this function will be responsible for freeing the child object. It is also possible to return a NULL
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
KROctreeNode* child = m_children[i];
child->m_parent = NULL;
m_children[i] = NULL;
return child;
}
}
return NULL;
}
KROctreeNode* KROctreeNode::getParent()
{
return m_parent;
}
KROctreeNode** KROctreeNode::getChildren()
{
return m_children;
}
std::set<KRNode*>& KROctreeNode::getSceneNodes()
{
return m_sceneNodes;
}
bool KROctreeNode::lineCast(const Vector3& v0, const Vector3& v1, HitInfo& hitinfo, unsigned int layer_mask)
{
bool hit_found = false;
if (hitinfo.didHit() && v1 != hitinfo.getPosition()) {
// Optimization: If we already have a hit, only search for hits that are closer
hit_found = lineCast(v0, hitinfo.getPosition(), hitinfo, layer_mask);
} else {
if (getBounds().intersectsLine(v0, v1)) {
for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
if (collider) {
if (collider->lineCast(v0, v1, hitinfo, layer_mask)) hit_found = true;
}
}
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
if (m_children[i]->lineCast(v0, v1, hitinfo, layer_mask)) {
hit_found = true;
}
}
}
}
}
return hit_found;
}
bool KROctreeNode::rayCast(const Vector3& v0, const Vector3& dir, HitInfo& hitinfo, unsigned int layer_mask)
{
bool hit_found = false;
if (hitinfo.didHit()) {
// Optimization: If we already have a hit, only search for hits that are closer
hit_found = lineCast(v0, hitinfo.getPosition(), hitinfo, layer_mask); // Note: This is purposefully lineCast as opposed to RayCast
} else {
if (getBounds().intersectsRay(v0, dir)) {
for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
if (collider) {
if (collider->rayCast(v0, dir, hitinfo, layer_mask)) hit_found = true;
}
}
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
if (m_children[i]->rayCast(v0, dir, hitinfo, layer_mask)) {
hit_found = true;
}
}
}
}
}
return hit_found;
}
bool KROctreeNode::sphereCast(const Vector3& v0, const Vector3& v1, float radius, HitInfo& hitinfo, unsigned int layer_mask)
{
bool hit_found = false;
/*
// FINDME, TODO - Adapt this optimization to work with sphereCasts
if(hitinfo.didHit() && v1 != hitinfo.getPosition()) {
// Optimization: If we already have a hit, only search for hits that are closer
hit_found = sphereCast(v0, hitinfo.getPosition(), radius, hitinfo, layer_mask);
} else {
*/
AABB swept_bounds = AABB::Create(Vector3::Create(KRMIN(v0.x, v1.x) - radius, KRMIN(v0.y, v1.y) - radius, KRMIN(v0.z, v1.z) - radius), Vector3::Create(KRMAX(v0.x, v1.x) + radius, KRMAX(v0.y, v1.y) + radius, KRMAX(v0.z, v1.z) + radius));
// FINDME, TODO - Investigate AABB - swept sphere intersections or OBB - AABB intersections: "if(getBounds().intersectsSweptSphere(v0, v1, radius)) {"
if (getBounds().intersects(swept_bounds)) {
for (std::set<KRNode*>::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) {
KRCollider* collider = dynamic_cast<KRCollider*>(*nodes_itr);
if (collider) {
if (collider->sphereCast(v0, v1, radius, hitinfo, layer_mask)) hit_found = true;
}
}
for (int i = 0; i < 8; i++) {
if (m_children[i]) {
if (m_children[i]->sphereCast(v0, v1, radius, hitinfo, layer_mask)) {
hit_found = true;
}
}
}
}
// }
return hit_found;
}