🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 10:42:44
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Suspense, useRef, useMemo, useEffect, type ReactNode, useState } from 'react';
|
||||
import { Canvas, useThree, useFrame } from '@react-three/fiber';
|
||||
import { OrbitControls, useGLTF, Grid, Html, Line, PerspectiveCamera, OrthographicCamera } from '@react-three/drei';
|
||||
import { OrbitControls, useGLTF, Grid, Html, Line, PerspectiveCamera, OrthographicCamera, PointerLockControls, Sky, Environment } from '@react-three/drei';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import * as THREE from 'three';
|
||||
import { useModelStore, type SceneModel } from '@/stores/useModelStore';
|
||||
@@ -1330,10 +1330,124 @@ function PositionDragHandler() {
|
||||
|
||||
|
||||
|
||||
function WalkControls() {
|
||||
const walkMode = useModelStore(s => s.walkMode);
|
||||
const setWalkMode = useModelStore(s => s.setWalkMode);
|
||||
const gridY = useModelStore(s => s.gridY);
|
||||
const { camera } = useThree();
|
||||
const controlsRef = useRef<any>(null);
|
||||
const keys = useRef({ w: false, a: false, s: false, d: false, shift: false });
|
||||
const initialized = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (walkMode && !initialized.current) {
|
||||
camera.position.set(0, gridY + 1.7, 5);
|
||||
// Give the camera an initial rotation looking forward
|
||||
camera.rotation.set(0, 0, 0);
|
||||
initialized.current = true;
|
||||
} else if (!walkMode) {
|
||||
initialized.current = false;
|
||||
}
|
||||
}, [walkMode, camera, gridY]);
|
||||
|
||||
useEffect(() => {
|
||||
if (walkMode) {
|
||||
// Small timeout to allow canvas to render and be clickable
|
||||
const timer = setTimeout(() => {
|
||||
try { controlsRef.current?.lock(); } catch (e) {}
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [walkMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW') keys.current.w = true;
|
||||
if (e.code === 'KeyA') keys.current.a = true;
|
||||
if (e.code === 'KeyS') keys.current.s = true;
|
||||
if (e.code === 'KeyD') keys.current.d = true;
|
||||
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') keys.current.shift = true;
|
||||
};
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === 'KeyW') keys.current.w = false;
|
||||
if (e.code === 'KeyA') keys.current.a = false;
|
||||
if (e.code === 'KeyS') keys.current.s = false;
|
||||
if (e.code === 'KeyD') keys.current.d = false;
|
||||
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') keys.current.shift = false;
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown);
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useFrame((_, dt) => {
|
||||
if (!walkMode) return;
|
||||
const speed = keys.current.shift ? 6.0 : 2.5;
|
||||
const dir = new THREE.Vector3();
|
||||
|
||||
// Front vector (ignoring pitch to walk purely horizontally)
|
||||
const front = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
|
||||
front.y = 0;
|
||||
if (front.lengthSq() > 0.001) front.normalize();
|
||||
|
||||
// Right vector
|
||||
const right = new THREE.Vector3(1, 0, 0).applyQuaternion(camera.quaternion);
|
||||
right.y = 0;
|
||||
if (right.lengthSq() > 0.001) right.normalize();
|
||||
|
||||
if (keys.current.w) dir.add(front);
|
||||
if (keys.current.s) dir.sub(front);
|
||||
if (keys.current.a) dir.sub(right);
|
||||
if (keys.current.d) dir.add(right);
|
||||
|
||||
if (dir.lengthSq() > 0) {
|
||||
dir.normalize();
|
||||
camera.position.addScaledVector(dir, speed * dt);
|
||||
}
|
||||
// Always stick to exactly 1.7m above ground
|
||||
camera.position.y = gridY + 1.7;
|
||||
});
|
||||
|
||||
if (!walkMode) return null;
|
||||
|
||||
return (
|
||||
<PointerLockControls
|
||||
ref={controlsRef}
|
||||
onUnlock={() => {
|
||||
setWalkMode(false);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function WalkEnvironment() {
|
||||
const walkMode = useModelStore(s => s.walkMode);
|
||||
const gridY = useModelStore(s => s.gridY);
|
||||
if (!walkMode) return null;
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Realistic blue sky with some clouds/haze */}
|
||||
<Sky sunPosition={[100, 20, 100]} turbidity={0.1} rayleigh={0.5} mieCoefficient={0.005} mieDirectionalG={0.8} />
|
||||
{/* Background HDRI provides distant trees, mountains, houses on the horizon */}
|
||||
<Environment preset="park" background />
|
||||
{/* Infinite walkable grass floor */}
|
||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, gridY - 0.01, 0]} receiveShadow>
|
||||
<planeGeometry args={[1000, 1000]} />
|
||||
<meshStandardMaterial color="#4ade80" roughness={0.9} metalness={0.1} />
|
||||
</mesh>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
const positionMode = useModelStore((s) => s.positionMode);
|
||||
const measureMode = useModelStore((s) => s.measureMode);
|
||||
const selectionMode = useModelStore((s) => s.selectionMode);
|
||||
const walkMode = useModelStore((s) => s.walkMode);
|
||||
return (
|
||||
<Canvas
|
||||
camera={{ position: [2, 2, 2], fov: 50, near: 0.001, far: 1000 }}
|
||||
@@ -1376,6 +1490,9 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
<VisibilityApplier />
|
||||
<SceneRefCapture />
|
||||
<ViewCubeAnimator />
|
||||
|
||||
<WalkControls />
|
||||
<WalkEnvironment />
|
||||
|
||||
<OrbitControls
|
||||
makeDefault
|
||||
@@ -1385,7 +1502,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||
maxDistance={50}
|
||||
minZoom={5}
|
||||
maxZoom={5000}
|
||||
enabled={!positionMode}
|
||||
enabled={!positionMode && !walkMode}
|
||||
ref={(c: any) => {
|
||||
mainControlsRef.current = c;
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user