155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
import { useEffect, useRef } from 'react';
|
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
import { PointerLockControls } from '@react-three/drei';
|
|
import * as THREE from 'three';
|
|
import { useModelStore } from '@/stores/useModelStore';
|
|
|
|
export function DesktopFirstPersonControls() {
|
|
const { camera } = useThree();
|
|
const activeMode = useModelStore((s) => s.desktopCameraMode);
|
|
const xrEnvironment = useModelStore((s) => s.xrEnvironment);
|
|
|
|
// Armazena o estado atual das teclas de locomoção
|
|
const keys = useRef({
|
|
w: false,
|
|
a: false,
|
|
s: false,
|
|
d: false,
|
|
arrowup: false,
|
|
arrowleft: false,
|
|
arrowdown: false,
|
|
arrowright: false,
|
|
q: false, // voar para baixo
|
|
e: false, // voar para cima
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (activeMode !== 'walk') return;
|
|
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
const k = e.key.toLowerCase();
|
|
if (k in keys.current) {
|
|
keys.current[k as keyof typeof keys.current] = true;
|
|
}
|
|
};
|
|
|
|
const handleKeyUp = (e: KeyboardEvent) => {
|
|
const k = e.key.toLowerCase();
|
|
if (k in keys.current) {
|
|
keys.current[k as keyof typeof keys.current] = false;
|
|
}
|
|
};
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
window.addEventListener('keyup', handleKeyUp);
|
|
|
|
return () => {
|
|
window.removeEventListener('keydown', handleKeyDown);
|
|
window.removeEventListener('keyup', handleKeyUp);
|
|
};
|
|
}, [activeMode]);
|
|
|
|
// Define posições iniciais amigáveis ao alternar para o modo passeio
|
|
const lastMode = useRef<string | null>(null);
|
|
useEffect(() => {
|
|
if (activeMode === 'walk' && lastMode.current !== 'walk') {
|
|
if (xrEnvironment === 'office') {
|
|
// Posiciona o operador em pé na ponta da mesa
|
|
camera.position.set(0, 1.4, 1.3);
|
|
camera.lookAt(0, 0.85, 0);
|
|
} else {
|
|
// Posiciona no campo aberto em escala real
|
|
camera.position.set(0, 1.65, 3);
|
|
camera.lookAt(0, 1.0, 0);
|
|
}
|
|
}
|
|
lastMode.current = activeMode;
|
|
}, [activeMode, xrEnvironment, camera]);
|
|
|
|
useFrame((state, delta) => {
|
|
if (activeMode !== 'walk') return;
|
|
|
|
const speed = 2.0 * delta; // Metros por frame adaptativo
|
|
const rotSpeed = 1.5 * delta;
|
|
|
|
const direction = new THREE.Vector3();
|
|
const side = new THREE.Vector3();
|
|
|
|
// Obtém vetor de olhar da câmera
|
|
camera.getWorldDirection(direction);
|
|
direction.y = 0; // Trava o vetor na horizontal para não voar para cima/baixo ao olhar
|
|
direction.normalize();
|
|
|
|
// Obtém vetor lateral
|
|
side.crossVectors(camera.up, direction).normalize();
|
|
|
|
// 1. Leituras do teclado
|
|
let moveFwd = 0;
|
|
let moveSide = 0;
|
|
let moveUp = 0;
|
|
|
|
if (keys.current.w || keys.current.arrowup) moveFwd += 1;
|
|
if (keys.current.s || keys.current.arrowdown) moveFwd -= 1;
|
|
if (keys.current.d || keys.current.arrowright) moveSide -= 1;
|
|
if (keys.current.a || keys.current.arrowleft) moveSide += 1;
|
|
if (keys.current.e) moveUp += 1;
|
|
if (keys.current.q) moveUp -= 1;
|
|
|
|
camera.position.addScaledVector(direction, moveFwd * speed);
|
|
camera.position.addScaledVector(side, moveSide * speed);
|
|
camera.position.y += moveUp * speed;
|
|
|
|
// 2. Leituras da Gamepad API (Xbox / PlayStation)
|
|
const gamepads = navigator.getGamepads();
|
|
const gp = gamepads[0] || gamepads[1];
|
|
if (gp) {
|
|
const deadzone = 0.15;
|
|
|
|
// Analógico Esquerdo (Locomoção)
|
|
const stickLX = gp.axes[0]; // Eixo lateral
|
|
const stickLY = gp.axes[1]; // Eixo frente/trás
|
|
|
|
if (Math.abs(stickLY) > deadzone) {
|
|
camera.position.addScaledVector(direction, -stickLY * speed);
|
|
}
|
|
if (Math.abs(stickLX) > deadzone) {
|
|
camera.position.addScaledVector(side, -stickLX * speed);
|
|
}
|
|
|
|
// Analógico Direito (Rotação da Câmera)
|
|
const stickRX = gp.axes[2]; // Rotação Y
|
|
const stickRY = gp.axes[3]; // Olhar X cima/baixo
|
|
|
|
if (Math.abs(stickRX) > deadzone) {
|
|
camera.rotation.y -= stickRX * rotSpeed * 1.6;
|
|
}
|
|
if (Math.abs(stickRY) > deadzone) {
|
|
// Rotaciona verticalmente
|
|
camera.rotation.x -= stickRY * rotSpeed * 1.6;
|
|
// Limita ângulo vertical para evitar inverter a visão (entre -80° e 80°)
|
|
camera.rotation.x = Math.max(-1.4, Math.min(1.4, camera.rotation.x));
|
|
}
|
|
|
|
// Gatilhos para controle de altura (RT sobe, LT desce)
|
|
const triggerLT = gp.buttons[6];
|
|
const triggerRT = gp.buttons[7];
|
|
if (triggerRT && triggerRT.value > 0.1) {
|
|
camera.position.y += triggerRT.value * speed;
|
|
}
|
|
if (triggerLT && triggerLT.value > 0.1) {
|
|
camera.position.y -= triggerLT.value * speed;
|
|
}
|
|
}
|
|
|
|
// Trava de colisão com o chão/mesa para manter realismo
|
|
const minHeight = xrEnvironment === 'office' ? 0.9 : 0.1;
|
|
if (camera.position.y < minHeight) {
|
|
camera.position.y = minHeight;
|
|
}
|
|
});
|
|
|
|
if (activeMode !== 'walk') return null;
|
|
|
|
return <PointerLockControls makeDefault />;
|
|
}
|