UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

387 lines (297 loc) • 10.7 kB
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) } }