HollowBond/Public/ecma/Geometries/Game/StairGeometryGame.ecma.js

256 lines
12 KiB
JavaScript

"use strict";
const StairGeometryGame = (function(){
const StairGeometryGame = function(hollow_bond){
const cache = {};
// Utilidad para añadir un prisma ortogonal (caja AABB)
const append_box = (buffers, min_x, max_x, min_y, max_y, min_z, max_z) => {
const { positions, normals, uvs, indices } = buffers;
let offset = positions.length / 3;
const add_quad = (p1, p2, p3, p4, n) => {
positions.push(...p1, ...p2, ...p3, ...p4);
normals.push(...n, ...n, ...n, ...n);
uvs.push(0, 0, 1, 0, 1, 1, 0, 1);
indices.push(offset, offset + 2, offset + 1, offset, offset + 3, offset + 2);
offset += 4;
};
add_quad([min_x, max_y, max_z], [max_x, max_y, max_z], [max_x, max_y, min_z], [min_x, max_y, min_z], [0, 1, 0]);
add_quad([min_x, min_y, min_z], [max_x, min_y, min_z], [max_x, min_y, max_z], [min_x, min_y, max_z], [0, -1, 0]);
add_quad([max_x, min_y, min_z], [min_x, min_y, min_z], [min_x, max_y, min_z], [max_x, max_y, min_z], [0, 0, -1]);
add_quad([min_x, min_y, max_z], [max_x, min_y, max_z], [max_x, max_y, max_z], [min_x, max_y, max_z], [0, 0, 1]);
add_quad([min_x, min_y, min_z], [min_x, min_y, max_z], [min_x, max_y, max_z], [min_x, max_y, min_z], [-1, 0, 0]);
add_quad([max_x, min_y, max_z], [max_x, min_y, min_z], [max_x, max_y, min_z], [max_x, max_y, max_z], [1, 0, 0]);
};
// Genera una barra inclinada continua conectando (L0, Y0) con (L1, Y1)
const append_inclined_bar = (buffers, is_x, l0, y0, l1, y1, c_min, c_max, bar_h) => {
const { positions, normals, uvs, indices } = buffers;
let offset = positions.length / 3;
const add_quad = (p1, p2, p3, p4, n) => {
positions.push(...p1, ...p2, ...p3, ...p4);
normals.push(...n, ...n, ...n, ...n);
uvs.push(0, 0, 1, 0, 1, 1, 0, 1);
indices.push(offset, offset + 2, offset + 1, offset, offset + 3, offset + 2);
offset += 4;
};
const make_pt = (l, y, c) => is_x ? [l, y, c] : [c, y, l];
const p0 = make_pt(l0, y0, c_min);
const p1 = make_pt(l0, y0, c_max);
const p2 = make_pt(l0, y0 + bar_h, c_max);
const p3 = make_pt(l0, y0 + bar_h, c_min);
const p4 = make_pt(l1, y1, c_min);
const p5 = make_pt(l1, y1, c_max);
const p6 = make_pt(l1, y1 + bar_h, c_max);
const p7 = make_pt(l1, y1 + bar_h, c_min);
// Caras longitudinales
add_quad(p3, p7, p6, p2, [0, 1, 0]); // Superior
add_quad(p0, p1, p5, p4, [0, -1, 0]); // Inferior
add_quad(p0, p4, p7, p3, is_x ? [0, 0, -1] : [-1, 0, 0]); // Lateral C_min
add_quad(p1, p2, p6, p5, is_x ? [0, 0, 1] : [1, 0, 0]); // Lateral C_max
// Tapas en los extremos
add_quad(p0, p3, p2, p1, is_x ? [-1, 0, 0] : [0, 0, -1]);
add_quad(p4, p5, p6, p7, is_x ? [1, 0, 0] : [0, 0, 1]);
};
this.get_for_stair = (stair, hole) => {
const is_x = (stair.wall === "north" || stair.wall === "south");
const length = is_x ? stair.width : stair.depth;
const rise_height = hollow_bond.room_height;
const landing_size = hollow_bond.stairs_landing_size || 1.0;
const key = `stair_v2_${stair.wall}_dir${stair.direction}_len${length}_h${rise_height}`;
if(cache[key]) return cache[key];
const buffers = { positions: [], normals: [], uvs: [], indices: [] };
const half_w = 0.5;
const flight_len = Math.max(1.0, length - (landing_size * 2));
const dir = stair.direction; // 1 o -1
// Orientación: qué lado da al vacío del foso
const inner_sign = (stair.wall === "north" || stair.wall === "west") ? 1 : -1;
const inner_c = inner_sign * (half_w - 0.04);
const outer_c = -inner_sign * (half_w - 0.04);
// 1. DESCANGILLOS (Plataformas horizontales de 1m)
const bot_l_start = (dir === 1) ? 0 : (length - landing_size);
const top_l_start = (dir === 1) ? (length - landing_size) : 0;
const add_platform = (l_start, y_level) => {
const l_end = l_start + landing_size;
if(is_x){
append_box(buffers, l_start, l_end, y_level - 0.05, y_level, -half_w, half_w);
} else {
append_box(buffers, -half_w, half_w, y_level - 0.05, y_level, l_start, l_end);
}
};
add_platform(bot_l_start, 0);
add_platform(top_l_start, rise_height);
// 2. PELDAÑOS NUMEROSOS (~0.18m contrahuella)
const step_count = Math.max(12, Math.round(rise_height / 0.1875));
const step_rise = rise_height / step_count;
const step_run = flight_len / step_count;
for(let i = 0; i < step_count; i++){
let l_min, l_max;
if(dir === 1){
l_min = landing_size + (i * step_run);
l_max = l_min + step_run;
} else {
l_max = (length - landing_size) - (i * step_run);
l_min = l_max - step_run;
}
const y_top = (i + 1) * step_rise;
const y_bot = y_top - 0.035;
if(is_x){
append_box(buffers, l_min, l_max, y_bot, y_top, -half_w + 0.06, half_w - 0.06);
} else {
append_box(buffers, -half_w + 0.06, half_w - 0.06, y_bot, y_top, l_min, l_max);
}
}
// 3. ZANCAS / BARRAS INCLINADAS QUE UNEN LOS PELDAÑOS
const flight_l_bot = (dir === 1) ? landing_size : (length - landing_size);
const flight_l_top = (dir === 1) ? (length - landing_size) : landing_size;
// Zanca exterior (lado pared)
append_inclined_bar(
buffers, is_x,
flight_l_bot, 0,
flight_l_top, rise_height,
outer_c - 0.025, outer_c + 0.025,
0.07
);
// Zanca interior (lado foso)
append_inclined_bar(
buffers, is_x,
flight_l_bot, 0,
flight_l_top, rise_height,
inner_c - 0.025, inner_c + 0.025,
0.07
);
// 4. BARANDILLA INCLINADA Y POSTES (1.0m de altura, solo lado vacío)
const rail_h = 1.0;
// Pasamanos superior (1.0m)
append_inclined_bar(
buffers, is_x,
flight_l_bot, rail_h - 0.04,
flight_l_top, rise_height + rail_h - 0.04,
inner_c - 0.02, inner_c + 0.02,
0.04
);
// Travesaño intermedio (0.5m)
append_inclined_bar(
buffers, is_x,
flight_l_bot, rail_h * 0.5 - 0.02,
flight_l_top, rise_height + rail_h * 0.5 - 0.02,
inner_c - 0.015, inner_c + 0.015,
0.03
);
// Postes verticales que nacen de la zanca y van al pasamanos
const post_count = Math.max(4, Math.round(flight_len / 0.75) + 1);
for(let k = 0; k < post_count; k++){
const t = k / (post_count - 1);
const l_post = (dir === 1)
? (landing_size + t * flight_len)
: ((length - landing_size) - t * flight_len);
const y_base = t * rise_height;
if(is_x){
append_box(buffers, l_post - 0.02, l_post + 0.02, y_base, y_base + rail_h, inner_c - 0.02, inner_c + 0.02);
} else {
append_box(buffers, inner_c - 0.02, inner_c + 0.02, y_base, y_base + rail_h, l_post - 0.02, l_post + 0.02);
}
}
// 5. BARANDILLAS EN LOS DESCANSOS (Lado interior)
const add_landing_rail = (l_start, y_level) => {
const l_end = l_start + landing_size;
// Pasamanos y travesaño
if(is_x){
append_box(buffers, l_start, l_end, y_level + rail_h - 0.04, y_level + rail_h, inner_c - 0.02, inner_c + 0.02);
append_box(buffers, l_start, l_end, y_level + rail_h * 0.5 - 0.02, y_level + rail_h * 0.5 + 0.01, inner_c - 0.015, inner_c + 0.015);
append_box(buffers, l_start - 0.02, l_start + 0.02, y_level, y_level + rail_h, inner_c - 0.02, inner_c + 0.02);
append_box(buffers, l_end - 0.02, l_end + 0.02, y_level, y_level + rail_h, inner_c - 0.02, inner_c + 0.02);
} else {
append_box(buffers, inner_c - 0.02, inner_c + 0.02, y_level + rail_h - 0.04, y_level + rail_h, l_start, l_end);
append_box(buffers, inner_c - 0.015, inner_c + 0.015, y_level + rail_h * 0.5 - 0.02, y_level + rail_h * 0.5 + 0.01, l_start, l_end);
append_box(buffers, inner_c - 0.02, inner_c + 0.02, y_level, y_level + rail_h, l_start - 0.02, l_start + 0.02);
append_box(buffers, inner_c - 0.02, inner_c + 0.02, y_level, y_level + rail_h, l_end - 0.02, l_end + 0.02);
}
};
add_landing_rail(bot_l_start, 0);
add_landing_rail(top_l_start, rise_height);
// 6. CIERRE FRONTAL (si el descansillo da al vacío y no a una pared del hueco)
if(hole){
const at_corner_start = is_x ? (stair.xf === hole.xf) : (stair.zf === hole.zf);
const at_corner_end = is_x ? (stair.xt === hole.xt) : (stair.zt === hole.zt);
// Cierre en extremo 0 (L = 0)
if(!at_corner_start){
const y_base = (dir === 1) ? 0 : rise_height;
if(is_x){
append_box(buffers, -0.02, 0.02, y_base + rail_h - 0.04, y_base + rail_h, -half_w, half_w);
append_box(buffers, -0.02, 0.02, y_base, y_base + rail_h, outer_c - 0.02, outer_c + 0.02);
} else {
append_box(buffers, -half_w, half_w, y_base + rail_h - 0.04, y_base + rail_h, -0.02, 0.02);
append_box(buffers, outer_c - 0.02, outer_c + 0.02, y_base, y_base + rail_h, -0.02, 0.02);
}
}
// Cierre en extremo length (L = length)
if(!at_corner_end){
const y_base = (dir === 1) ? rise_height : 0;
if(is_x){
append_box(buffers, length - 0.02, length + 0.02, y_base + rail_h - 0.04, y_base + rail_h, -half_w, half_w);
append_box(buffers, length - 0.02, length + 0.02, y_base, y_base + rail_h, outer_c - 0.02, outer_c + 0.02);
} else {
append_box(buffers, -half_w, half_w, y_base + rail_h - 0.04, y_base + rail_h, length - 0.02, length + 0.02);
append_box(buffers, outer_c - 0.02, outer_c + 0.02, y_base, y_base + rail_h, length - 0.02, length + 0.02);
}
}
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.Float32BufferAttribute(buffers.positions, 3));
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(buffers.normals, 3));
geometry.setAttribute("uv", new THREE.Float32BufferAttribute(buffers.uvs, 2));
geometry.setIndex(buffers.indices);
cache[key] = geometry;
return geometry;
};
this.reset = () => {
for(const k in cache){
cache[k].dispose();
delete cache[k];
}
};
};
return StairGeometryGame;
})();