"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; })();