Changes
Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import { useRef, useMemo, useEffect, useState } from 'react';
|
||||
import { Canvas, useFrame, useThree, ThreeEvent } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { mainCameraRef, mainControlsRef, requestView } from './three/viewCubeBus';
|
||||
|
||||
/** Builds a square canvas texture with a label centered on it. */
|
||||
function makeFaceTexture(label: string, accent: boolean): THREE.CanvasTexture {
|
||||
const size = 256;
|
||||
const c = document.createElement('canvas');
|
||||
c.width = size;
|
||||
c.height = size;
|
||||
const ctx = c.getContext('2d')!;
|
||||
// Background
|
||||
const grad = ctx.createLinearGradient(0, 0, 0, size);
|
||||
grad.addColorStop(0, accent ? '#1a2742' : '#101725');
|
||||
grad.addColorStop(1, accent ? '#0c1424' : '#070b14');
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
// Border
|
||||
ctx.strokeStyle = '#00f3ff';
|
||||
ctx.lineWidth = 6;
|
||||
ctx.strokeRect(3, 3, size - 6, size - 6);
|
||||
// Label
|
||||
ctx.fillStyle = '#e6faff';
|
||||
ctx.font = '700 56px JetBrains Mono, ui-monospace, monospace';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.shadowColor = '#00f3ff';
|
||||
ctx.shadowBlur = 18;
|
||||
ctx.fillText(label, size / 2, size / 2);
|
||||
const tex = new THREE.CanvasTexture(c);
|
||||
tex.anisotropy = 4;
|
||||
tex.needsUpdate = true;
|
||||
return tex;
|
||||
}
|
||||
|
||||
const FACE_DEFS: { label: string; dir: [number, number, number] }[] = [
|
||||
// BoxGeometry material order: +X, -X, +Y, -Y, +Z, -Z
|
||||
{ label: 'DIR', dir: [ 1, 0, 0] },
|
||||
{ label: 'ESQ', dir: [-1, 0, 0] },
|
||||
{ label: 'TOPO', dir: [ 0, 1, 0] },
|
||||
{ label: 'BASE', dir: [ 0,-1, 0] },
|
||||
{ label: 'FRENTE', dir: [ 0, 0, 1] },
|
||||
{ label: 'ATRÁS', dir: [ 0, 0,-1] },
|
||||
];
|
||||
|
||||
function CubeMesh() {
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const { camera: localCam } = useThree();
|
||||
const [hover, setHover] = useState<number | null>(null);
|
||||
|
||||
const materials = useMemo(() => {
|
||||
return FACE_DEFS.map((f, i) => {
|
||||
const tex = makeFaceTexture(f.label, false);
|
||||
const mat = new THREE.MeshBasicMaterial({ map: tex });
|
||||
mat.userData.baseTex = tex;
|
||||
mat.userData.hoverTex = makeFaceTexture(f.label, true);
|
||||
mat.userData.faceIndex = i;
|
||||
return mat;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
materials.forEach((m) => {
|
||||
m.userData.baseTex?.dispose?.();
|
||||
m.userData.hoverTex?.dispose?.();
|
||||
m.dispose();
|
||||
});
|
||||
};
|
||||
}, [materials]);
|
||||
|
||||
// Apply hover tint
|
||||
useEffect(() => {
|
||||
materials.forEach((m, i) => {
|
||||
m.map = i === hover ? m.userData.hoverTex : m.userData.baseTex;
|
||||
m.needsUpdate = true;
|
||||
});
|
||||
}, [hover, materials]);
|
||||
|
||||
// Mirror main camera orientation each frame
|
||||
useFrame(() => {
|
||||
const main = mainCameraRef.current;
|
||||
const controls = mainControlsRef.current;
|
||||
if (!main) return;
|
||||
const target: THREE.Vector3 = controls?.target ?? new THREE.Vector3();
|
||||
const dir = new THREE.Vector3().subVectors(main.position, target).normalize();
|
||||
// Place local cam along same direction at fixed distance
|
||||
localCam.position.copy(dir.multiplyScalar(3));
|
||||
localCam.up.copy(main.up);
|
||||
localCam.lookAt(0, 0, 0);
|
||||
});
|
||||
|
||||
const onClick = (e: ThreeEvent<MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
const idx = e.face?.materialIndex;
|
||||
if (idx == null) return;
|
||||
const def = FACE_DEFS[idx];
|
||||
if (!def) return;
|
||||
requestView(new THREE.Vector3(...def.dir));
|
||||
};
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
material={materials}
|
||||
onClick={onClick}
|
||||
onPointerMove={(e) => {
|
||||
e.stopPropagation();
|
||||
const idx = e.face?.materialIndex ?? null;
|
||||
setHover(idx);
|
||||
}}
|
||||
onPointerOut={() => setHover(null)}
|
||||
>
|
||||
<boxGeometry args={[1.4, 1.4, 1.4]} />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
/** Tiny axis tripod next to the cube to reinforce the UCS reading. */
|
||||
function AxisTripod() {
|
||||
return (
|
||||
<group position={[-1.2, -1.2, 0]}>
|
||||
<arrowHelper args={[new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 0, 0), 0.7, 0xff4444, 0.18, 0.12]} />
|
||||
<arrowHelper args={[new THREE.Vector3(0, 1, 0), new THREE.Vector3(0, 0, 0), 0.7, 0x44ff66, 0.18, 0.12]} />
|
||||
<arrowHelper args={[new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 0.7, 0x4488ff, 0.18, 0.12]} />
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function ViewCube() {
|
||||
return (
|
||||
<div
|
||||
className="absolute right-3 top-3 z-20 h-[120px] w-[120px] rounded-md border border-primary/30 bg-background/40 backdrop-blur-sm shadow-lg"
|
||||
style={{ pointerEvents: 'auto' }}
|
||||
title="Cubo de vistas — clique numa face para girar"
|
||||
>
|
||||
<Canvas
|
||||
camera={{ position: [2, 2, 2], fov: 35, near: 0.1, far: 50 }}
|
||||
gl={{ antialias: true, alpha: true }}
|
||||
dpr={[1, 2]}
|
||||
>
|
||||
<ambientLight intensity={1} />
|
||||
<CubeMesh />
|
||||
<AxisTripod />
|
||||
</Canvas>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user