From 7cc92da012a3eee239ca2254b6d26a78e7f29a4c Mon Sep 17 00:00:00 2001 From: marti Date: Sun, 21 Dec 2025 00:43:36 +0100 Subject: [PATCH] =?UTF-8?q?Mejoras=20en=20vistas=20isom=C3=A9tricas=20y=20?= =?UTF-8?q?sistema=20de=20opacidad=20de=20paredes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- src/main.js | 145 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 132 insertions(+), 13 deletions(-) diff --git a/src/main.js b/src/main.js index 78e8b31..f579bf7 100644 --- a/src/main.js +++ b/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(),