HollowBond/Public/ecma/Managers/InspectorManager.ecma.js

484 lines
18 KiB
JavaScript

"use strict";
const InspectorManager = (function(){
const InspectorManager = function(hollow_bond){
const self = this;
this.scene = null;
this.camera = null;
this.renderer = null;
this.on = false;
// Configuración de visualización vertical
this.current_floor = 0;
this.vertical_layers_range = 2; // Cantidad de pisos arriba y abajo a mostrar
this.floor_height = hollow_bond.room_height || 3;
this.geometries = {
frame : new FrameGeometryDebug(hollow_bond),
room : new RoomGeometryDebug(hollow_bond),
hole : new HoleGeometryDebug(hollow_bond),
stair : new StairGeometryDebug(hollow_bond),
elevator : new ElevatorGeometryDebug(hollow_bond)
};
this.materials = {
frame : new FrameMaterialDebug(),
room : new RoomMaterialDebug(),
hole : new HoleMaterialDebug(),
stair : new StairMaterialDebug(),
elevator : new ElevatorMaterialDebug()
};
// Diccionarios de objetos visuales en la escena
this.objects = {
frame : {},
room : {},
hole : {},
stair : {},
elevator : {}
};
let floor_indicator_plane = null,
hud_element = null;
let last_frame_time = performance.now();
/**
* Inicializa la vista de inspección con vuelo libre.
*/
this.start = () => {
self.on = true;
setup_scene();
setup_hud();
hollow_bond.controls.debug.setup(self);
// Poblamos entidades que ya existan en el mapa
self.synchronize_all();
// Activamos contador de FPS si está habilitado
hollow_bond.views.fps.go();
render_loop();
};
/**
* Sincroniza todas las entidades disponibles en el mapa global.
*/
this.synchronize_all = () => {
synchronize_holes();
synchronize_elevators();
synchronize_stairs();
synchronize_frames();
synchronize_rooms();
self.update_layer_visibility();
};
/**
* Sincroniza de forma incremental un Chunk específico recién generado.
* @param {ChunkModel} chunk
*/
this.sync_chunk = chunk => {
if(!chunk) return;
// 1. Huecos
if(chunk.holes){
for(const hid in chunk.holes){
if(!self.objects.hole[hid]){
const model = chunk.holes[hid];
const obj = new HoleObjectDebug(hollow_bond, model, self.geometries.hole, self.materials.hole);
obj.model = model;
self.objects.hole[hid] = obj;
self.scene.add(obj.mesh);
}
}
}
// 2. Ascensores
if(chunk.elevators){
for(const eid in chunk.elevators){
if(!self.objects.elevator[eid]){
const model = chunk.elevators[eid];
const obj = new ElevatorObjectDebug(hollow_bond, model, self.geometries.elevator, self.materials.elevator);
obj.model = model;
self.objects.elevator[eid] = obj;
self.scene.add(obj.mesh);
}
}
}
// 3. Escaleras
if(chunk.stairs){
for(const sid in chunk.stairs){
if(!self.objects.stair[sid]){
const model = chunk.stairs[sid];
const obj = new StairObjectDebug(hollow_bond, model);
obj.model = model;
self.objects.stair[sid] = obj;
self.scene.add(obj.mesh);
}
}
}
// 4. Marcos (con visibilidad de capa inmediata)
if(chunk.frames){
for(const fid in chunk.frames){
if(!self.objects.frame[fid]){
const model = chunk.frames[fid];
const obj = new FrameObjectDebug(hollow_bond, model, self.geometries.frame, self.materials.frame);
obj.model = model;
const dist = Math.abs(model.y - self.current_floor);
if(dist > self.vertical_layers_range){
obj.mesh.visible = false;
} else {
obj.mesh.visible = true;
obj.set_layer_distance && obj.set_layer_distance(dist);
}
self.objects.frame[fid] = obj;
self.scene.add(obj.mesh);
}
}
}
// 5. Habitaciones (con visibilidad de capa inmediata)
if(chunk.rooms){
for(const rid in chunk.rooms){
if(!self.objects.room[rid]){
const model = chunk.rooms[rid];
const obj = new RoomObjectDebug(hollow_bond, model);
obj.model = model;
const floor_index = Math.floor(model.y / self.floor_height);
const dist = Math.abs(floor_index - self.current_floor);
obj.mesh.visible = (dist <= self.vertical_layers_range);
self.objects.room[rid] = obj;
self.scene.add(obj.mesh);
}
}
}
};
/**
* Retira y destruye las mallas 3D asociadas a un Chunk descargado.
* @param {ChunkModel} chunk
*/
this.remove_chunk = chunk => {
if(!chunk) return;
const remove_group = (type, source) => {
if(!source) return;
for(const id in source){
if(self.objects[type][id]){
self.scene && self.scene.remove(self.objects[type][id].mesh);
self.objects[type][id].destroy && self.objects[type][id].destroy();
delete self.objects[type][id];
}
}
};
remove_group("frame", chunk.frames);
remove_group("room", chunk.rooms);
remove_group("hole", chunk.holes);
remove_group("elevator", chunk.elevators);
remove_group("stair", chunk.stairs);
};
/**
* Limpia por completo la escena y libera memoria de GPU.
*/
this.reset = () => {
// 1. Limpieza de mallas en la escena
for(const key in self.objects){
for(const id in self.objects[key]){
self.scene && self.scene.remove(self.objects[key][id].mesh);
self.objects[key][id].destroy && self.objects[key][id].destroy();
};
self.objects[key] = {};
};
// 2. Liberación de buffers geométricos
for(const key in self.geometries)
self.geometries[key].reset && self.geometries[key].reset();
// 3. Liberación de shaders y materiales
for(const key in self.materials)
self.materials[key].reset && self.materials[key].reset();
};
const setup_scene = () => {
const chunk_height = hollow_bond.chunk_size * hollow_bond.room_height;
self.scene = new THREE.Scene();
self.scene.background = new THREE.Color(0x0a0a0d);
self.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1500);
self.renderer = new THREE.WebGLRenderer({
antialias : hollow_bond.render_scale >= 100,
powerPreference : "high-performance"
});
self.renderer.domElement.className = "hb-canvas-inspector";
self.renderer.domElement.tabIndex = 1;
hollow_bond.item_self.appendChild(self.renderer.domElement);
self.update_resolution();
// Iluminación
self.scene.add(new THREE.AmbientLight(0xffffff, 0.85));
const sun = new THREE.DirectionalLight(0xd6d0ba, 1.2);
sun.position.set(25, 60, 25);
self.scene.add(sun);
// Cajas guía de los Chunks iniciales
[-1, 0, 1].forEach(cy => {
const wire_box = new THREE.Mesh(
new THREE.BoxGeometry(hollow_bond.chunk_size, chunk_height, hollow_bond.chunk_size),
new THREE.MeshBasicMaterial({
color : cy === 0 ? 0x3d4450 : 0x1f232b,
wireframe : true,
transparent : true,
opacity : cy === 0 ? 0.35 : 0.15
})
);
wire_box.position.set(
hollow_bond.chunk_size / 2,
(cy * chunk_height) + (chunk_height / 2),
hollow_bond.chunk_size / 2
);
self.scene.add(wire_box);
});
// Plano visual sutil que marca la cota del piso seleccionado
floor_indicator_plane = new THREE.GridHelper(hollow_bond.chunk_size * 3, hollow_bond.chunk_size * 3, 0x00bcd4, 0x1e293b);
floor_indicator_plane.position.set(hollow_bond.chunk_size / 2, 0, hollow_bond.chunk_size / 2);
self.scene.add(floor_indicator_plane);
// Posición inicial de la cámara
self.camera.position.set(hollow_bond.chunk_size / 2, 5, hollow_bond.chunk_size / 2 + 10);
window.addEventListener("resize", self.update_resolution);
};
this.update_resolution = () => {
if(!self.renderer || !self.camera) return;
const w = window.innerWidth,
h = window.innerHeight,
scale = Math.max(0.03125, hollow_bond.render_scale / 100);
self.camera.aspect = w / h;
self.camera.updateProjectionMatrix();
self.renderer.setSize(Math.floor(w * scale), Math.floor(h * scale), false);
self.renderer.domElement.style.width = "100%";
self.renderer.domElement.style.height = "100%";
};
/**
* Ajusta la cota activa y recalcula las opacidades verticales.
*/
this.set_floor = floor_y => {
self.current_floor = floor_y;
if(floor_indicator_plane){
floor_indicator_plane.position.y = self.current_floor * self.floor_height;
}
self.update_layer_visibility();
self.update_hud();
};
/**
* Aplica la visibilidad y opacidad degradada a marcos y habitaciones.
*/
this.update_layer_visibility = () => {
const range = self.vertical_layers_range;
// 1. Filtrado y gradiente de marcos
for(const id in self.objects.frame){
const frame_obj = self.objects.frame[id],
dist = Math.abs(frame_obj.model.y - self.current_floor);
if(dist > range){
frame_obj.mesh.visible = false;
} else {
frame_obj.mesh.visible = true;
frame_obj.set_layer_distance && frame_obj.set_layer_distance(dist);
}
}
// 2. Filtrado de habitaciones
for(const id in self.objects.room){
const room_obj = self.objects.room[id],
floor_index = Math.floor(room_obj.model.y / self.floor_height),
dist = Math.abs(floor_index - self.current_floor);
room_obj.mesh.visible = (dist <= range);
}
};
// --- Sincronizadores Globales ---
const synchronize_frames = () => {
if(!hollow_bond.map || !hollow_bond.map.frames) return;
for(const frame_id in hollow_bond.map.frames){
if(!self.objects.frame[frame_id]){
const frame_model = hollow_bond.map.frames[frame_id];
const frame_obj = new FrameObjectDebug(
hollow_bond,
frame_model,
self.geometries.frame,
self.materials.frame
);
frame_obj.model = frame_model;
const dist = Math.abs(frame_model.y - self.current_floor);
if(dist > self.vertical_layers_range){
frame_obj.mesh.visible = false;
} else {
frame_obj.mesh.visible = true;
frame_obj.set_layer_distance && frame_obj.set_layer_distance(dist);
}
self.objects.frame[frame_id] = frame_obj;
self.scene.add(frame_obj.mesh);
}
}
};
const synchronize_rooms = () => {
if(!hollow_bond.map || !hollow_bond.map.rooms) return;
for(const room_id in hollow_bond.map.rooms){
if(!self.objects.room[room_id]){
const room_model = hollow_bond.map.rooms[room_id];
const room_obj = new RoomObjectDebug(hollow_bond, room_model);
room_obj.model = room_model;
const floor_index = Math.floor(room_model.y / self.floor_height),
dist = Math.abs(floor_index - self.current_floor);
room_obj.mesh.visible = (dist <= self.vertical_layers_range);
self.objects.room[room_id] = room_obj;
self.scene.add(room_obj.mesh);
}
}
};
const synchronize_holes = () => {
if(!hollow_bond.map || !hollow_bond.map.chunks) return;
for(const cid in hollow_bond.map.chunks){
const chunk = hollow_bond.map.chunks[cid];
if(!chunk || !chunk.holes) continue;
for(const hid in chunk.holes){
if(!self.objects.hole[hid]){
const hole_model = chunk.holes[hid];
const hole_obj = new HoleObjectDebug(
hollow_bond,
hole_model,
self.geometries.hole,
self.materials.hole
);
hole_obj.model = hole_model;
self.objects.hole[hid] = hole_obj;
self.scene.add(hole_obj.mesh);
}
}
}
};
const synchronize_elevators = () => {
if(!hollow_bond.map || !hollow_bond.map.chunks) return;
for(const cid in hollow_bond.map.chunks){
const chunk = hollow_bond.map.chunks[cid];
if(!chunk || !chunk.elevators) continue;
for(const eid in chunk.elevators){
if(!self.objects.elevator[eid]){
const elevator_model = chunk.elevators[eid];
const elevator_obj = new ElevatorObjectDebug(
hollow_bond,
elevator_model,
self.geometries.elevator,
self.materials.elevator
);
elevator_obj.model = elevator_model;
self.objects.elevator[eid] = elevator_obj;
self.scene.add(elevator_obj.mesh);
}
}
}
};
const synchronize_stairs = () => {
if(!hollow_bond.map || !hollow_bond.map.chunks) return;
for(const cid in hollow_bond.map.chunks){
const chunk = hollow_bond.map.chunks[cid];
if(!chunk || !chunk.stairs) continue;
for(const sid in chunk.stairs){
if(!self.objects.stair[sid]){
const stair_model = chunk.stairs[sid];
const stair_obj = new StairObjectDebug(hollow_bond, stair_model);
stair_obj.model = stair_model;
self.objects.stair[sid] = stair_obj;
self.scene.add(stair_obj.mesh);
}
}
}
};
const setup_hud = () => {
hud_element = hollow_bond.html(hollow_bond.item_self, ["div", {"class" : "inspector-hud"}, [
["div", {"class" : "hud-title"}, "MODO INSPECCIÓN TÉCNICA"],
["div", {"class" : "hud-row"}, [
["span", {}, "Piso enfocado (Y): "],
["b", {"class" : "hud-floor-label"}, String(self.current_floor)]
]],
["div", {"class" : "hud-row"}, [
["span", {}, "Capas adyacentes: ±"],
["b", {"class" : "hud-range-label"}, String(self.vertical_layers_range)]
]],
["div", {"class" : "hud-controls-hint"}, "[Q/E] Subir/Bajar piso | [0..4] Rango de capas | [WASD+Ratón] Volar"]
]])[0];
};
this.update_hud = () => {
if(!hud_element) return;
hud_element.querySelector(".hud-floor-label").textContent = String(self.current_floor);
hud_element.querySelector(".hud-range-label").textContent = String(self.vertical_layers_range);
};
const render_loop = () => {
requestAnimationFrame(render_loop);
const now = performance.now(),
delta = Math.min((now - last_frame_time) / 1000, 0.1);
last_frame_time = now;
hollow_bond.controls.debug.update_movement(self, delta);
hollow_bond.show_fps && hollow_bond.views.fps && hollow_bond.views.fps.tick();
self.renderer.render(self.scene, self.camera);
};
};
return InspectorManager;
})();