@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
387 lines (297 loc) • 10.7 kB
JavaScript
import { assert } from "../../../assert.js";
import { UINT32_MAX } from "../../../binary/UINT32_MAX.js";
import { clamp } from "../../../math/clamp.js";
import { BinaryElementPool } from "../../3d/topology/struct/binary/BinaryElementPool.js";
export const NULL_POINTER = UINT32_MAX;
/**
* Single-linked list
* Pointer to next NODE in the same grid cell
* @type {number}
*/
const COLUMN_NODE_SAME_NODE_NEXT = 0;
/**
* Single-linked list
* Pointer to next NODE containing the same element
* @type {number}
*/
const COLUMN_NODE_SAME_ELEMENT_NEXT = 1;
/**
* Pointer to ELEMENT
* @type {number}
*/
const COLUMN_NODE_ELEMENT = 2;
/**
* Points to grid cell that stores this node
* @type {number}
*/
const COLUMN_NODE_GRID_INDEX = 3;
const COLUMN_ELEMENT_DATA = 0;
const COLUMN_ELEMENT_NODE_FIRST = 1;
export const COLUMN_ELEMENT_X0 = 2;
export const COLUMN_ELEMENT_Y0 = 3;
export const COLUMN_ELEMENT_X1 = 4;
export const COLUMN_ELEMENT_Y1 = 5;
export class SpatialHashGrid {
#element_pool = new BinaryElementPool(24)
#node_pool = new BinaryElementPool(16);
#grid_data = new Uint32Array(0);
#size_x = 0;
#size_y = 0;
#scale = 1;
#scale_inverse = 1;
get size_x() {
return this.#size_x;
}
get size_y() {
return this.#size_y;
}
get node_pool() {
return this.#node_pool;
}
/**
*
* @return {BinaryElementPool}
*/
get element_pool() {
return this.#element_pool;
}
get scale() {
return this.#scale;
}
get scale_inverse() {
return this.#scale_inverse;
}
constructor(size_x = 1, size_y = 1, scale = 8) {
assert.notNaN(scale, "scale");
assert.isFiniteNumber(scale, 'scale');
assert.greaterThan(scale, 0, 'scale must be greater than 0');
this.#size_x = size_x;
this.#size_y = size_y;
this.#scale = scale;
this.#scale_inverse = 1 / scale;
this.#build();
}
#build() {
this.#grid_data = new Uint32Array(this.#size_x * this.#size_y);
this.#grid_data.fill(NULL_POINTER);
}
element_allocate() {
const element = this.#element_pool.allocate();
return element;
}
element_set_user_data(element, user_data) {
const pool = this.#element_pool;
const address = pool.element_word(element);
pool.data_uint32[address + COLUMN_ELEMENT_DATA] = user_data;
}
element_get_user_data(element) {
const pool = this.#element_pool;
const address = pool.element_word(element);
return pool.data_uint32[address + COLUMN_ELEMENT_DATA];
}
/**
* You have to remove the element from the grid before changing its bounds
* @param {number} element
* @param {number} x0
* @param {number} y0
* @param {number} x1
* @param {number} y1
*/
element_set_bounds_primitive(element, x0, y0, x1, y1) {
const pool = this.#element_pool;
const address = pool.element_word(element);
const float32 = pool.data_float32;
float32[address + COLUMN_ELEMENT_X0] = x0;
float32[address + COLUMN_ELEMENT_Y0] = y0;
float32[address + COLUMN_ELEMENT_X1] = x1;
float32[address + COLUMN_ELEMENT_Y1] = y1;
}
/**
*
* @param {number} element
*/
element_release(element) {
this.#element_pool.release(element);
}
/**
*
* @param {number} element
*/
element_insert(element) {
const pool = this.#element_pool;
const address = pool.element_word(element);
const uint32 = pool.data_uint32;
// make sure that we don't point at anything yet
uint32[address + COLUMN_ELEMENT_NODE_FIRST] = NULL_POINTER;
const float32 = pool.data_float32;
const x0 = float32[address + COLUMN_ELEMENT_X0];
const y0 = float32[address + COLUMN_ELEMENT_Y0];
const x1 = float32[address + COLUMN_ELEMENT_X1];
const y1 = float32[address + COLUMN_ELEMENT_Y1];
// convert to grid coordinates
const grid_x0 = Math.floor(x0 * this.#scale_inverse);
const grid_y0 = Math.floor(y0 * this.#scale_inverse);
const grid_x1 = Math.floor(x1 * this.#scale_inverse);
const grid_y1 = Math.floor(y1 * this.#scale_inverse);
for (let y = grid_y0; y <= grid_y1; y++) {
for (let x = grid_x0; x <= grid_x1; x++) {
this.#element_insert_into_cell(element, x, y);
}
}
}
/**
*
* @param {number} node
* @return {number}
*/
node_get_element(node) {
const address = this.#node_pool.element_word(node);
return this.#node_pool.data_uint32[address + COLUMN_NODE_ELEMENT];
}
/**
* Next element in the linked list of cell's nodes
* @param {number} node
* @return {number}
*/
node_get_same_cell_next_node(node) {
const address = this.#node_pool.element_word(node);
return this.#node_pool.data_uint32[address + COLUMN_NODE_SAME_NODE_NEXT];
}
/**
*
* @param {number} cell_index
* @return {number}
*/
cell_get_first_node(cell_index) {
assert.lessThan(cell_index, this.#size_x * this.#size_y);
return this.#grid_data[cell_index]
}
/**
*
* @param {number} x
* @param {number} y
* @return {number}
*/
world_position_to_cell_index(x, y) {
const grid_x = clamp(Math.floor(x * this.#scale_inverse), 0, this.#size_x - 1);
const grid_y = clamp(Math.floor(y * this.#scale_inverse), 0, this.#size_y - 1);
return this.cell_position_to_index(grid_x, grid_y);
}
/**
*
* @param {number} x
* @param {number} y
* @returns {number}
*/
cell_position_to_index(x, y) {
assert.isNonNegativeInteger(x, 'x');
assert.isNonNegativeInteger(y, 'y');
return y * this.#size_x + x;
// const cell_count = this.#size_x * this.#size_y;
//
// const _x = split_by_2(x);
// const _y = split_by_2(y);
//
// const hash = _x | (_y << 1);
//
// return hash % cell_count;
}
/**
*
* @param {number} cell_x
* @param {number} cell_y
* @return {boolean}
*/
is_cell_empty(cell_x, cell_y) {
assert.isNonNegativeInteger(cell_x, 'cell_x');
assert.lessThan(cell_x, this.#size_x, 'cell_x overflow');
assert.isNonNegativeInteger(cell_y, 'cell_y');
assert.lessThan(cell_y, this.#size_y, 'cell_y overflow');
const index = this.cell_position_to_index(cell_x, cell_y);
const grid = this.#grid_data;
return grid[index] === NULL_POINTER;
}
/**
*
* @param {number} element
* @param {number} grid_x
* @param {number} grid_y
*/
#element_insert_into_cell(element, grid_x, grid_y) {
// create a node
const node_pool = this.#node_pool;
const node = node_pool.allocate();
const node_address = node_pool.element_word(node);
const node_data_uint32 = node_pool.data_uint32;
node_data_uint32[node_address + COLUMN_NODE_ELEMENT] = element;
// link node to the element
const element_pool = this.#element_pool;
const element_address = element_pool.element_word(element);
const element_data_uint32 = element_pool.data_uint32;
const element_first_node = element_data_uint32[element_address + COLUMN_ELEMENT_NODE_FIRST];
// point to previous first
node_data_uint32[node_address + COLUMN_NODE_SAME_ELEMENT_NEXT] = element_first_node;
element_data_uint32[element_address + COLUMN_ELEMENT_NODE_FIRST] = node;
// link node to the cell
const cell_index = this.cell_position_to_index(grid_x, grid_y);
node_data_uint32[node_address + COLUMN_NODE_GRID_INDEX] = cell_index;
const grid_data = this.#grid_data;
const cell_first_node = grid_data[cell_index];
node_data_uint32[node_address + COLUMN_NODE_SAME_NODE_NEXT] = cell_first_node;
grid_data[cell_index] = node;
}
/**
*
* @param {number} cell_index
* @param {number} node
* @returns {boolean}
*/
#grid_cell_remove_node(cell_index, node) {
assert.isNonNegativeInteger(cell_index, 'cell_index');
assert.isNonNegativeInteger(node, 'node');
const pool = this.#node_pool;
const node_data_uint32 = pool.data_uint32;
const grid_data = this.#grid_data;
let n = grid_data[cell_index]
let previous = NULL_POINTER;
while (n !== NULL_POINTER) {
const address = pool.element_word(n);
const next = node_data_uint32[address + COLUMN_NODE_SAME_NODE_NEXT];
if (n === node) {
// found the node to cut
if (previous === NULL_POINTER) {
// first element in the list
grid_data[cell_index] = next;
} else {
const previous_address = pool.element_word(previous);
node_data_uint32[previous_address + COLUMN_NODE_SAME_NODE_NEXT] = next;
}
return true;
}
previous = n;
n = next;
}
return false;
}
/**
*
* @param {number} element
*/
element_remove(element) {
assert.isNonNegativeInteger(element, 'element');
const element_pool = this.#element_pool;
const address = element_pool.element_word(element);
let node = element_pool.data_uint32[address + COLUMN_ELEMENT_NODE_FIRST];
const node_pool = this.#node_pool;
const node_data_uint32 = node_pool.data_uint32;
do {
const node_address = node_pool.element_word(node);
const next = node_data_uint32[node_address + COLUMN_NODE_SAME_ELEMENT_NEXT];
const grid_index = node_data_uint32[node_address + COLUMN_NODE_GRID_INDEX];
// cut this node from CELL list
this.#grid_cell_remove_node(grid_index, node);
// advance onto next
node = next;
} while (node !== NULL_POINTER)
}
}