"use strict"; const RoomCollider = (function(){ const RoomCollider = function(hollow_bond, model, frames = [], holes = []){ const self = this; this.model = model; this.frames = frames; this.holes = holes; const pad = model.padding || 0; // 1. Límites exactos del espacio de la sala (contacto continuo) this.room_bounds = { min_x: model.x, max_x: model.x + model.width, min_z: model.z, max_z: model.z + model.depth, min_y: model.y, max_y: model.y + model.height }; // 2. Límites de los muros interiores this.wall_bounds = { min_x: model.x + pad, max_x: model.x + model.width - pad, min_z: model.z + pad, max_z: model.z + model.depth - pad }; const py = Math.round(model.y / model.height); // Huecos activos que perforan suelo y techo this.floor_holes = holes.filter(h => py > h.yf && py < h.yt); this.ceiling_holes = holes.filter(h => py >= h.yf && py < (h.yt - 1)); this.get_floor_height = () => self.room_bounds.min_y; this.contains_xz = (x, z) => { return ( x >= self.room_bounds.min_x && x <= self.room_bounds.max_x && z >= self.room_bounds.min_z && z <= self.room_bounds.max_z ); }; // Sin solapamiento vertical entre plantas: solo reconoce la sala correspondiente this.contains_3d = (x, y, z) => { if(!self.contains_xz(x, z)) return false; return (y >= (self.room_bounds.min_y - 0.2) && y < (self.room_bounds.max_y - 0.5)); }; this.contains = (a, b, c) => { if(c !== undefined){ return self.contains_3d(a, b, c); } return self.contains_xz(a, b); }; this.resolve_player = (position, controls, player_radius = 0.22) => { const margin = 0.5; const is_near = ( position.x >= (self.room_bounds.min_x - margin) && position.x <= (self.room_bounds.max_x + margin) && position.z >= (self.room_bounds.min_z - margin) && position.z <= (self.room_bounds.max_z + margin) ); if(!is_near) return; const eye_height = controls.current_height || 1.75; const floor_y = self.room_bounds.min_y; const ceiling_y = self.room_bounds.max_y; const feet_y = position.y - eye_height; // Filtro estricto: si el jugador no pertenece a esta altura, no resolver nada if(feet_y < floor_y - 0.25 || feet_y > ceiling_y - 0.4){ return; } // 1. SUELO Y TECHO const in_floor_area = ( position.x >= (self.room_bounds.min_x - 0.15) && position.x <= (self.room_bounds.max_x + 0.15) && position.z >= (self.room_bounds.min_z - 0.15) && position.z <= (self.room_bounds.max_z + 0.15) ); if(in_floor_area){ let is_over_hole = false; for(const h of self.floor_holes){ const min_x = Math.min(h.xf, h.xt); const max_x = Math.max(h.xf, h.xt); const min_z = Math.min(h.zf, h.zt); const max_z = Math.max(h.zf, h.zt); if(position.x > min_x && position.x < max_x && position.z > min_z && position.z < max_z){ is_over_hole = true; break; } } // Apoyo de suelo: solo si cae o está quieto (permite saltar con velocity_y > 0) // Apoyo de suelo: EXCLUSIVAMENTE si el jugador está a menos de 25cm por encima o debajo del suelo de ESTA sala if(!is_over_hole){ const target_eye_y = floor_y + eye_height; const dist_to_floor = position.y - target_eye_y; // Solo apoya si está cayendo/parado Y a una distancia inmediata del suelo (-0.25m a +0.10m) if(controls.velocity_y <= 0 && dist_to_floor >= -0.25 && dist_to_floor <= 0.10){ position.y = target_eye_y; controls.velocity_y = 0; controls.is_grounded = true; } else if(dist_to_floor > 0.10){ controls.is_grounded = false; } } // Techo: solo frena si el jugador va subiendo let is_under_hole = false; for(const h of self.ceiling_holes){ const min_x = Math.min(h.xf, h.xt); const max_x = Math.max(h.xf, h.xt); const min_z = Math.min(h.zf, h.zt); const max_z = Math.max(h.zf, h.zt); if(position.x > min_x && position.x < max_x && position.z > min_z && position.z < max_z){ is_under_hole = true; break; } } if(!is_under_hole && controls.velocity_y > 0 && position.y >= ceiling_y){ position.y = ceiling_y; controls.velocity_y = 0; } } // 2. PAREDES Y PUERTAS const min_allowed_x = self.wall_bounds.min_x + player_radius; const max_allowed_x = self.wall_bounds.max_x - player_radius; const min_allowed_z = self.wall_bounds.min_z + player_radius; const max_allowed_z = self.wall_bounds.max_z - player_radius; let in_door_north = false, in_door_south = false, in_door_west = false, in_door_east = false; for(const f of self.frames){ if(f.axis === "horizontal"){ if(position.x >= (f.x - 0.05) && position.x <= (f.x + f.width + 0.05)){ if(Math.abs(f.z - model.z) < Math.abs(f.z - (model.z + model.depth))){ in_door_north = true; } else { in_door_south = true; } } } else { if(position.z >= (f.z - 0.05) && position.z <= (f.z + f.width + 0.05)){ if(Math.abs(f.x - model.x) < Math.abs(f.x - (model.x + model.width))){ in_door_west = true; } else { in_door_east = true; } } } } if(!in_door_west && position.x < min_allowed_x && position.x >= self.room_bounds.min_x - 0.1){ position.x = min_allowed_x; } if(!in_door_east && position.x > max_allowed_x && position.x <= self.room_bounds.max_x + 0.1){ position.x = max_allowed_x; } if(!in_door_north && position.z < min_allowed_z && position.z >= self.room_bounds.min_z - 0.1){ position.z = min_allowed_z; } if(!in_door_south && position.z > max_allowed_z && position.z <= self.room_bounds.max_z + 0.1){ position.z = max_allowed_z; } // 3. BARANDILLAS (bloquean el borde del hueco salvo si se salta >= 1.0m o por descansillo) if(self.railings){ for(let i = 0; i < self.railings.length; i++){ self.railings[i].resolve_player(position, controls, player_radius); } } }; }; return RoomCollider; })();