// // 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& 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::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) { KRCollider* collider = dynamic_cast(*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::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) { KRCollider* collider = dynamic_cast(*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::iterator nodes_itr = m_sceneNodes.begin(); nodes_itr != m_sceneNodes.end(); nodes_itr++) { KRCollider* collider = dynamic_cast(*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; }