I would start with a 2D grid.
You start by defining world-space dimensions for the cells of the grid. The ideal choice of dimensions is game dependent, and relates to the size and distribution of objects in your world. Represent a GridCell as a vector of object references. Represent a Grid in such a way that you can index GridCell objects by their grid-space coordinates. You can determine the grid-space coordinates for any point with a very simple function. Something like this:
-- given a grid of infinite squares of cell_dim,
-- gets the grid coordinates for a point in the world
function get_grid_coords(pos, cell_dim)
return floor(pos.x / cell_dim), floor(pos.y / cell_dim)
end
You'll use these coordinates to index into your Grid.
Every time something moves, use this function to determine the grid-space coordinates, and add a reference to this moved object to the GridCell that it currently resides in. Of course, also remove it from any GridCell that it no longer resides in.
THEN
To find nearby objects, you first find the grid-space coordinates of an object, and then you use them to index into the Grid to get the GridCell, which contains references to all of the objects in the same GridCell as the object in question.