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 {
|
return {
|
||||||
rooms: rooms,
|
rooms: rooms,
|
||||||
visitedRooms: new Set([1]),
|
visitedRooms: new Set([1]),
|
||||||
@@ -210,19 +222,116 @@ controls.zoomToCursor = true;
|
|||||||
controls.minZoom = 0.5;
|
controls.minZoom = 0.5;
|
||||||
controls.maxZoom = 3;
|
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];
|
const view = CAMERA_VIEWS[direction];
|
||||||
|
|
||||||
// Aplicar posición, quaternion y up FIJOS (sin lookAt que acumula errores)
|
// Encontrar el personaje del jugador para centrar la vista
|
||||||
camera.position.copy(view.position);
|
let playerPosition = new THREE.Vector3(0, 0, 0);
|
||||||
camera.quaternion.copy(view.quaternion);
|
for (const room of ROOMS.rooms) {
|
||||||
camera.up.copy(view.up);
|
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);
|
// Calcular offset de la vista (diferencia entre posición y target)
|
||||||
controls.update();
|
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;
|
SESSION.currentView = direction;
|
||||||
updateCompassUI();
|
updateCompassUI();
|
||||||
|
updateWallOpacities(); // Actualizar opacidades de paredes según nueva vista
|
||||||
}
|
}
|
||||||
|
|
||||||
// Establecer vista inicial
|
// Establecer vista inicial
|
||||||
@@ -619,18 +728,20 @@ async function renderRoom(room) {
|
|||||||
const wallGeometry = new THREE.PlaneGeometry(worldWidth, wallHeight);
|
const wallGeometry = new THREE.PlaneGeometry(worldWidth, wallHeight);
|
||||||
|
|
||||||
const wallConfigs = [
|
const wallConfigs = [
|
||||||
{ side: 'N', offset: { x: 0, z: -halfSizeZ }, rotation: 0, opacity: 1.0 },
|
{ side: 'N', offset: { x: 0, z: -halfSizeZ }, rotation: 0 },
|
||||||
{ side: 'S', offset: { x: 0, z: halfSizeZ }, rotation: 0, opacity: 0.5 },
|
{ side: 'S', offset: { x: 0, z: halfSizeZ }, rotation: 0 },
|
||||||
{ side: 'E', offset: { x: halfSizeX, z: 0 }, rotation: Math.PI / 2, opacity: 0.5 },
|
{ side: 'E', offset: { x: halfSizeX, z: 0 }, rotation: Math.PI / 2 },
|
||||||
{ side: 'W', offset: { x: -halfSizeX, z: 0 }, rotation: Math.PI / 2, opacity: 1.0 }
|
{ side: 'W', offset: { x: -halfSizeX, z: 0 }, rotation: Math.PI / 2 }
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const config of wallConfigs) {
|
for (const config of wallConfigs) {
|
||||||
if (room.walls.includes(config.side)) {
|
if (room.walls.includes(config.side)) {
|
||||||
|
const opacity = getWallOpacity(config.side, SESSION.currentView);
|
||||||
|
|
||||||
const wallMaterial = new THREE.MeshStandardMaterial({
|
const wallMaterial = new THREE.MeshStandardMaterial({
|
||||||
map: wallTex.clone(),
|
map: wallTex.clone(),
|
||||||
transparent: config.opacity < 1.0,
|
transparent: opacity < 1.0,
|
||||||
opacity: config.opacity,
|
opacity: opacity,
|
||||||
side: THREE.DoubleSide
|
side: THREE.DoubleSide
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -643,6 +754,7 @@ async function renderRoom(room) {
|
|||||||
wall.rotation.y = config.rotation;
|
wall.rotation.y = config.rotation;
|
||||||
wall.castShadow = true;
|
wall.castShadow = true;
|
||||||
wall.receiveShadow = true;
|
wall.receiveShadow = true;
|
||||||
|
wall.userData.wallSide = config.side; // Metadata para identificar el lado
|
||||||
scene.add(wall);
|
scene.add(wall);
|
||||||
roomMeshes.walls.push(wall);
|
roomMeshes.walls.push(wall);
|
||||||
}
|
}
|
||||||
@@ -654,6 +766,13 @@ async function renderRoom(room) {
|
|||||||
const doorHeight = 2.0;
|
const doorHeight = 2.0;
|
||||||
|
|
||||||
for (const door of room.doors) {
|
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 doorGeometry = new THREE.PlaneGeometry(doorWidth, doorHeight);
|
||||||
const doorMaterial = new THREE.MeshStandardMaterial({
|
const doorMaterial = new THREE.MeshStandardMaterial({
|
||||||
map: doorTex.clone(),
|
map: doorTex.clone(),
|
||||||
|
|||||||
Reference in New Issue
Block a user