281 lines
8.2 KiB
C++
Executable File
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;
|
|
}
|
|
|