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:
145
src/main.js
145
src/main.js
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user