🚀 Auto-deploy: melhoria no snap e medição AR em 30/05/2026 12:24:45

This commit is contained in:
2026-05-30 12:24:45 +00:00
parent 7489b611b0
commit e4a9c8623a
+41 -21
View File
@@ -32,7 +32,7 @@ class ModelErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBounda
} }
} }
import { Canvas, useFrame } from '@react-three/fiber'; import { Canvas, useFrame } from '@react-three/fiber';
import { OrbitControls, Text, useGLTF, Billboard, Html } from '@react-three/drei'; import { OrbitControls, Text, useGLTF, Billboard, Html, TransformControls } from '@react-three/drei';
import * as THREE from 'three'; import * as THREE from 'three';
import { supabase } from '@/integrations/supabase/client'; import { supabase } from '@/integrations/supabase/client';
import { useModelStore, type SceneModel } from '@/stores/useModelStore'; import { useModelStore, type SceneModel } from '@/stores/useModelStore';
@@ -527,12 +527,13 @@ function RealMiiAvatar({
// Carregador e Escalador Inteligente da Maquete Holográfica // Carregador e Escalador Inteligente da Maquete Holográfica
// Carregador e Escalador Inteligente da Maquete Holográfica // Carregador e Escalador Inteligente da Maquete Holográfica
function HologramModel({ model, presentationState }: { model: SceneModel; presentationState: PresentationState }) { function HologramModel({ model, presentationState, isPresenter, onPresentationChange }: { model: SceneModel; presentationState: PresentationState; isPresenter: boolean; onPresentationChange: (state: Partial<PresentationState>) => void }) {
const { scene } = useGLTF(model.url); const { scene } = useGLTF(model.url);
const clone = useMemo(() => scene.clone(), [scene]); const clone = useMemo(() => scene.clone(), [scene]);
const presentationGroupRef = useRef<THREE.Group>(null); const presentationGroupRef = useRef<THREE.Group>(null);
const baseScaleRef = useRef<number>(1); const baseScaleRef = useRef<number>(1);
const centerRef = useRef<THREE.Vector3>(new THREE.Vector3()); const centerRef = useRef<THREE.Vector3>(new THREE.Vector3());
const [isDragging, setIsDragging] = useState(false);
// Calcula escala de aproximadamente 1 metro para caber na mesa (apenas UMA vez no carregamento) // Calcula escala de aproximadamente 1 metro para caber na mesa (apenas UMA vez no carregamento)
useEffect(() => { useEffect(() => {
@@ -589,12 +590,10 @@ function HologramModel({ model, presentationState }: { model: SceneModel; presen
// Aplica transformações de Rotação e Escala compartilhadas no grupo externo de apresentação // Aplica transformações de Rotação e Escala compartilhadas no grupo externo de apresentação
useFrame(() => { useFrame(() => {
if (presentationGroupRef.current && presentationState) { if (presentationGroupRef.current && presentationState && !isDragging) {
const { rotation, scale } = presentationState; const { rotation, scale } = presentationState;
if (rotation) { if (rotation) {
presentationGroupRef.current.rotation.x = rotation[0]; presentationGroupRef.current.rotation.set(rotation[0], rotation[1], rotation[2]);
presentationGroupRef.current.rotation.y = rotation[1];
presentationGroupRef.current.rotation.z = rotation[2];
} }
if (scale) { if (scale) {
presentationGroupRef.current.scale.setScalar(baseScaleRef.current * scale); presentationGroupRef.current.scale.setScalar(baseScaleRef.current * scale);
@@ -666,27 +665,48 @@ function HologramModel({ model, presentationState }: { model: SceneModel; presen
}, [calQuatArr]); }, [calQuatArr]);
return ( return (
// Grupo Externo de Apresentação Compartilhada <>
<group ref={presentationGroupRef}> {isPresenter && (
{/* Grupo de Translação Fina */} <TransformControls
<group position={[fineTuning.posX, fineTuning.posY, fineTuning.posZ]}> object={presentationGroupRef}
{/* Grupo de Rotação Fina e Escala Fina */} mode="rotate"
<group rotation={[rotXRad, rotYRad, rotZRad]} scale={[s, s, s]}> size={0.6}
{/* Grupo de Calibração */} onDraggingChanged={(e) => setIsDragging(!!e?.value)}
<group quaternion={calQuat}> onChange={() => {
{/* Grupo de Centralização do Bounding Box (para girar em torno do seu centro geométrico) */} if (isDragging && presentationGroupRef.current) {
<group position={[-centerRef.current.x, -centerRef.current.y, -centerRef.current.z]}> onPresentationChange({
<primitive object={clone} /> rotation: [
presentationGroupRef.current.rotation.x,
presentationGroupRef.current.rotation.y,
presentationGroupRef.current.rotation.z
]
});
}
}}
/>
)}
{/* Grupo Externo de Apresentação Compartilhada */}
<group ref={presentationGroupRef}>
{/* Ignoramos fineTuning.posX/Y/Z para ancorar perfeitamente à mesa */}
<group>
{/* Grupo de Rotação Fina e Escala Fina */}
<group rotation={[rotXRad, rotYRad, rotZRad]} scale={[s, s, s]}>
{/* Grupo de Calibração */}
<group quaternion={calQuat}>
{/* Grupo de Centralização do Bounding Box */}
<group position={[-centerRef.current.x, -centerRef.current.y, -centerRef.current.z]}>
<primitive object={clone} />
</group>
</group> </group>
</group> </group>
</group> </group>
</group> </group>
</group> </>
); );
} }
// Cenário da Sala de Reunião Clara // Cenário da Sala de Reunião Clara
function ThreeMeetingRoomScene({ currentActiveModel, presentationState, isFocusMode }: { currentActiveModel: SceneModel | null; presentationState: PresentationState; isFocusMode: boolean }) { function ThreeMeetingRoomScene({ currentActiveModel, presentationState, isFocusMode, isPresenter, onPresentationChange }: { currentActiveModel: SceneModel | null; presentationState: PresentationState; isFocusMode: boolean; isPresenter: boolean; onPresentationChange: (state: Partial<PresentationState>) => void }) {
const tableGeom = useMemo(() => { const tableGeom = useMemo(() => {
// Geometria da mesa em U / Retangular de cantos arredondados // Geometria da mesa em U / Retangular de cantos arredondados
const shape = new THREE.Shape(); const shape = new THREE.Shape();
@@ -793,7 +813,7 @@ function ThreeMeetingRoomScene({ currentActiveModel, presentationState, isFocusM
</mesh> </mesh>
}> }>
{currentActiveModel ? ( {currentActiveModel ? (
<HologramModel model={currentActiveModel} presentationState={presentationState} /> <HologramModel model={currentActiveModel} presentationState={presentationState} isPresenter={isPresenter} onPresentationChange={onPresentationChange} />
) : ( ) : (
// Holograma Padrão Geométrico se não houver maquete carregada // Holograma Padrão Geométrico se não houver maquete carregada
<group rotation={[0, Date.now() * 0.00012, 0]}> <group rotation={[0, Date.now() * 0.00012, 0]}>
@@ -1717,7 +1737,7 @@ export default function MeetingRoom() {
<div className="flex-1 h-full relative"> <div className="flex-1 h-full relative">
{isWebGLSupported ? ( {isWebGLSupported ? (
<Canvas camera={{ position: [0, 2.5, 2.8], fov: 45 }} shadows gl={{ localClippingEnabled: true }}> <Canvas camera={{ position: [0, 2.5, 2.8], fov: 45 }} shadows gl={{ localClippingEnabled: true }}>
<ThreeMeetingRoomScene currentActiveModel={activeModel} presentationState={presentationState} isFocusMode={isFocusMode} /> <ThreeMeetingRoomScene currentActiveModel={activeModel} presentationState={presentationState} isFocusMode={isFocusMode} isPresenter={isPresenter} onPresentationChange={handlePresentationChange} />
{/* Renderizar avatares de TODOS na sala - Ocultados se modo foco */} {/* Renderizar avatares de TODOS na sala - Ocultados se modo foco */}
{!isFocusMode && Object.keys(mergedPeers).map((chairKey) => { {!isFocusMode && Object.keys(mergedPeers).map((chairKey) => {