UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

228 lines (168 loc) • 6.09 kB
import { array_quick_sort_by_comparator } from "../../../../core/collection/array/array_quick_sort_by_comparator.js"; import { compose_tile_address } from "./tile/compose_tile_address.js"; import { finger_print_to_tile_address } from "./tile/finger_print_to_tile_address.js"; import { tile_address_to_finger_print } from "./tile/tile_address_to_finger_print.js"; export class VirtualTextureUsage { #counts_intrinsic = new Uint32Array(0); /** * Stores indices of tiles with usage > 0 * @type {Uint32Array} */ #occupancy = new Uint32Array(0); /** * Points at the end of the occupancy list * @type {number} */ #occupancy_cursor = 0; get counts() { return this.#counts_intrinsic; } #max_mip_level = 0; set max_mip_level(v) { this.#max_mip_level = v; this.#ensureCapacity(); } get max_mip_level() { return this.#max_mip_level; } /** * * @return {Uint32Array} */ get occupancy() { return this.#occupancy; } /** * * @return {number} */ get occupancy_count() { return this.#occupancy_cursor; } /** * */ #ensureCapacity() { const size = compose_tile_address(this.#max_mip_level + 1, 0, 0); if (this.#counts_intrinsic.length >= size) { // already large enough return; } this.#counts_intrinsic = new Uint32Array(size); this.#occupancy = new Uint32Array(size); } /** * * @param {number} fingerprint * @returns {number} */ getCountByFingerprint(fingerprint) { const index = finger_print_to_tile_address(fingerprint); return this.#counts_intrinsic[index]; } /** * * @param {number} mip * @param {number} x * @param {number} y * @returns {number} */ getCountBy(mip, x, y) { const address = compose_tile_address(mip, x, y); return this.#counts_intrinsic[address]; } clear() { this.#counts_intrinsic.fill(0); this.#occupancy_cursor = 0; } /** * Given existing usage, award same usage to all tiles up the chain all the way to the root * This helps avoid popping and ensures that when there's not enough space - at least the higher level LOD tiles are available * @param {number} bias added to the ancestor tiles to make them more likely to be loaded */ promoteAncestors(bias = 1) { // traverse mip pyramid in reverse, so we can push counts to parents one level at a time const max_mip = this.#max_mip_level; for (let mip = max_mip; mip > 0; mip--) { const mip_resolution = 1 << mip; for (let y = 0; y < mip_resolution; y++) { for (let x = 0; x < mip_resolution; x++) { const count = this.getCountBy(mip, x, y); if (count <= 0) { continue; } // get higher-level lod const parent_level = mip - 1; const parent_x = x >>> 1; const parent_y = y >>> 1; const parent_index = compose_tile_address(parent_level, parent_x, parent_y); const parent_count = this.#counts_intrinsic[parent_index]; this.#counts_intrinsic[parent_index] = parent_count + count + bias; if (parent_count === 0) { this.#append_to_occupancy(parent_index); } } } } } /** * * @param {number} address */ #append_to_occupancy(address) { this.#occupancy[this.#occupancy_cursor] = address; this.#occupancy_cursor++; } sortOccupancyByLOD() { /** * * @param {number} a * @param {number} b * @return {number} */ const compare = (a, b) => { const fingerprint_a = tile_address_to_finger_print(a); const fingerprint_b = tile_address_to_finger_print(b); // read MIP levels const mip_a = (fingerprint_a >> 24) & 0xFF; const mip_b = (fingerprint_b >> 24) & 0xFF; const mip_delta = mip_a - mip_b; if (mip_delta !== 0) { return mip_delta; } const intrinsic = this.#counts_intrinsic; const count_a = intrinsic[a]; const count_b = intrinsic[b]; return count_b - count_a; }; array_quick_sort_by_comparator(this.#occupancy, compare, null, 0, this.#occupancy_cursor - 1); } /** * * @param {Uint8Array} data usage texture data */ fromTexture(data) { this.clear(); const data_size = data.length; this.#ensureCapacity(); const counts = this.#counts_intrinsic; for (let offset = 0; offset < data_size; offset += 4) { if (data[offset + 3] === 0) { // no data continue; } const mip_level = data[offset]; // we create a mask to make sure we don't end up with invalid tile position values const max_tile_value = (1 << mip_level) - 1; const tile_x = data[offset + 1] & max_tile_value; const tile_y = data[offset + 2] & max_tile_value; const tile_address = compose_tile_address(mip_level, tile_x, tile_y); const previous_count = counts[tile_address]; if (previous_count === 0) { // first occurrence, mark occupancy this.#append_to_occupancy(tile_address); } counts[tile_address] = previous_count + 1; } } }