285 lines
9.8 KiB
JavaScript
285 lines
9.8 KiB
JavaScript
"use strict";
|
|
|
|
const MapManager = (function(){
|
|
|
|
const MapManager = function(hollow_bond){
|
|
|
|
const self = this,
|
|
frontier_selected_keys = new Set();
|
|
let maximum_chunks = 0,
|
|
maximum_complete_layer = 0,
|
|
frontier_layer = 0;
|
|
|
|
this.current_chunk = {x : 0, y : 0, z : 0};
|
|
|
|
// Colección de Chunks activos indexados por "x_y_z"
|
|
this.chunks = {};
|
|
this.frames = {};
|
|
this.rooms = {};
|
|
this.holes = {};
|
|
this.stairs = {};
|
|
this.elevators = {};
|
|
|
|
const get_chunk_key = (x, y, z) => `${x}_${y}_${z}`;
|
|
|
|
this.get_chunk = (x, y, z) => self.chunks[get_chunk_key(x, y, z)] || null;
|
|
|
|
this.clear = () => {
|
|
self.chunks = {};
|
|
self.frames = {};
|
|
self.rooms = {};
|
|
self.holes = {};
|
|
self.stairs = {};
|
|
self.elevators = {};
|
|
};
|
|
|
|
/**
|
|
* Destruye y libera un Chunk de la memoria activa.
|
|
*/
|
|
const unload_chunk = (x, y, z) => {
|
|
|
|
const key = get_chunk_key(x, y, z),
|
|
visualizer = (
|
|
hollow_bond.inspector.on ? hollow_bond.inspector :
|
|
hollow_bond.dungeon.on ? hollow_bond.dungeon :
|
|
null);
|
|
|
|
if(!self.chunks[key])
|
|
return;
|
|
|
|
// Avisamos a DungeonManager si está activo para que elimine los meshes 3D
|
|
visualizer && visualizer.remove_chunk(self.chunks[key]);
|
|
|
|
// Limpiamos referencias de frames y entidades de ese Chunk
|
|
for(const id in self.chunks[key].frames) delete self.frames[id];
|
|
for(const id in self.chunks[key].rooms) delete self.rooms[id];
|
|
for(const id in self.chunks[key].holes) delete self.holes[id];
|
|
for(const id in self.chunks[key].stairs) delete self.stairs[id];
|
|
for(const id in self.chunks[key].elevators) delete self.elevators[id];
|
|
|
|
delete self.chunks[key];
|
|
|
|
};
|
|
|
|
/**
|
|
* Actualiza el centro del streaming.
|
|
* Carga los nuevos vecinos que toquen y descarga los que queden lejos.
|
|
*/
|
|
this.update_active_center = (cx, cy, cz, callback = null) => {
|
|
|
|
const newly_created = [],
|
|
pending_targets = [
|
|
[0, 0, 0], // Centro actual
|
|
[-1, 0, 0], // Oeste
|
|
[1, 0, 0], // Este
|
|
[0, -1, 0], // Abajo
|
|
[0, 1, 0], // Arriba
|
|
[0, 0, -1], // Norte
|
|
[0, 0, 1] // Sur
|
|
].map(([ox, oy, oz]) => [cx + ox, cy + oy, cz + oz]).filter(([tx, ty, tz]) => (
|
|
!self.chunks[get_chunk_key(tx, ty, tz)] && chunk_exists(tx, ty, tz)
|
|
)),
|
|
process_uploads = () => {
|
|
for(const key in self.chunks)
|
|
Math.max(Math.abs(self.chunks[key].x - cx), Math.abs(self.chunks[key].y - cy), Math.abs(self.chunks[key].z - cz)) > 2 &&
|
|
unload_chunk(self.chunks[key].x, self.chunks[key].y, self.chunks[key].z);
|
|
};
|
|
|
|
self.current_chunk.x = cx;
|
|
self.current_chunk.y = cy;
|
|
self.current_chunk.z = cz;
|
|
|
|
if(callback){
|
|
|
|
let i = 0;
|
|
const process_next = () => {
|
|
if(i < pending_targets.length)
|
|
generate_chunk(...pending_targets[i ++], chunk => {
|
|
chunk && newly_created.push(chunk);
|
|
process_next();
|
|
});
|
|
else{
|
|
process_uploads();
|
|
callback(newly_created);
|
|
};
|
|
};
|
|
|
|
process_next();
|
|
|
|
return;
|
|
};
|
|
|
|
for(const [tx, ty, tz] of pending_targets){
|
|
|
|
const chunk = generate_chunk(tx, ty, tz);
|
|
|
|
chunk && newly_created.push(chunk);
|
|
|
|
};
|
|
|
|
process_uploads();
|
|
|
|
return newly_created;
|
|
};
|
|
|
|
/**
|
|
* Calcula el límite de capas completas y la selección exacta de la capa frontera.
|
|
*/
|
|
const setup_budget_layers = () => {
|
|
|
|
frontier_selected_keys.clear();
|
|
|
|
if(maximum_chunks <= 0)
|
|
return;
|
|
|
|
// 1. Caso base: 1 Chunk exacto (solo el origen)
|
|
if(maximum_chunks === 1){
|
|
maximum_complete_layer = 0;
|
|
frontier_layer = 0;
|
|
return;
|
|
};
|
|
|
|
// Buscamos si caben cubos completos mayores: (2*L + 1)^3
|
|
let layer = 0, leftover;
|
|
|
|
// Capa 0 está completa (el Chunk 0, 0, 0)
|
|
maximum_complete_layer = 0;
|
|
frontier_layer = 1;
|
|
|
|
// for(; Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks; layer ++);
|
|
while(Math.pow(2 * (layer + 1) + 1, 3) <= maximum_chunks){
|
|
layer++;
|
|
}
|
|
|
|
maximum_complete_layer = layer; // Capa 100% llena
|
|
frontier_layer = layer + 1; // Capa frontera
|
|
|
|
leftover = maximum_chunks - Math.pow(2 * maximum_complete_layer + 1, 3);
|
|
|
|
if(leftover <= 0)
|
|
return;
|
|
|
|
// 2. Recolectamos todas las coordenadas de la cáscara cúbica frontier_layer
|
|
const candidates = [];
|
|
|
|
for(let cx = -frontier_layer; cx <= frontier_layer; cx ++)
|
|
for(let cy = -frontier_layer; cy <= frontier_layer; cy ++)
|
|
for(let cz = -frontier_layer; cz <= frontier_layer; cz ++)
|
|
Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz)) === frontier_layer && candidates.push({
|
|
key : `${cx}_${cy}_${cz}`,
|
|
manhattan : Math.abs(cx) + Math.abs(cy) + Math.abs(cz),
|
|
score : hollow_bond.hash.chunk_presence(cx, cy, cz)
|
|
});
|
|
|
|
// 3. Ordenamos: primero los que tienen contacto de cara (menor distancia Manhattan),
|
|
// y dentro de ellos por su score determinista del hash.
|
|
candidates.sort((a, b) => (
|
|
a.manhattan !== b.manhattan ? a.manhattan - b.manhattan :
|
|
a.score - b.score));
|
|
|
|
// 4. Seleccionamos exactamente 'leftover'
|
|
for(let i = 0, count = Math.min(leftover, candidates.length); i < count; i ++)
|
|
frontier_selected_keys.add(candidates[i].key);
|
|
|
|
};
|
|
|
|
const chunk_exists = (cx, cy, cz) => {
|
|
// Modo Infinito
|
|
if(maximum_chunks <= 0)
|
|
return true;
|
|
|
|
const layer = Math.max(Math.abs(cx), Math.abs(cy), Math.abs(cz));
|
|
|
|
return (
|
|
layer <= maximum_complete_layer ? true :
|
|
layer > frontier_layer ? false :
|
|
frontier_selected_keys.has(`${cx}_${cy}_${cz}`));
|
|
};
|
|
|
|
const generate_chunk = (x, y, z, callback = null) => {
|
|
|
|
// Si el Chunk no existe en el presupuesto, no se crea
|
|
if(!chunk_exists(x, y, z)){
|
|
callback && callback();
|
|
return null;
|
|
};
|
|
|
|
const key = get_chunk_key(x, y, z);
|
|
|
|
if(self.chunks[key]){
|
|
callback && callback();
|
|
return self.chunks[key];
|
|
};
|
|
|
|
const visualizer = (
|
|
hollow_bond.inspector.on ? hollow_bond.inspector :
|
|
hollow_bond.dungeon.on ? hollow_bond.dungeon :
|
|
null),
|
|
on_chunk_ready = () => {
|
|
|
|
Object.assign(self.frames, self.chunks[key].frames);
|
|
Object.assign(self.rooms, self.chunks[key].rooms);
|
|
Object.assign(self.holes, self.chunks[key].holes);
|
|
Object.assign(self.stairs, self.chunks[key].stairs);
|
|
Object.assign(self.elevators, self.chunks[key].elevators);
|
|
|
|
visualizer && visualizer.sync_chunk(self.chunks[key]);
|
|
|
|
callback && callback();
|
|
|
|
};
|
|
|
|
self.chunks[key] = new ChunkModel(hollow_bond, {
|
|
x : x,
|
|
y : y,
|
|
z : z,
|
|
neighbors : {
|
|
west : chunk_exists(x - 1, y, z),
|
|
east : chunk_exists(x + 1, y, z),
|
|
down : chunk_exists(x, y - 1, z),
|
|
up : chunk_exists(x, y + 1, z),
|
|
north : chunk_exists(x, y, z - 1),
|
|
south : chunk_exists(x, y, z + 1)
|
|
}
|
|
}, callback ? on_chunk_ready : null);
|
|
|
|
callback || on_chunk_ready();
|
|
|
|
return self.chunks[key];
|
|
};
|
|
|
|
this.generate = (seed = null, new_maximum_chunks = 0, callback = null) => {
|
|
self.clear();
|
|
|
|
if(seed !== null)
|
|
hollow_bond.set_seed(seed);
|
|
maximum_chunks = new_maximum_chunks;
|
|
|
|
setup_budget_layers();
|
|
|
|
if(callback)
|
|
return new Promise(resolve => {
|
|
self.update_active_center(0, 0, 0, () => {
|
|
console.log(self.chunks);
|
|
callback(self.chunks);
|
|
resolve(self.chunks);
|
|
});
|
|
});
|
|
|
|
self.update_active_center(0, 0, 0);
|
|
|
|
console.log(self.chunks);
|
|
|
|
return self.chunks;
|
|
};
|
|
|
|
this.generate_asynchronous = (seed = null, new_maximum_chunks = 0, callback = null) => self.generate(seed, new_maximum_chunks, callback || (() => {}));
|
|
|
|
const constructor = () => {};
|
|
|
|
constructor();
|
|
|
|
};
|
|
|
|
return MapManager;
|
|
})(); |