I've been trying to learn C and have become a little more comfortable in it recently. I am building a 2d space shooting game (kind of Galaga inspired, written using SDL) and decided to use Quad Trees for collision detection. Yes, I am aware that QTs are overkill for this, but I decided to use it as a learning experience and have just been messing around.
My quad trees appear to be working and when I render the boundaries of the tree nodes that have values in them I see that things look normal. All is well. My question however, isn't really about how Quad Trees work but rather how to handle them in a strict language like C.
The QT implementation depends on a lower level struct type struct Enemy
. So quadtree.h
includes enemy.h
. But enemy.h
also includes quadtree.h
in order to add an enemy to the tree as they get updated every animation frame. This of course creates a circular dependency. I realized the issue was that the Quad Tree wasn't generic and didn't follow dependency inversion (higher level modules shouldn't depend on lower level modules). I need to find a way to make the Quad Tree generic enough to store any type, but at the same time provide a method for retrieving pointers to the objects it stores. Currently the tree only stores the positional data in the form of SDL_FRect
. But that gives me no way of accessing the parent struct Enemy
. But if I store the entire enemy, then I'm introducing a circular dependency. I've considered a few possible solutions and am looking for feedback on which of these solutions are good. Alternatively, if none of them are then I'm open to suggestions.
Inspired by qsort.c
in stdlib.h
I could pass function pointers to my node adding function that provides custom logic for A: seeing if an object is within the Quad Tree node boundary at all and B: finding what quadrant/s the object spans. Then just store void pointers instead of struct Enemy
objects in the values array of the node.
I could store 2 arrays in each QT node. One that just contains the positioning information and the other that stores void *
s to the actual object. Then when doing collision detection I can just reference the second list and cast the void pointer.
Instead of storing just positional data I could store an ID of sorts in each value a node stores. Then track IDs and Enemies in another data structure. A hashmap or 2d array. Then when we get a hit or collision, I can look up what item I hit through the secondary data structure. Honestly, I don't really love this idea as it requires me to either loop through all the enemies to find which one I hit which feels inefficient. Also, it requires extra memory overhead and logic. I'm hoping there's a better solution.
The definition of a Quad Tree node is as follows:
struct QTNode {
SDL_FRect *values[MAX_NODE_VALUES];
struct QTNode *northwest;
struct QTNode *northeast;
struct QTNode *southeast;
struct QTNode *southwest;
SDL_FRect boundary;
int is_leaf;
int values_count;
};
An enemy is defined like this:
struct Enemy {
float x, y;
SDL_FRect rect;
SDL_Texture *texture;
int health;
};
All of the functions and logic that go along with the Quad Tree are found here
Any help is appreciated. And also, if you feel like it, just general feedback on the code would be great. I'm trying to really learn and understand C. Thanks
You don't actually need to include quadtree.h
in enemy.h
.
All the compiler needs to know to understand
void render_enemies(struct EnemyCluster *enemy_cluster, SDL_Renderer *renderer,
struct QTNode *q_tree);
is that QTNode is some kind of struct. The implementation may need to know what's in it, the declaration does not.
So you can do:
struct QTNode;
void render_enemies(struct EnemyCluster *enemy_cluster, SDL_Renderer *renderer,
struct QTNode *q_tree);
and drop the #include "quadtree.h"
from the header.
See also: "forward declaration".
You then need to include it in enemy.c
but at least the circle in the includes has been interrupted.
And if you want to have a struct Enemy *enemy;
pointer inside QTNode
you don't need to include enemy.h
for it there either. Again, if you want to declare a pointer to a struct, you don't need to immediately explain what's in it.