Code Monkey home page Code Monkey logo

unityoctree's Introduction

Please note: This repository is no longer maintained.

UnityOctree

A dynamic octree implementation for Unity written in C#.
Originally written for my game Scraps but intended to be general-purpose.

There are two octree implementations here:
BoundsOctree stores any type of object, with the object boundaries defined as an axis-aligned bounding box. It's a dynamic octree and can also be a loose octree.
PointOctree is the same basic implementation, but stores objects as a point in space instead of bounds. This allows some simplification of the code. It's a dynamic octree as well.

Octree: An octree a tree data structure which divides 3D space into smaller partitions (nodes) and places objects into the appropriate nodes. This allows fast access to objects in an area of interest without having to check every object.

Dynamic: The octree grows or shrinks as required when objects are added or removed. It also splits and merges nodes as appropriate. There is no maximum depth. Nodes have a constant (numObjectsAllowed) which sets the amount of items allowed in a node before it splits.

Loose: The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent. This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries. A looseness value of 1.0 will make it a "normal" octree.

A few functions are implemented:

With BoundsOctree, you can pass in bounds and get a true/false answer for if it's colliding with anything (IsColliding), or get a list of everything it's collising with (GetColliding). With PointOctree, you can cast a ray and get a list of objects that are within x distance of that ray (GetNearby). You may also get a list of objects that are within x distance from a specified origin point.

It shouldn't be too hard to implement additional functions if needed. For instance, PointOctree could check for points that fall inside a given bounds.

Considerations:

Tree searches are recursive, so there is technically the potential for a stack overflow on very large trees. The minNodeSize parameter limits node side and hence the depth of the tree, putting a cap on recursion.

I tried switching to an iterative solution using my own stack, but creating and manipulating the stack made the results generally slower than the simple recursive solution. However, I wouldn't be surprised it someone smarter than me can come up with a faster solution.

Another note: You may notice when viewing the bounds visualisation that the child nodes' outer edges are all inside the parent nodes. But loose octrees are meant to make the inner nodes bigger... aren't they? The answer is yes, but the parent nodes are also bigger, and e.g. ((1.2 * 10) - 10) is bigger than ((1.2 * 5) - 5), so the parent node ends up being bigger overall.

This seems to be the standard way that loose octrees are done. I did an experiment: I tried making the child node dimensions looseness * the parent's actual size, instead of looseness * the parent's base size before looseness is applied. This seems more intuitively correct to me, but performance seems to be about the same.

Example Usage

Create An Octree

// Initial size (metres), initial centre position, minimum node size (metres), looseness
BoundsOctree<GameObject> boundsTree = new BoundsOctree<GameObject>(15, MyContainer.position, 1, 1.25f);
// Initial size (metres), initial centre position, minimum node size (metres)
PointOctree<GameObject> pointTree = new PointOctree<GameObject>(15, MyContainer.position, 1);
  • Here I've used GameObject, but the tree's content can be any type you like (as long as it's all the same type
  • The initial size should ideally cover an area just encompassing all your objects. If you guess too small, the octree will grow automatically, but it will be eight times the size (double dimensions), which could end up covering a large area unnecessarily. At the same time, the octree will be able to shrink down again if the outlying objects are removed. If you guess an initial size that's too big, it won't be able to shrink down, but that may be the safer option. Don't worry too much: In reality the starting value isn't hugely important for performance.
  • The initial position should ideally be in the centre of where your objects are.
  • The minimum node size is effectively a depth limit; it limits how many times the tree can divide. If all your objects are e.g. 1m+ wide, you wouldn't want to set it smaller than 1m.
  • The best way to choose a looseness value is to try different values (between 1 and maybe 1.5) and check the performance with your particular data. Generally around 1.2 is good.

Add And Remove

boundsTree.Add(myObject, myBounds);
boundsTree.Remove(myObject);

pointTree.Add(myObject, myVector3);
boundsTree.Remove(myObject);
  • The object's type depends on the tree's type.
  • The bounds or point determine where it's inserted.

Built-in Functions

bool isColliding = boundsTree.IsColliding(bounds);
List<GameObject> collidingWith = new List<GameObject>();
boundsTree.GetColliding(collidingWith, bounds);
  • Where GameObject is the type of the octree
pointTree.GetNearby(myRay, 4);
  • Where myRay is a Ray
  • In this case we're looking for any point within 4m of the closest point on the ray
pointTree.GetNearby(myPos, 4);

Debugging Visuals

Visualisation example.

void OnDrawGizmos() {
	boundsTree.DrawAllBounds(); // Draw node boundaries
	boundsTree.DrawAllObjects(); // Draw object boundaries
	boundsTree.DrawCollisionChecks(); // Draw the last *numCollisionsToSave* collision check boundaries

	pointTree.DrawAllBounds(); // Draw node boundaries
	pointTree.DrawAllObjects(); // Mark object positions
}
  • Must be in Unity's OnDrawGizmos() method in a class that inherits from MonoBehaviour
  • Point octrees need the marker.tif file to be in your Unity /Assets/Gizmos subfolder for DrawAllObjects to work

Potential Improvements

A significant portion of the octree's time is taken just to traverse through the nodes themselves. There's potential for a performance increase there, maybe by linearising the tree - that is, representing all the nodes as a one-dimensional array lookup.

unityoctree's People

Contributors

amirebrahimi avatar danzel avatar forestrf avatar hgdebarba avatar mcserep avatar mhabrah avatar mtschoen avatar mtschoen-unity avatar nition avatar nonakesh avatar omgwtfgames avatar txzeenath avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unityoctree's Issues

ShrinkIfPossible method bug

The implementation of ShrinkIfPossible(float minLength) requires that there is only 1 child in the node, as mentioned in the documentation. However, the current implementation makes no such check and in some instances of node removal, some nodes that shouldn't be removed end of getting implicitly removed (this is basically a memory leak).

The fix is simple:

public BoundsOctreeNode<T> ShrinkIfPossible(float minLength) {
	// NEW CODE
	if (objects.Count >= 2) {
		return this;
	}
	// END NEW CODE

	if (BaseLength < (2 * minLength)) {
		return this;
	}
	if (objects.Count == 0 && (children == null || children.Length == 0)) {
		return this;
	}
//.... the rest of the code

How to initiate it?

Hello, I'm having trouble with initiating it and would appreciate some help with the first steps. I suppose this isn't easy stuff for newbies, but I still would like to give it a try if I could just get as far as adding objects (or vertices to be precise) to a boundsTree / pointTree.

I have created an octree:

Renderer rendy = gObject.GetComponent< Renderer>();
Vector3 rCenter = rendy.bounds.center;					
// Initial size (metres), initial centre position, minimum node size (metres), looseness
boundsTree = new BoundsOctree<Vector3>(15, rCenter, 1, 1.25f);
// Initial size (metres), initial centre position, minimum node size (metres)
pointTree = new PointOctree<Vector3>(15, rCenter, 1);

I just want to add the vertices of a gameobject's mesh to an octree. I'm not sure what values I need to use when adding/removing as presented here:

boundsTree.Add(myObject, myBounds);
boundsTree.Remove(myObject);

pointTree.Add(myObject, myVector3);
boundsTree.Remove(myObject);

I'm assuming I need to iterate through the mesh's vertices and then add them to the octree, but I'm confused about object type and "The bounds or point determine where it's inserted":

Vector3[] vx = myMesh.vertices;

for (int i = 0; i < vx.Length; i++)
{
    boundsTree.Add(objectType?, vx[i]); // Argument 2: cannot convert from 'UnityEngine.Vector3' to 'UnityEngine.Bounds'
    pointsTree.Add(objectType?, vx[i]); // 
}

Another thing is the OnDrawGizmos(): Must all octree references be made within the gizmos script? As I can't seem to figure out how to get the references to the boundsTree / pointTree otherwise.

  • Erlend

BoundsOctreeNode.GetColliding can be faster

When I was experimenting with the Octree I was able to get the search performance down by factor 10 when fiddling with the BoundsOctreeNode.GetColliding() internals.

Specifically:

  • manually doing the bounds check (Unity Bounds allocates and is slow)
  • caching the bound vectors in the node itself (Bounds.max and min are always created in the getter)
  • and working with a pre-allocated list to fill in return objects.

Maybe this helps. Cheers, Jonas

public void GetColliding(ref Bounds checkBounds, List<T> result) {
    // Are the input bounds at least partially in this node?
    if (!bounds.Intersects(checkBounds)) {
        return;
    // ...
}

public void GetCollidingOptimized(Vector3 positionMin, Vector3 positionMax, ref int[] resultList, ref int resultIndex) {
    if (positionMax.x < BoundsMin.x || positionMin.x > BoundsMax.x) return;
    if (positionMax.y < BoundsMin.y || positionMin.y > BoundsMax.y) return;
    if (positionMax.z < BoundsMin.z || positionMin.z > BoundsMax.z) return;
    // ....
}

public struct BoundsOctreeNodeStruct<T> {
  public struct OctreeObject {
    public Vector3 BoundsMin;
    public Vector3 BoundsMax;
    // ...

Does growing actually work?

If my initialWorldSize was not large enough to include all the points I added, the resulting Octree did not appear to always correctly store the points. Calling GetNearby with a ray at the position of the point and a small maxDistance did not always find that point.

Index Out of Range in "ShrinkIfPossible"

// We have children. Use the appropriate child as the new root node return children[bestFit];

I'm hitting a case where "bestFit" can still be -1 at this line. The culprit appears to be this case:

objects.Count = 0
children.Length = 8

Nodes who have children shouldn't contain any OctreeObject

Hi Nition,
If I understand the code correctly, when adding an object into the octree, if the current node contains enough object, we create children and move all the objects a level deeper. But in "GetNearby" function, I found that if a node has children, it STILL contains all the objects. Its children will have copies of the same objects. Could you please look into this? Or am I misunderstanding anything here?

Point.Normalize is giving stack overflow

Hello.

Thank you very much for the code. In the "Pure C# Implementation" I am getting stack overflow issue when I just ask for Normalised vector (Point) of a Point.

I am missing something ?

Thanks in advance.

has a fast way to update a Object?

Hello!if I want to update an object in octree,for example the object Bounds has update,maybe it should go to another octreeNode. I must call remove function and add function again? or another fast way to do this.

Possible bug with maxDistance

   bounds.Expand(new Vector3(maxDistance, maxDistance, maxDistance));

Shouldn't these be maxDistance*2, since Expand increases the size, but only 1/2 in each of 6 directions?

I noticed this while implementing a GetNearby(Vector3 point) version, not actually seeing a bug from it.

Getting started resoruces?

Hello, im interested in this repo. Can you provide some getting started scene or script where everything is setup for us newbros to just see setup. I need to crate octree that will dynamically update as objects are going through zones. I see this is possible with this but I'm unable to wrap my head around it. Thank you.

Null Reference Error in "ShrinkIfPossible"

I am sometimes getting a null reference error in the function
public PointOctreeNode<T> ShrinkIfPossible(float minLength)

on the first line of this block
if (objects.Count == 0 && children.Length == 0) { return this; }

after calling PointOctree.Remove().

Attached is a screen of MonoDevelop hitting a break point in a test block I added. It appears that "objects" can have a count of 0 and "children" can be null at the same time. Note also that inserting this block appears to prevent the issue, but I'm concerned about what side effects it might have as I don't really have a good grasp of the inner workings here.

screen shot 2018-05-02 at 10 22 01 am

This occurs often but not always in my scenario (perhaps 30% of the time), and usually throws null reference errors for a number of frames before stopping. This is a somewhat elaborate project with a strong possibility of timing and chance influencing the scenario here.

I regret that I don't have a clean reproduction case for you :(

ShrinkIfPossible bug

Hi,

the ShrinkIfPossible method is called and possibly a child node becomes root
because all the node's objects can fit the child octant.
Where the objects of the previous root go?
Shouldn't they be added to the child node in the ShrinkIfPossible method?

Update Position

No way to update the position of an octree, has an initial position but no way to change once set?

Shrink Problem

When remove an object, my excute Shrink,
Shink will change the root node, but the objects in the old root losted.

Performance Upgrades

  • - Combine point and bounds logic
  • - Object add method.
  • - Object remove method.
  • - Object move method.
  • - Node splitting on-demand.
  • - Branch based object count tracking.
  • - Automatic merging of branches/leaves.
  • - Custom intersection tests to avoid unity internal calls.
  • - Get rid of dependence on unity's Bounds struct internally.
  • - Instance pooling.
  • - O(1) Object removal.
  • - Implement queries into tree.
  • - Implement queries from objects.
  • - Eliminate recursive methods where plausible.
  • - Documentation.
  • - Clean up/write summaries.
  • - Add queries to testing scene
  • - Add queries to tree visualization

Scrapped:
Linear. Actually proved to be slower with current implementation.
Directional tree growth. Not really necessary. Proper implementation avoids this need.
Tree shrinking. As above.

Feature Request - GetNearby(Vector3 point, int requestedResults)

Using the PointTree, would like a T[] GetNearby(Vector3 point, int requestedResults) method. This would return the requestedResults closest items to point in the tree.

I am going to take a crack at implementing this, but would also appreciate any suggestions/pointers for how one might go about doing this... (i have a "bad" version which simply calls GetNearby with increasing sized maxDistance values until you get the # of results you want)...

Main difference

Hello, thanks a lot for your example!

I have a question. What's the main difference between bound or point-based octree? I've read your documentation (and it's great) and code (sorry, I'm not good in C# so much) but I can't see the purpose...

Point-based octree seems better to me but what are the restrictions?

Thanks for your reply. Good work!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.