1 year ago

#369634

test-img

Nick Román

Correctly managing pointers in C++ Quadtree implementation

I'm working on a C++ quadtree implementation for collision detection. I tried to adapt this Java implementation to C++ by using pointers; namely, storing the child nodes of each node as Node pointers (code at the end). However, since my understanding of pointers is still rather lacking, I am struggling to understand why my Quadtree class produces the following two issues:

  1. When splitting a Node in 4, the debugger tells me that all my childNodes entries are identical to the first one, i.e., same address and bounds.

  2. Even if 1. is ignored, I get an Access violation reading location 0xFFFFFFFFFFFFFFFF, which I found out is a consequence of the childNode pointees being deleted after the first split, resulting in undefined behaviour.

My question is: what improvements should I make to my Quadtree.hpp so that each Node can contain 4 distinct child node pointers and have those references last until the quadtree is cleared?

What I have tried so far:

  1. Modifying getChildNode according to this guide and using temporary variables in split() to avoid all 4 entries of childNodes to point to the same Node:

         void split() {
             for (int i = 0; i < 4; i++) {
                 Node temp = getChildNode(level, bounds, i + 1);
                 childNodes[i] = &(temp);
             }
         }
    

but this does not solve the problem.

  1. This one is particularly confusing. My initial idea was to just store childNodes as Nodes themselves, but turns out that cannot be done while we're defining the Node class itself. Hence, it looks like the only way to store Nodes is by first creating them and then storing pointers to them as I tried to do in split(), yet it seems that those will not "last" until we've inserted all the objects since the pointees get deleted (run out of scope) and we get the aforementioned undefined behaviour. I also thought of using smart pointers, but that seems to only overcomplicate things.

The code:

Quadtree.hpp

#pragma once

#include <vector>
#include <algorithm>
#include "Box.hpp"

namespace quadtree {
    class Node {
    public:

        Node(int p_level, quadtree::Box<float> p_bounds)
            :level(p_level), bounds(p_bounds) 
        {
            parentWorld = NULL;
        }

        // NOTE: mandatory upon Quadtree initialization
        void setParentWorld(World* p_world_ptr) {
            parentWorld = p_world_ptr;
        }

        /*
         Clears the quadtree
         */
        void clear() {
            objects.clear();

            for (int i = 0; i < 4; i++) {
                if (childNodes[i] != nullptr) {
                    (*(childNodes[i])).clear();
                    childNodes[i] = nullptr;
                }
            }
        }

        /*
         Splits the node into 4 subnodes
         */
        void split() {
            for (int i = 0; i < 4; i++) {
                childNodes[i] = &getChildNode(level, bounds, i + 1);;
            }
        }

        /*
         Determine which node the object belongs to. -1 means
         object cannot completely fit within a child node and is part
         of the parent node
         */
        int getIndex(Entity* p_ptr_entity) {
            quadtree::Box<float> nodeBounds;
            quadtree::Box<float> entityHitbox;
            for (int i = 0; i < 4; i++) {
                nodeBounds = childNodes[i]->bounds;
                ComponentHandle<Hitbox> hitbox;
                parentWorld->unpack(*p_ptr_entity, hitbox);
                entityHitbox = hitbox->box;
                if (nodeBounds.contains(entityHitbox)) {
                    return i;
                }
            }
            return -1;  // if no childNode completely contains Entity Hitbox
        }

        /*
         Insert the object into the quadtree. If the node
         exceeds the capacity, it will split and add all
         objects to their corresponding nodes.
         */
        void insertObject(Entity* p_ptr_entity) {
            if (childNodes[0] != nullptr) {
                int index = getIndex(p_ptr_entity);

                if (index != -1) {
                    (*childNodes[index]).insertObject(p_ptr_entity);  // insert in child node
                    return;
                }
            }

            objects.push_back(p_ptr_entity);  // add to parent node

            if (objects.size() > MAX_OBJECTS && level < MAX_DEPTH) {
                if (childNodes[0] == nullptr) {
                    split();
                }

                int i = 0;
                while (i < objects.size()) {
                    int index = getIndex(objects[i]);
                    if (index != -1) 
                    {
                        Entity* temp_entity = objects[i];
                        {
                            // remove i-th element of the vector
                            using std::swap;
                            swap(objects[i], objects.back());
                            objects.pop_back();
                        }
                        (*childNodes[index]).insertObject(temp_entity);
                    }
                    else 
                    {
                        i++;
                    }
                }
            }

        }

        /*
         Return all objects that could collide with the given object
         */
        std::vector<Entity*> retrieve(Entity* p_ptr_entity, std::vector<Entity*> returnObjects) {
            int index = getIndex(p_ptr_entity);
            if (index != -1 && childNodes[0] == nullptr) {
                (*childNodes[index]).retrieve(p_ptr_entity, returnObjects);
            }

            returnObjects.insert(returnObjects.end(), objects.begin(), objects.end());

            return returnObjects;
        }

        World* getParentWorld() {
            return parentWorld;
        }

    private:
        int MAX_OBJECTS = 10;
        int MAX_DEPTH = 5;
        World* parentWorld;  // used to unpack entities

        int level;  // depth of the node
        quadtree::Box<float> bounds;  // boundary of nodes in the game's map
        std::vector<Entity*> objects;  // list of objects contained in the node: pointers to Entitites in the game
        Node* childNodes[4];

        quadtree::Box<float> getQuadrantBounds(quadtree::Box<float> p_parentBounds, int p_quadrant_id) {

            quadtree::Box<float> quadrantBounds;
            quadrantBounds.width = p_parentBounds.width / 2;
            quadrantBounds.height = p_parentBounds.height / 2;

            switch (p_quadrant_id) {
            case 1:  // NE
                quadrantBounds.top = p_parentBounds.top;
                quadrantBounds.left = p_parentBounds.width / 2;
                break;
            case 2:  // NW
                quadrantBounds.top = p_parentBounds.top;
                quadrantBounds.left = p_parentBounds.left;
                break;
            case 3:  // SW
                quadrantBounds.top = p_parentBounds.height / 2;
                quadrantBounds.left = p_parentBounds.left;
                break;
            case 4:  // SE
                quadrantBounds.top = p_parentBounds.height / 2;
                quadrantBounds.left = p_parentBounds.width / 2;
                break;
            }

            return quadrantBounds;
        }

        Node& getChildNode(int parentLevel, Box<float> parentBounds, int quadrant) {
            static Node temp = Node(parentLevel + 1, getQuadrantBounds(parentBounds, quadrant));
            return temp;
        }
    };
}

Where Box is just a helper class that contains some helper methods for rectangular shapes and collision detection. Any help would be greatly appreciated!

c++

pointers

access-violation

quadtree

0 Answers

Your Answer

Accepted video resources