270 lines
11 KiB
JavaScript
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;
|
|
})(); |