Mejoras en vistas isométricas y sistema de opacidad de paredes

- Sistema de opacidad dinámica de paredes según vista actual
- Vistas con transición animada suave (600ms)
- Centrado automático en el jugador al cambiar vista
- Quaternions precalculados para evitar degradación de vistas
- Validación de puertas que apuntan a salas existentes
- Limpieza de puertas inválidas en generador de mazmorras
- Paredes opacas/transparentes según orientación de cámara

Pendiente: Resolver z-fighting de puertas en ciertas vistas
This commit is contained in:
2025-12-21 00:43:36 +01:00
parent 92fdfed49c
commit 7cc92da012

View File

@@ -126,6 +126,18 @@ function generateDungeon() {
}
}
// Limpiar puertas que apuntan a salas inexistentes
const existingRoomIds = new Set(rooms.map(r => r.id));
rooms.forEach(room => {
room.doors = room.doors.filter(door => {
const isValid = existingRoomIds.has(door.leadsTo);
if (!isValid) {
console.log(`Eliminando puerta inválida en sala ${room.id} que apunta a sala ${door.leadsTo}`);
}
return isValid;
});
});
return {
rooms: rooms,
visitedRooms: new Set([1]),
@@ -210,19 +222,116 @@ controls.zoomToCursor = true;
controls.minZoom = 0.5;
controls.maxZoom = 3;
function setCameraView(direction) {
// Determinar opacidad de pared según vista actual
function getWallOpacity(wallSide, viewDirection) {
const opacityRules = {
N: { opaque: ['N', 'W'], transparent: ['S', 'E'] },
S: { opaque: ['S', 'E'], transparent: ['N', 'W'] },
E: { opaque: ['N', 'E'], transparent: ['S', 'W'] },
W: { opaque: ['W', 'S'], transparent: ['N', 'E'] }
};
const rule = opacityRules[viewDirection];
if (rule.opaque.includes(wallSide)) {
return 1.0; // Opaco
} else {
return 0.5; // Semi-transparente
}
}
// Actualizar opacidades de todas las paredes según la vista actual
function updateWallOpacities() {
Object.values(SESSION.roomMeshes).forEach(roomData => {
if (roomData.walls) {
roomData.walls.forEach(wall => {
const wallSide = wall.userData.wallSide;
if (wallSide) {
const newOpacity = getWallOpacity(wallSide, SESSION.currentView);
wall.material.opacity = newOpacity;
wall.material.transparent = newOpacity < 1.0;
}
});
}
});
}
function setCameraView(direction, animate = true) {
const view = CAMERA_VIEWS[direction];
// Aplicar posición, quaternion y up FIJOS (sin lookAt que acumula errores)
camera.position.copy(view.position);
camera.quaternion.copy(view.quaternion);
camera.up.copy(view.up);
// Encontrar el personaje del jugador para centrar la vista
let playerPosition = new THREE.Vector3(0, 0, 0);
for (const room of ROOMS.rooms) {
const player = room.entities.find(e => e.type === 'hero_1');
if (player && player.mesh) {
playerPosition.copy(player.mesh.position);
playerPosition.y = 0;
break;
}
}
controls.target.copy(view.target);
controls.update();
// Calcular offset de la vista (diferencia entre posición y target)
const viewOffset = view.position.clone().sub(view.target);
// Nueva posición de cámara centrada en el jugador
const newPosition = playerPosition.clone().add(viewOffset);
const newTarget = playerPosition.clone();
if (animate && SESSION.currentView !== direction) {
// Animación suave de transición
const startPos = camera.position.clone();
const startQuat = camera.quaternion.clone();
const startTarget = controls.target.clone();
const duration = 600; // ms
const startTime = Date.now();
const animateTransition = () => {
const elapsed = Date.now() - startTime;
const progress = Math.min(elapsed / duration, 1);
// Easing suave (ease-in-out)
const eased = progress < 0.5
? 2 * progress * progress
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
// Interpolar posición
camera.position.lerpVectors(startPos, newPosition, eased);
// Interpolar quaternion (rotación suave)
camera.quaternion.slerpQuaternions(startQuat, view.quaternion, eased);
// Interpolar target
controls.target.lerpVectors(startTarget, newTarget, eased);
camera.up.copy(view.up);
controls.update();
if (progress < 1) {
requestAnimationFrame(animateTransition);
} else {
// Asegurar valores finales exactos
camera.position.copy(newPosition);
camera.quaternion.copy(view.quaternion);
camera.up.copy(view.up);
controls.target.copy(newTarget);
controls.update();
}
};
animateTransition();
} else {
// Sin animación (cambio instantáneo)
camera.position.copy(newPosition);
camera.quaternion.copy(view.quaternion);
camera.up.copy(view.up);
controls.target.copy(newTarget);
controls.update();
}
SESSION.currentView = direction;
updateCompassUI();
updateWallOpacities(); // Actualizar opacidades de paredes según nueva vista
}
// Establecer vista inicial
@@ -619,18 +728,20 @@ async function renderRoom(room) {
const wallGeometry = new THREE.PlaneGeometry(worldWidth, wallHeight);
const wallConfigs = [
{ side: 'N', offset: { x: 0, z: -halfSizeZ }, rotation: 0, opacity: 1.0 },
{ side: 'S', offset: { x: 0, z: halfSizeZ }, rotation: 0, opacity: 0.5 },
{ side: 'E', offset: { x: halfSizeX, z: 0 }, rotation: Math.PI / 2, opacity: 0.5 },
{ side: 'W', offset: { x: -halfSizeX, z: 0 }, rotation: Math.PI / 2, opacity: 1.0 }
{ side: 'N', offset: { x: 0, z: -halfSizeZ }, rotation: 0 },
{ side: 'S', offset: { x: 0, z: halfSizeZ }, rotation: 0 },
{ side: 'E', offset: { x: halfSizeX, z: 0 }, rotation: Math.PI / 2 },
{ side: 'W', offset: { x: -halfSizeX, z: 0 }, rotation: Math.PI / 2 }
];
for (const config of wallConfigs) {
if (room.walls.includes(config.side)) {
const opacity = getWallOpacity(config.side, SESSION.currentView);
const wallMaterial = new THREE.MeshStandardMaterial({
map: wallTex.clone(),
transparent: config.opacity < 1.0,
opacity: config.opacity,
transparent: opacity < 1.0,
opacity: opacity,
side: THREE.DoubleSide
});
@@ -643,6 +754,7 @@ async function renderRoom(room) {
wall.rotation.y = config.rotation;
wall.castShadow = true;
wall.receiveShadow = true;
wall.userData.wallSide = config.side; // Metadata para identificar el lado
scene.add(wall);
roomMeshes.walls.push(wall);
}
@@ -654,6 +766,13 @@ async function renderRoom(room) {
const doorHeight = 2.0;
for (const door of room.doors) {
// Verificar que la sala destino existe
const targetRoom = ROOMS.rooms.find(r => r.id === door.leadsTo);
if (!targetRoom) {
console.warn(`Puerta en sala ${room.id} apunta a sala inexistente ${door.leadsTo}`);
continue; // Saltar esta puerta
}
const doorGeometry = new THREE.PlaneGeometry(doorWidth, doorHeight);
const doorMaterial = new THREE.MeshStandardMaterial({
map: doorTex.clone(),