HollowBond/Public/ecma/Objects/Game/RoomObjectGame.ecma.js

237 lines
10 KiB
JavaScript

"use strict";
const RoomObjectGame = (function(){
const RoomObjectGame = function(hollow_bond, model, geometry_factory, material_factory){
const self = this;
this.model = model;
const geom_fac = geometry_factory
|| (hollow_bond.dungeon && hollow_bond.dungeon.geometries && hollow_bond.dungeon.geometries.room)
|| new RoomGeometryGame(hollow_bond);
const mat_fac = material_factory
|| (hollow_bond.dungeon && hollow_bond.dungeon.materials && hollow_bond.dungeon.materials.room)
|| new RoomMaterialGame(hollow_bond);
// 1. Recuperamos marcos de esta sala
const frame_models = [];
if(model.frames && hollow_bond.map && hollow_bond.map.frames){
for(const fid of model.frames){
const f = hollow_bond.map.frames[fid];
if(f) frame_models.push(f);
}
}
// 1.1 Detectamos ascensores que lindan con esta sala para abrir el corte en la pared
const py = Math.round(model.y / model.height);
const map_elevators = (hollow_bond.map && hollow_bond.map.elevators) || {};
const door_w = hollow_bond.elevator_door_width || 1.1;
const cut_w = door_w + 0.08; // 1.18m (4cm de holgura a cada lado para que el marco solape)
const cut_h = 2.10; // 2.10m de altura para que el dintel cubra la parte superior
for(const eid in map_elevators){
const el = map_elevators[eid];
if(el.y !== py) continue;
const ex1 = el.x, ex2 = el.x + el.width;
const ez1 = el.z, ez2 = el.z + el.depth;
const rx1 = model.x, rx2 = model.x + model.width;
const rz1 = model.z, rz2 = model.z + model.depth;
let touches = false;
let axis = "horizontal";
let open_x = 0, open_z = 0;
// Norte: ascensor mira al norte, habitación está al norte (pared sur de la sala)
if(el.facing === "north" && Math.abs(rz2 - ez1) < 0.2 && ex2 > rx1 && ex1 < rx2){
touches = true;
axis = "horizontal";
open_x = (ex1 + ex2) * 0.5 - cut_w * 0.5;
open_z = rz2;
}
// Sur: ascensor mira al sur, habitación está al sur (pared norte de la sala)
else if(el.facing === "south" && Math.abs(rz1 - ez2) < 0.2 && ex2 > rx1 && ex1 < rx2){
touches = true;
axis = "horizontal";
open_x = (ex1 + ex2) * 0.5 - cut_w * 0.5;
open_z = rz1;
}
// Oeste: ascensor mira al oeste, habitación está al oeste (pared este de la sala)
else if(el.facing === "west" && Math.abs(rx2 - ex1) < 0.2 && ez2 > rz1 && ez1 < rz2){
touches = true;
axis = "vertical";
open_x = rx2;
open_z = (ez1 + ez2) * 0.5 - cut_w * 0.5;
}
// Este: ascensor mira al este, habitación está al este (pared oeste de la sala)
else if(el.facing === "east" && Math.abs(rx1 - ex2) < 0.2 && ez2 > rz1 && ez1 < rz2){
touches = true;
axis = "vertical";
open_x = rx1;
open_z = (ez1 + ez2) * 0.5 - cut_w * 0.5;
}
if(touches){
model.elevators = model.elevators || [];
if(!model.elevators.includes(el.id)) model.elevators.push(el.id);
frame_models.push({
id: `elev_cut_${el.id}`,
axis: axis,
x: open_x,
z: open_z,
width: cut_w,
height: cut_h
});
}
}
// 2. Recuperamos huecos verticales de esta sala
const hole_models = [];
if(model.holes && hollow_bond.map && hollow_bond.map.holes){
for(const hid of model.holes){
const h = hollow_bond.map.holes[hid];
if(h) hole_models.push(h);
}
}
// 3. Geometría con vanos de pared y perforaciones en suelo/techo
const geometry = geom_fac.get_for_room(model, frame_models, hole_models);
// 4. Materiales del tema
const materials = mat_fac.get_materials();
this.mesh = new THREE.Mesh(geometry, materials);
const center_x = model.x + model.width / 2;
const center_y = model.y + (model.height / 2);
const center_z = model.z + model.depth / 2;
this.mesh.position.set(center_x, center_y, center_z);
// 5. Generación de Barandillas en los huecos con caída inferior
this.railings = [];
const build_railings = () => {
const py = Math.round(model.y / model.height);
// Solo huecos que perforan hacia abajo en este piso
const active_holes = hole_models.filter(h => py > h.yf && py < h.yt);
if(active_holes.length === 0) return;
const railing_mat = (hollow_bond.materials && hollow_bond.materials.railing)
? hollow_bond.materials.railing.get()
: (hollow_bond.dungeon && hollow_bond.dungeon.materials && hollow_bond.dungeon.materials.railing
? hollow_bond.dungeon.materials.railing.get()
: null);
const landing_size = hollow_bond.stairs_landing_size || 1.0;
const subtract_1d = (min, max, cuts) => {
let segments = [{ start: min, end: max }];
for(const c of cuts){
const next = [];
for(const s of segments){
if(c.end <= s.start || c.start >= s.end){
next.push(s);
continue;
}
if(c.start > s.start) next.push({ start: s.start, end: c.start });
if(c.end < s.end) next.push({ start: c.end, end: s.end });
}
segments = next;
}
return segments.filter(s => (s.end - s.start) > 0.05);
};
for(const hole of active_holes){
const hx1 = Math.max(model.x, hole.xf);
const hx2 = Math.min(model.x + model.width, hole.xt);
const hz1 = Math.max(model.z, hole.zf);
const hz2 = Math.min(model.z + model.depth, hole.zt);
if(hx2 <= hx1 || hz2 <= hz1) continue;
// Identificar descansillos de escaleras
const cuts = { north: [], south: [], west: [], east: [] };
for(const stair of (hole.stairs || [])){
const stair_top_py = stair.yf + 1;
let at_floor = (stair.yf === py) ? "bottom" : (stair_top_py === py ? "top" : null);
if(!at_floor) continue;
const is_start = (at_floor === "bottom" && stair.direction === 1) ||
(at_floor === "top" && stair.direction === -1);
if(stair.wall === "north" || stair.wall === "south"){
const lx1 = is_start ? stair.xf : (stair.xt - landing_size);
const lx2 = lx1 + landing_size;
cuts[stair.wall].push({ start: lx1, end: lx2 });
// Esquinas: liberar el lado perpendicular si toca el lateral
if(Math.abs(lx1 - hole.xf) < 0.05) cuts.west.push({ start: hole.zf, end: hole.zf + landing_size });
if(Math.abs(lx2 - hole.xt) < 0.05) cuts.east.push({ start: hole.zf, end: hole.zf + landing_size });
} else {
const lz1 = is_start ? stair.zf : (stair.zt - landing_size);
const lz2 = lz1 + landing_size;
cuts[stair.wall].push({ start: lz1, end: lz2 });
if(Math.abs(lz1 - hole.zf) < 0.05) cuts.north.push({ start: (stair.wall === "west" ? hole.xf : hole.xt - landing_size), end: (stair.wall === "west" ? hole.xf + landing_size : hole.xt) });
if(Math.abs(lz2 - hole.zt) < 0.05) cuts.south.push({ start: (stair.wall === "west" ? hole.xf : hole.xt - landing_size), end: (stair.wall === "west" ? hole.xf + landing_size : hole.xt) });
}
}
// Generar los 4 lados perimetrales
const add_side = (side, min, max, fixed, cuts_arr) => {
const valid_segs = subtract_1d(min, max, cuts_arr);
for(const s of valid_segs){
const is_horiz = (side === "north" || side === "south");
const seg_data = {
side: side,
y: model.y, // Cota de mundo
height: 1.0,
thickness: 0.08,
xf: is_horiz ? s.start : fixed,
xt: is_horiz ? s.end : fixed,
zf: is_horiz ? fixed : s.start,
zt: is_horiz ? fixed : s.end
};
const rail_obj = new RailingObjectGame(hollow_bond, seg_data, railing_mat, model);
self.railings.push(rail_obj);
self.mesh.add(rail_obj.mesh);
}
};
if(hole.zf >= model.z) add_side("north", hx1, hx2, hole.zf, cuts.north);
if(hole.zt <= (model.z + model.depth)) add_side("south", hx1, hx2, hole.zt, cuts.south);
if(hole.xf >= model.x) add_side("west", hz1, hz2, hole.xf, cuts.west);
if(hole.xt <= (model.x + model.width)) add_side("east", hz1, hz2, hole.xt, cuts.east);
}
};
build_railings();
// 6. Collider con caídas y pasos libres
this.collider = new RoomCollider(hollow_bond, model, frame_models, hole_models);
// Vinculamos los colliders de las barandillas directamente a la sala
this.collider.railings = this.railings.map(r => r.collider);
this.destroy = () => {
if(self.railings){
for(let i = 0; i < self.railings.length; i++){
self.railings[i].destroy();
}
self.railings = [];
}
if(self.mesh){
self.mesh.geometry.dispose();
}
};
};
return RoomObjectGame;
})();