kontra
Version:
Kontra HTML5 game development library
348 lines (259 loc) • 9.54 kB
JavaScript
import Quadtree from '../../src/quadtree.js'
import { init, getCanvas } from '../../src/core.js'
// --------------------------------------------------
// quadtree
// --------------------------------------------------
describe('quadtree', () => {
before(() => {
if (!getCanvas()) {
let canvas = document.createElement('canvas');
canvas.width = canvas.height = 600;
init(canvas);
}
});
// --------------------------------------------------
// init
// --------------------------------------------------
describe('init', () => {
it('should create an initial bounding box', () => {
let quadtree = Quadtree();
expect(typeof quadtree.bounds).to.equal('object');
expect(quadtree.bounds.x).to.equal(0);
expect(quadtree.bounds.y).to.equal(0);
expect(quadtree.bounds.width).to.equal(getCanvas().width);
expect(quadtree.bounds.height).to.equal(getCanvas().height);
});
it('should allow you to set the maxDepth and maxObject counts', () => {
let quadtree = Quadtree({
maxDepth: 1,
maxObjects: 10
});
expect(quadtree.maxDepth).to.equal(1);
expect(quadtree.maxObjects).to.equal(10);
});
});
// --------------------------------------------------
// add
// --------------------------------------------------
describe('add', () => {
let quadtree;
beforeEach(() => {
quadtree = Quadtree({
maxObjects: 5,
bounds: {
x: 0,
y: 0,
width: 100,
height: 100
}
});
});
afterEach(() => {
quadtree.clear();
});
it('should add an object to the quadtree', () => {
quadtree.add({id: 1});
expect(quadtree._o.length).to.equal(1);
});
it('should take multiple objects and add them to the quadtree', () => {
quadtree.add({id: 1}, {id: 2}, {id: 3});
expect(quadtree._o.length).to.equal(3);
});
it('should take an array of objects and add them to the quadtree', () => {
quadtree.add([{id: 1}, {id: 2}], {id: 3});
expect(quadtree._o.length).to.equal(3);
});
it('should split the quadtree if there are too many objects', () => {
for (let i = 0; i < 5; i++) {
quadtree.add({id: i});
}
expect(quadtree._o.length).to.equal(5);
expect(quadtree._s.length).to.equal(0);
quadtree.add({id: 4});
expect(quadtree._o.length).to.equal(0);
expect(quadtree._s.length).to.equal(4);
});
it('should make each subnode 1/4 the size of the bounds when split', () => {
for (let i = 0; i < 6; i++) {
quadtree.add({id: i});
}
expect(quadtree._s[0].bounds).to.eql({x: 0, y: 0, width: 50, height: 50});
expect(quadtree._s[1].bounds).to.eql({x: 50, y: 0, width: 50, height: 50});
expect(quadtree._s[2].bounds).to.eql({x: 0, y: 50, width: 50, height: 50});
expect(quadtree._s[3].bounds).to.eql({x: 50, y: 50, width: 50, height: 50});
});
it('should add split objects to their correct subnodes', () => {
let subnode;
let objects = [
{id: 0, x: 15, y: 10, width: 10, height: 10}, // quadrant 0
{id: 1, x: 30, y: 20, width: 10, height: 10}, // quadrant 0
{id: 2, x: 45, y: 30, width: 10, height: 10}, // quadrant 0,1
{id: 3, x: 60, y: 40, width: 10, height: 10}, // quadrant 1,3
{id: 4, x: 75, y: 50, width: 10, height: 10}, // quadrant 3
{id: 5, x: 90, y: 60, width: 10, height: 10} // quadrant 3
];
quadtree.add(objects);
// quadrant 0
subnode = quadtree._s[0]._o;
expect(subnode.length).to.equal(3);
expect(subnode.indexOf(objects[0])).to.not.equal(-1);
expect(subnode.indexOf(objects[1])).to.not.equal(-1);
expect(subnode.indexOf(objects[2])).to.not.equal(-1);
// quadrant 1
subnode = quadtree._s[1]._o;
expect(subnode.length).to.equal(2);
expect(subnode.indexOf(objects[2])).to.not.equal(-1);
expect(subnode.indexOf(objects[3])).to.not.equal(-1);
// quadrant 2
subnode = quadtree._s[2]._o;
expect(subnode.length).to.equal(0);
// quadrant 3
subnode = quadtree._s[3]._o;
expect(subnode.length).to.equal(3);
expect(subnode.indexOf(objects[3])).to.not.equal(-1);
expect(subnode.indexOf(objects[4])).to.not.equal(-1);
expect(subnode.indexOf(objects[5])).to.not.equal(-1);
});
it('should add an object to a subnode if the quadtree is already split', () => {
for (let i = 0; i < 6; i++) {
quadtree.add({id: i});
}
let object = {x: 10, y: 10, width: 10, height: 10};
let subnode = quadtree._s[0]._o;
quadtree.add(object);
expect(subnode.length).to.equal(1);
expect(subnode[0]).to.equal(object);
});
it('shouldn\'t split a quadtree node twice after clearing', () => {
// cause the tree to split
for (let i = 0; i < 6; i++) {
quadtree.add({id: i});
}
let subnodes = quadtree._s;
quadtree.clear();
// cause to split again
for (let i = 0; i < 6; i++) {
quadtree.add({id: i});
}
// since splitting overrides the subnodes with new values this should
// test that the subnodes were left alone after the 2nd split
expect(quadtree._s).to.equal(subnodes);
});
});
// --------------------------------------------------
// clear
// --------------------------------------------------
describe('clear', () => {
let quadtree;
beforeEach(() => {
quadtree = Quadtree({
maxObjects: 5,
bounds: {
x: 0,
y: 0,
width: 100,
height: 100
}
});
});
it('should clear all objects of the quadtree', () => {
for (let i = 0; i < 4; i++) {
quadtree.add({id: i, x: i*15, y: i*10, width: 10, height: 10});
}
quadtree.clear();
expect(quadtree._o.length).to.equal(0);
});
it('should clear all objects in subnodes of the quadtree', () => {
for (let i = 0; i < 9; i++) {
quadtree.add({id: i, x: i*10, y: i*10, width: 10, height: 10});
}
quadtree.clear();
expect(quadtree._o.length).to.equal(0);
expect(quadtree._s[0]._o.length).to.equal(0);
expect(quadtree._s[1]._o.length).to.equal(0);
expect(quadtree._s[2]._o.length).to.equal(0);
expect(quadtree._s[3]._o.length).to.equal(0);
});
});
// --------------------------------------------------
// get
// --------------------------------------------------
describe('get', () => {
let quadtree;
beforeEach(() => {
quadtree = Quadtree({
maxObjects: 5,
bounds: {
x: 0,
y: 0,
width: 100,
height: 100
}
});
});
afterEach(() => {
quadtree.clear();
});
it('should return an object in the same node as the passed object', () => {
let object = {x: 10, y: 10, width: 10, height: 10};
quadtree.add(object);
let getObjects = quadtree.get({x: 30, y: 30, width: 20, height: 20});
expect(getObjects.length).to.equal(1);
expect(getObjects[0]).to.equal(object);
});
it('should return all objects in multiple subnodes if the object intercepts multiple subnodes', () => {
let objects = [
{x: 10, y: 10, width: 10, height: 10},
{x: 60, y: 10, width: 10, height: 10}
];
quadtree.add(objects);
let getObjects = quadtree.get({x: 45, y: 25, width: 20, height: 20});
expect(getObjects.length).to.equal(2);
expect(getObjects[0]).to.equal(objects[0]);
expect(getObjects[1]).to.equal(objects[1]);
});
it('should return objects from leaf nodes', () => {
let objects = [
{x: 0, y: 0, width: 10, height: 10},
{x: 10, y: 10, width: 10, height: 10},
{x: 20, y: 20, width: 10, height: 10},
{x: 30, y: 30, width: 10, height: 10},
{x: 40, y: 40, width: 10, height: 10},
{x: 50, y: 50, width: 10, height: 10},
{x: 60, y: 10, width: 10, height: 10}
];
quadtree.add(objects);
let getObjects = quadtree.get({x: 5, y: 25, width: 20, height: 20});
expect(getObjects.length).to.equal(5);
expect(getObjects[0]).to.equal(objects[0]);
expect(getObjects[1]).to.equal(objects[1]);
expect(getObjects[2]).to.equal(objects[2]);
expect(getObjects[3]).to.equal(objects[3]);
expect(getObjects[4]).to.equal(objects[4]);
});
it('should not return the passed object', () => {
let objects = [
{x: 10, y: 10, width: 10, height: 10},
{x: 20, y: 10, width: 10, height: 10}
];
quadtree.add(objects);
let getObjects = quadtree.get(objects[0]);
expect(getObjects.length).to.equal(1);
expect(getObjects[0]).to.equal(objects[1]);
});
it('should not return the same object more than once', () => {
let objects = [
{x: 0, y: 0, width: 10, height: 10},
{x: 10, y: 10, width: 10, height: 10},
{x: 20, y: 20, width: 10, height: 10},
{x: 30, y: 30, width: 10, height: 10},
{x: 40, y: 40, width: 10, height: 10},
{x: 50, y: 50, width: 10, height: 10},
{x: 60, y: 10, width: 10, height: 10}
];
quadtree.add(objects);
let getObjects = quadtree.get({x: 45, y: 45, width: 20, height: 20});
expect(getObjects.length).to.equal(7);
});
});
});