484 lines
18 KiB
JavaScript
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;
|
|
})(); |