@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
228 lines (168 loc) • 6.09 kB
JavaScript
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;
}
}
}