tiby312 / broccoli-project

MIT License
79 stars 2 forks source link

Broccoli

Crates.io docs.rs Crates.io

Broccoli is a 2D broad-phase collision detection library.

The base data structure is a hybrid between a KD Tree and Sweep and Prune.

Checkout it out on github and on crates.io. Documentation at docs.rs.

Screenshot

Screen capture from the inner analysis/demo-web project.

screenshot

Example

use broccoli::rect;
fn main() {
    let mut inner1 = 0;
    let mut inner2 = 0;
    let mut inner3 = 0;

    // Rect is stored directly in tree,
    // but inner is not.
    let mut aabbs = [
        (rect(00, 10, 00, 10), &mut inner1),
        (rect(15, 20, 15, 20), &mut inner2),
        (rect(05, 15, 05, 15), &mut inner3),
    ];

    // Construct tree by doing many swapping of elements
    let mut tree = broccoli::Tree::new(&mut aabbs);

    // Find all colliding aabbs.
    tree.find_colliding_pairs(|a, b| {
        // We aren't given &mut T reference, but instead of AabbPin<&mut T>.
        // We call unpack_inner() to extract the portion that we are allowed to mutate.
        // (We are not allowed to change the bounding box while in the tree)
        **a.unpack_inner() += 1;
        **b.unpack_inner() += 1;
    });

    assert_eq!(inner1, 1);
    assert_eq!(inner2, 1);
    assert_eq!(inner3, 2);
}

Cached Key Example

For more convinience you can use the cached_key interface:

fn main() {
    let mut inner = [0, 4, 8];

    broccoli::from_cached_key!(tree, &mut inner, |&a| broccoli::rect(a, a + 5, 0, 10));

    tree.find_colliding_pairs(|a, b| {
        broccoli::unpack!(a, b);
        *a += 1;
        *b += 1;
    });

    // bboxes 1st and 2nd intersect, as well as 2nd and 3rd.
    assert_eq!(inner, [0 + 1, 4 + 2, 8 + 1]);
}

Size of T in Tree

During construction, the elements of a tree are swapped around a lot. Therefore if the size of T is too big, the performance can regress a lot! To combat this, consider using the semi-direct or even indirect layouts listed below. The Indirect layout achieves the smallest element size (just one pointer), however it can suffer from a lot of cache misses of large problem sizes. The Semi-direct layout is more cache-friendly but can use more memory. See more in the optimizations section below. In almost all cases you want to use the Semi-direct layout.

Memory layout

The tree is composed of nodes that each point to a slice of all the aabbs. The nodes are arranged in pre-order. The aabbs slices are also arranged in pre-order.

Cache results

Functions to cache colliding pairs are provieded by the broccoli-ext crate.

3D?

Not supported, but you can use broccoli to partition 2 dimensions and use something else for the 3rd. You could just check if the elements intersect on the z axis inside of the callback function. Alternatively, you could split the z axis into planes and use broccoli on each plane. I think in a lot of cases, the problem is "mostly 2D" in that the distribution lies mostly on a 2d plane, so it might actually be faster to use a more 2d centric collision system.

Optimisation

I've focused mainly on making finding colliding pairs as fast as possible primarily in distributions where there are a lot of overlapping aabbs.

Quick rundown of what i've spent effort on and a rough estimate of performance cost of each algorithm in general.

Algorithm Cost Effort spent
Construction 7 10
Colliding Pairs 8 10
Collide With 3 2
knearest 1 2
raycast 1 2
rect 1 2
nbody 10 1

Numbers are out of 10 and are just rough made up numbers. For more in-depth analysis, see the output of the inner analysis/report-web/plot-gen at: https://tiby312.github.io/broccoli-project/

See legacy report (I havent updated it in a while) from analysis/report-legacy at: broccoli book.

Name

If you shorten "broad-phase collision" to "broad colli" and say it fast, it sounds like broccoli. Broccoli are also basically small trees and broccoli uses a tree data structure.