gibbon.js
Version:
Actor/Component system for use with pixi.js.
258 lines (199 loc) • 6.6 kB
text/typescript
import { GridCell } from './grid-cell';
import { GridRange } from './grid-range';
/**
* Tyoe for objects stored in Grid.
*/
export type Positionable = { x: number, y: number, width: number, height: number, flags?: number, hitFlags?: number };
/**
* Grid for tracking Actor location and hit tests.
*/
export class Grid<T extends Positionable> {
private rows: number;
private cols: number;
private _cells: GridCell<T>[][];
private width: number;
private height: number;
private cellWidth: number;
private cellHeight: number;
constructor(props: {
/**
* Width of entire grid.
*/
width: number,
/**
* height of entire grid.
*/
height: number,
rows?: number,
cols?: number
}
) {
this.width = props.width;
this.height = props.height;
this.rows = props.rows ?? 20;
this.cols = props.cols ?? 20;
this.cellWidth = this.width / this.cols;
this.cellHeight = this.height / this.rows;
this._cells = this.initTiles();
}
/**
* Add items to range of grid cells.
* @param range
* @param item
*/
public addToRange(range: GridRange, item: T) {
const maxR = range.maxRow;
const maxC = range.maxCol;
for (let r = range.minRow; r <= maxR; r++) {
for (let c = range.minCol; c <= maxC; c++) {
this._cells[r][c].addItem(item);
}
}
}
public removeFromRange(range: GridRange, item: T) {
const maxR = range.maxRow;
const maxC = range.maxCol;
for (let r = range.minRow; r <= maxR; r++) {
for (let c = range.minCol; c <= maxC; c++) {
this._cells[r][c].removeItem(item);
}
}
}
/**
* Get hits matching item.
* @param results
*/
public getHits(item: T, results: T[] = [], range?: GridRange) {
range = range ?? this.getRange(item);
for (let r = range.minRow; r <= range.minRow; r++) {
for (let c = range.minCol; c <= range.maxCol; c++) {
this._cells[r][c].getHits(item, results);
}
}
return results;
}
/**
* Get the range of an item.
* @param item
* @param result - Optional grid range in which to place results.
*/
public getRange(item: T, result?: GridRange) {
result = result ?? new GridRange();
let v = Math.floor((item.y - item.height / 2) / this.cellHeight);
if (v < 0) {
v = 0;
} else if (v >= this.rows) {
v = this.rows - 1;
}
result.minRow = v;
v = Math.floor((item.y + item.height / 2) / this.cellHeight);
if (v < 0) {
v = 0;
} else if (v >= this.rows) {
v = this.rows - 1;
}
result.maxRow = v;
v = Math.floor((item.x - item.width / 2) / this.cellWidth);
if (v < 0) {
v = 0;
} else if (v >= this.cols) {
v = this.cols - 1;
}
result.minCol = v;
v = Math.floor((item.x + item.width / 2) / this.cellWidth);
if (v < 0) {
v = 0;
} else if (v >= this.cols) {
v = this.cols - 1;
}
result.maxCol = v;
return result;
}
/**
* Get objects within grid range.
* @param range
* @param results
*/
public getItems(range: GridRange, results: T[] = []) {
const maxR = range.maxRow;
const maxC = range.maxCol;
for (let r = range.minRow; r <= maxR; r++) {
for (let c = range.minCol; c <= maxC; c++) {
this._cells[r][c].getItems(results);
}
}
return results;
}
public addItem(item: T) {
let minRow = Math.floor((item.y - item.height / 2) / this.cellHeight);
if (minRow < 0) {
minRow = 0;
} else if (minRow >= this.rows) {
minRow = this.rows - 1;
}
let maxRow = Math.floor((item.y + item.height / 2) / this.cellHeight);
if (maxRow < 0) {
maxRow = 0;
} else if (maxRow >= this.rows) {
maxRow = this.rows - 1;
}
let minCol = Math.floor((item.x - item.width / 2) / this.cellWidth);
if (minCol < 0) {
minCol = 0;
} else if (minCol >= this.cols) {
minCol = this.cols - 1;
}
let maxCol = Math.floor((item.x + item.width / 2) / this.cellWidth);
if (maxCol < 0) {
maxCol = 0;
} else if (maxCol >= this.cols) {
maxCol = this.cols - 1;
}
for (let r = minRow; r <= maxRow; r++) {
for (let c = minCol; c <= maxCol; c++) {
this._cells[r][c].addItem(item);
}
}
}
public removeItem(go: T) {
let minRow = Math.floor((go.y - go.height / 2) / this.cellHeight);
if (minRow < 0) {
minRow = 0;
} else if (minRow >= this.rows) {
minRow = this.rows - 1;
}
let maxRow = Math.floor((go.y + go.height / 2) / this.cellHeight);
if (maxRow < 0) {
maxRow = 0;
} else if (maxRow >= this.rows) {
maxRow = this.rows - 1;
}
let minCol = Math.floor((go.x - go.width / 2) / this.cellWidth);
if (minCol < 0) {
minCol = 0;
} else if (minCol >= this.cols) {
minCol = this.cols - 1;
}
let maxCol = Math.floor((go.x + go.width / 2) / this.cellWidth);
if (maxCol < 0) {
maxCol = 0;
} else if (maxCol >= this.cols) {
maxCol = this.cols - 1;
}
for (let r = minRow; r <= maxRow; r++) {
for (let c = minCol; c <= maxCol; c++) {
this._cells[r][c].removeItem(go);
}
}
}
private initTiles() {
const a = new Array<Array<GridCell<T>>>(this.rows);
for (let r = 0; r < this.rows; r++) {
const b = a[r] = new Array<GridCell<T>>(this.cols);
for (let c = 0; c < this.cols; c++) {
b[c] = new GridCell<T>();
}
}
return a;
}
}