0

I have an octree that I want to use to perform pathfinding in 3D space. I'm using Morton codes to order the nodes with the intention of being able to easily find the nodes nearby any given node. It appears to work great; any subset of the nodes I grab are clustered relatively close together. Where I'm struggling is finding an efficient way of getting a node's direct neighbors.

An earlier version of the tree had it subdivide the tree until the leaves were all equal sized nodes at the size I wanted. This made it easy to find neighbors as there was a consistent offset between each node, so I could just recalculate the Morton code at the position I expected to find a node, and then retrieve the node based on the code. But I'm trying to be more efficient with the tree by not breaking it down as much in areas where there isn't as much geometry (e.g. where I would get 8 empty children). As a result, I no longer know how many neighbors a given node may have, nor the size of any of the neighbors (other than it being a power of 2).

My initial thought was to search nearby nodes in either direction in the array, which would work, but I'd have to cast too wide a net to be guaranteed to get all neighbors, and I'd have spent time evaluating a ton of nodes that I don't need. A quick test indicates that not even 50 nodes in either direction in the array is guaranteed to get me all neighbors.

The tree is static, so I could perhaps do a one time generation of a node's neighbors using the above brute force method and store the neighbors; but I fear this may increase the initial loading time of the program beyond acceptable levels. Is there an algorithm that can find the neighbors in real-time?

Edit: Reworded the question to clarify what I'm looking for and what I've tried.

user1265215
  • 33
  • 2
  • 8

1 Answers1

0

Yes.

Don't brute-force, go straight to the tile you want.

Provided you have:

  • A Tile toMorton(Point3D location) function which provides a Tile given its real-world location
  • A Point3D fromMorton(Tile t) function that provide the real-world coordinates of a Tile (both available from Wikipaedia I believe)

These are simple, real-time operations.

The naive approach

Simply convert to real-wrold, offset there, and back to Morton space:

Tile originalTile = ...
Point3D originalLocation = fromMorton(original);
Point3D offsetLocation = new Point3D(offsetLocation.x + 1, offsetLocation.y, offsetLocation.y);
Tile offsetTile = toMorton(offsetLocation)

This requires just a few arithmetic operations, so should already be suitable for real-time (more so than brute-forcing).

The Optimised Approach

A quick google search on "morton code neighborhood" led me to a very well written blog entry from volumeseoffun.

I'll provide the best bits here:

for a given (x,y,z) which has a Morton position p; if we want to peek at (x+1,y,z) then the amount by which p must increase, Δp, is dependent only on x and is independent of y and z. This same logic holds for peeking in y and z.

This means we do not need to toMorton / fromMorton all three coordinates

Furthermore (although neither the blog nor its source back it up) it seems that said offset can be precomputed via a lookup table. This gives additional speedup for the realtime capability you're looking for. To me it makes sense given that Morton algo is well-defined and repeatable, so I trust this assumption.

Simply pre-compute the offset table, then from a start tile, use that table to offset yor tile to its real-world neighbour:

int[] offsetX = [ ... compute once ... ]
int[] offsetY = [ ... compute once ... ]
int[] offsetZ = [ ... compute once ... ]
Tile[] allTiles = ... // Init 3D world
public Tile neighbour(Tile original, Direction dir){
     switch(dir){
     case DIR_X_PLUS: return allTiles[original.chunkID + offsetX[original.chunkID];
     case DIR_X_MINUS: return allTiles[original.chunkID - offsetX[original.chunkID];
     case DIR_Y_PLUS: return allTiles[original.chunkID + offsetY[original.chunkID];
     [... etc...]
     default: return original;
     }
}

Hope this helps.

Community
  • 1
  • 1
MrBrushy
  • 680
  • 3
  • 17
  • What you're describing is how I'm doing it currently. What I'm talking about is if the neighbors aren't the same size. For example, currently every node is 2x2x2, so if I'm looking for above neighbors, I get 9 neighbors (directly above, north, east, south, west, and the diagonals). I just need to offset by 2 in the appropriate directions. But what if instead of all 2x2x2 neighbors, one of the neighbors is 8x8x8, because the tree didn't subdivide as much in that area. How do I know I need to adjust the offset, or by how much do I adjust it? Unless I'm missing something about this explanation. – user1265215 Nov 07 '16 at 20:30
  • Huh, I must've had a brain fart there if Imissed that entirely. The answer doesn't stande, but you might still do this, albeit on each level. For that we'd need specifics on how each layer is stored. Is there a Morton function applied independently*per layer*? – MrBrushy Nov 07 '16 at 20:59
  • I think i got your 'net' metaphor: you're sampling in the Morton array, in front and behind of the original tile? That can't work, the naighbour could be arbitrarily far (well, except with limited size tree, but then the limit would likely be close to the entire array.) – MrBrushy Nov 07 '16 at 21:03
  • A Morton code is applied to every node, even nodes that contain other nodes. However, a node currently doesn't possess it's level of subdivision. Would be easy enough to add though. Are you proposing something like performing the offsets for each level of subdivision that could possibly be a neighbor, and taking the combination of the results as the neighbors? – user1265215 Nov 08 '16 at 09:05
  • Still not 100% clear with subdivision. Nodes of different hierarchy levels may share the same real-world location, yet be distinct. Like level-1 block `{(0,0)}` would have a Morton address of `0`, but so would level-2 block `{(0,0),(0,1),(1,0),(1,1)}` etc. This means that when you sub-divide blocks, each layer must have its own Morton array to back it. Is that what you plan to do? If so then yes, it is what I propose, maybe for a new answer. – MrBrushy Nov 08 '16 at 09:56
  • I hadn't considered a Morton array for each level of subdivision, but it makes sense. What about the subdivision do I need to clarify? – user1265215 Nov 08 '16 at 19:40
  • You just did. Yeah I think recursive Morton is the way to go. You'd need sparse arrays to store the deepest levels - otherwise it would defeat the point of having subdivision and not storing the neighbours. I'll have to think about it but alas I will be away for a couple weeks. Feel free to keep us posted on your own endeavours! – MrBrushy Nov 08 '16 at 22:46