256 lines
12 KiB
JavaScript
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;
|
|
})(); |