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

270 lines
11 KiB
JavaScript

"use strict";
const RoomGeometryGame = (function(){
const RoomGeometryGame = function(hollow_bond){
const cache = {};
/**
* @param {RoomModel} model
* @param {Array<FrameModel>} frames
* @param {Array<HoleModel>} holes
*/
this.get_for_room = (model, frames = [], holes = []) => {
const pad = model.padding || 0;
const w = Math.max(0.1, model.width - pad * 2);
const h = Math.max(0.1, model.height - pad * 2);
const d = Math.max(0.1, model.depth - pad * 2);
const frame_ids = frames.map(f => f.id).sort().join(",");
const hole_ids = holes.map(h => h.id).sort().join(",");
// const key = `${w}_${h}_${d}_[F:${frame_ids}]_[H:${hole_ids}]`;
const key = model.id;
if(cache[key]){
return cache[key];
}
const geometry = new THREE.BufferGeometry();
const hx = w / 2;
const hy = h / 2;
const hz = d / 2;
const positions = [];
const normals = [];
const uvs = [];
const indices = [];
let index_offset = 0;
const add_quad = (p1, p2, p3, p4, normal, uv_w, uv_h, uv_ox = 0, uv_oy = 0) => {
positions.push(...p1, ...p2, ...p3, ...p4);
normals.push(...normal, ...normal, ...normal, ...normal);
uvs.push(
uv_ox, uv_oy,
uv_ox + uv_w, uv_oy,
uv_ox + uv_w, uv_oy + uv_h,
uv_ox, uv_oy + uv_h
);
indices.push(
index_offset, index_offset + 2, index_offset + 1,
index_offset, index_offset + 3, index_offset + 2
);
index_offset += 4;
};
// Obtenemos el índice del piso normalizado (0, 1, -1, -2...)
const py = Math.round(model.y / model.height);
// Filtrado de huecos activos en Suelo y Techo
// Suelo perforado solo si el pozo continúa hacia abajo
const floor_holes = holes.filter(hole => py > hole.yf && py < hole.yt);
// Techo perforado solo si el pozo continúa hacia arriba
const ceiling_holes = holes.filter(hole => py >= hole.yf && py < (hole.yt - 1));
// Subdivisión de un plano horizontal alrededor de 1 o más huecos
const build_horizontal_plane = (hole_list, is_floor) => {
const y_pos = is_floor ? -hy : hy;
const norm = is_floor ? [0, 1, 0] : [0, -1, 0];
const add_surface_patch = (x1, x2, z1, z2) => {
const pw = x2 - x1;
const pd = z2 - z1;
if(pw <= 0.001 || pd <= 0.001) return;
const px1 = -hx + x1, px2 = -hx + x2;
const pz1 = -hz + z1, pz2 = -hz + z2;
if(is_floor){
add_quad(
[px1, y_pos, pz1],
[px2, y_pos, pz1],
[px2, y_pos, pz2],
[px1, y_pos, pz2],
norm, pw, pd, x1, z1
);
} else {
add_quad(
[px1, y_pos, pz2],
[px2, y_pos, pz2],
[px2, y_pos, pz1],
[px1, y_pos, pz1],
norm, pw, pd, x1, z1
);
}
};
// Si no hay huecos en este plano, generamos el plano entero de una pieza
if(!hole_list || hole_list.length === 0){
add_surface_patch(0, w, 0, d);
return;
}
// Lista de parches de superficie sólida inicial (toda la habitación)
let patches = [{ x1: 0, x2: w, z1: 0, z2: d }];
for(const target_hole of hole_list){
const hx1 = Math.max(0, target_hole.xf - (model.x + pad));
const hx2 = Math.min(w, target_hole.xt - (model.x + pad));
const hz1 = Math.max(0, target_hole.zf - (model.z + pad));
const hz2 = Math.min(d, target_hole.zt - (model.z + pad));
// Si el hueco no solapa con el espacio útil de la sala, lo ignoramos
if(hx2 <= hx1 || hz2 <= hz1) continue;
const next_patches = [];
for(const p of patches){
// Comprobar si el parche actual 'p' interseca con este hueco
const ix1 = Math.max(p.x1, hx1);
const ix2 = Math.min(p.x2, hx2);
const iz1 = Math.max(p.z1, hz1);
const iz2 = Math.min(p.z2, hz2);
// Si no hay solape, el parche se conserva íntegro
if(ix1 >= ix2 || iz1 >= iz2){
next_patches.push(p);
continue;
}
// Sustracción 2D: dividimos el parche en las 4 regiones que rodean el hueco
// 1. Franja Norte (Z entre p.z1 e iz1)
if(iz1 > p.z1){
next_patches.push({ x1: p.x1, x2: p.x2, z1: p.z1, z2: iz1 });
}
// 2. Franja Sur (Z entre iz2 y p.z2)
if(p.z2 > iz2){
next_patches.push({ x1: p.x1, x2: p.x2, z1: iz2, z2: p.z2 });
}
// 3. Franja Oeste (X entre p.x1 e ix1, Z recortado entre iz1 e iz2)
if(ix1 > p.x1){
next_patches.push({ x1: p.x1, x2: ix1, z1: iz1, z2: iz2 });
}
// 4. Franja Este (X entre ix2 y p.x2, Z recortado entre iz1 e iz2)
if(p.x2 > ix2){
next_patches.push({ x1: ix2, x2: p.x2, z1: iz1, z2: iz2 });
}
}
patches = next_patches;
}
// Generamos la geometría de todos los parches sólidos resultantes
for(const p of patches){
add_surface_patch(p.x1, p.x2, p.z1, p.z2);
}
};
// --- 1. SUELO (+Y) -> Grupo 0 ---
const floor_start = indices.length;
build_horizontal_plane(floor_holes, true);
geometry.addGroup(floor_start, indices.length - floor_start, 0);
// --- 2. TECHO (-Y) -> Grupo 1 ---
const ceiling_start = indices.length;
build_horizontal_plane(ceiling_holes, false);
geometry.addGroup(ceiling_start, indices.length - ceiling_start, 1);
// --- 3. PAREDES -> Grupo 2 ---
const walls_start = indices.length;
const wall_frames = { north: [], south: [], west: [], east: [] };
frames.forEach(f => {
if(f.axis === "horizontal"){
const dist_north = Math.abs(f.z - model.z);
const dist_south = Math.abs(f.z - (model.z + model.depth));
if(dist_north <= dist_south) wall_frames.north.push(f);
else wall_frames.south.push(f);
} else {
const dist_west = Math.abs(f.x - model.x);
const dist_east = Math.abs(f.x - (model.x + model.width));
if(dist_west <= dist_east) wall_frames.west.push(f);
else wall_frames.east.push(f);
}
});
const build_segmented_wall = (wall_len, wall_h, frame_list, add_rect, normal) => {
if(!frame_list || frame_list.length === 0){
add_rect(0, wall_len, 0, wall_h, normal);
return;
}
const openings = frame_list.map(f => {
const offset = (f.axis === "horizontal")
? (f.x - (model.x + pad))
: (f.z - (model.z + pad));
return {
start: Math.max(0, offset),
end: Math.min(wall_len, offset + f.width),
height: Math.min(wall_h, f.height)
};
}).sort((a, b) => a.start - b.start);
let current_x = 0;
openings.forEach(op => {
if(op.start > current_x){
add_rect(current_x, op.start, 0, wall_h, normal);
}
if(op.height < wall_h){
add_rect(op.start, op.end, op.height, wall_h, normal);
}
current_x = Math.max(current_x, op.end);
});
if(current_x < wall_len){
add_rect(current_x, wall_len, 0, wall_h, normal);
}
};
// Norte (-Z)
build_segmented_wall(w, h, wall_frames.north, (x1, x2, y1, y2, norm) => {
const px1 = -hx + x1, px2 = -hx + x2, py1 = -hy + y1, py2 = -hy + y2;
add_quad([px2, py1, -hz], [px1, py1, -hz], [px1, py2, -hz], [px2, py2, -hz], norm, x2 - x1, y2 - y1, x1, y1);
}, [0, 0, 1]);
// Sur (+Z)
build_segmented_wall(w, h, wall_frames.south, (x1, x2, y1, y2, norm) => {
const px1 = -hx + x1, px2 = -hx + x2, py1 = -hy + y1, py2 = -hy + y2;
add_quad([px1, py1, hz], [px2, py1, hz], [px2, py2, hz], [px1, py2, hz], norm, x2 - x1, y2 - y1, x1, y1);
}, [0, 0, -1]);
// Oeste (-X)
build_segmented_wall(d, h, wall_frames.west, (z1, z2, y1, y2, norm) => {
const pz1 = -hz + z1, pz2 = -hz + z2, py1 = -hy + y1, py2 = -hy + y2;
add_quad([-hx, py1, pz1], [-hx, py1, pz2], [-hx, py2, pz2], [-hx, py2, pz1], norm, z2 - z1, y2 - y1, z1, y1);
}, [1, 0, 0]);
// Este (+X)
build_segmented_wall(d, h, wall_frames.east, (z1, z2, y1, y2, norm) => {
const pz1 = -hz + z1, pz2 = -hz + z2, py1 = -hy + y1, py2 = -hy + y2;
add_quad([hx, py1, pz2], [hx, py1, pz1], [hx, py2, pz1], [hx, py2, pz2], norm, z2 - z1, y2 - y1, z1, y1);
}, [-1, 0, 0]);
geometry.addGroup(walls_start, indices.length - walls_start, 2);
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3));
geometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
geometry.setIndex(indices);
cache[key] = geometry;
return geometry;
};
this.reset = () => {
for(const key in cache){
if(cache[key]){
cache[key].dispose();
delete cache[key];
}
}
};
};
return RoomGeometryGame;
})();