🚀 Auto-deploy: melhoria no snap e medição AR em 25/05/2026 00:07:59
This commit is contained in:
+250
-196
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState, useMemo } from 'react';
|
import { useEffect, useRef, useState, useMemo, Suspense } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
import { Canvas, useFrame } from '@react-three/fiber';
|
||||||
import { OrbitControls, Text, Line } from '@react-three/drei';
|
import { OrbitControls, Text, useGLTF } 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 } from '@/stores/useModelStore';
|
import { useModelStore } from '@/stores/useModelStore';
|
||||||
@@ -10,7 +10,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import {
|
import {
|
||||||
Users, MessageSquare, Mic, MicOff, Home, Send,
|
Users, MessageSquare, Mic, MicOff, Home, Send,
|
||||||
ArrowRight, Check, ChevronLeft, ChevronRight, Share2, LogOut
|
ArrowRight, ChevronLeft, ChevronRight, Share2, LogOut
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// --- Configurações da Mesa de Reunião ---
|
// --- Configurações da Mesa de Reunião ---
|
||||||
@@ -26,42 +26,60 @@ const CHAIRS = [
|
|||||||
{ id: '8', name: 'Assento 8', pos: [-1.2, 0.95, 0.6], lookAt: [0, 0.95, 0] },
|
{ id: '8', name: 'Assento 8', pos: [-1.2, 0.95, 0.6], lookAt: [0, 0.95, 0] },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 10 Modelos de Avatar Futurista (representações no Lobby)
|
// 10 Modelos de Avatar estilo Nintendo Wii (Mii) com características humanas
|
||||||
const AVATAR_PRESETS = [
|
const AVATAR_PRESETS = [
|
||||||
{ id: 1, name: 'Cyber Helmet', desc: 'Elmo octaédrico com visor ciano', color: '#0ea5e9' },
|
{ id: 1, name: 'Mii Cyber', desc: 'Pele clara com óculos vermelhos e cabelo curto', color: '#ef4444' },
|
||||||
{ id: 2, name: 'Gold Mask', desc: 'Máscara poliédrica dourada', color: '#eab308' },
|
{ id: 2, name: 'Mii Classic', desc: 'Pele média com cabelo castanho clássico', color: '#b45309' },
|
||||||
{ id: 3, name: 'Neon Ring', desc: 'Esfera com órbita luminosa', color: '#ec4899' },
|
{ id: 3, name: 'Mii Goldie', desc: 'Cabelo loiro longo com olhos azuis expressivos', color: '#eab308' },
|
||||||
{ id: 4, name: 'Retro Bot', desc: 'Unidade cúbica com visor esmeralda', color: '#10b981' },
|
{ id: 4, name: 'Mii Sporty', desc: 'Boné azul virado para trás e sorriso aberto', color: '#3b82f6' },
|
||||||
{ id: 5, name: 'Crystal Prism', desc: 'Estrutura geométrica ametista', color: '#a855f7' },
|
{ id: 5, name: 'Mii Ginger', desc: 'Cabelo ruivo longo ondulado e expressivo', color: '#f97316' },
|
||||||
{ id: 6, name: 'Vector Visor', desc: 'Busto metálico com LED vermelho', color: '#ef4444' },
|
{ id: 6, name: 'Mii Sleek', desc: 'Pele parda com cabelo curto liso lateral', color: '#10b981' },
|
||||||
{ id: 7, name: 'Quantum Core', desc: 'Núcleo de energia flutuante', color: '#00f5ff' },
|
{ id: 7, name: 'Mii Beard', desc: 'Careca estilosa com barba e bigode', color: '#78350f' },
|
||||||
{ id: 8, name: 'Obsidian Shell', desc: 'Casca geométrica com núcleo laranja', color: '#f97316' },
|
{ id: 8, name: 'Mii Sun', desc: 'Cabelo raspado e óculos escuros pretos', color: '#1e293b' },
|
||||||
{ id: 9, name: 'Helix Guardian', desc: 'Espiral cósmica dupla', color: '#3b82f6' },
|
{ id: 9, name: 'Mii Synth', desc: 'Cabelo roxo ondulado moderno', color: '#a855f7' },
|
||||||
{ id: 10, name: 'Carbon Apex', desc: 'Estrutura furtiva em cinza grafite', color: '#64748b' },
|
{ id: 10, name: 'Mii Straw', desc: 'Chapéu de palha do campo clássico', color: '#ca8a04' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- Subcomponentes 3D do Cenário ---
|
// Cores de pele padrão para os Miis
|
||||||
|
const SKIN_COLORS = ['#fed7aa', '#fdba74', '#f59e0b', '#d97706', '#9a3412', '#ffedd5'];
|
||||||
|
|
||||||
// Avatar Tridimensional Renderizado
|
// --- Subcomponentes 3D ---
|
||||||
function ThreeAvatar({ avatarId, color, isTalking, username, ...props }: { avatarId: number; color: string; isTalking?: boolean; username: string } & any) {
|
|
||||||
|
// Avatar estilo Mii (Nintendo Wii)
|
||||||
|
function MiiAvatar({ avatarId, username, isTalking, ...props }: { avatarId: number; username: string; isTalking?: boolean } & any) {
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const colorObj = useMemo(() => new THREE.Color(color), [color]);
|
|
||||||
|
// Parâmetros do rosto e cabelo com base no ID selecionado
|
||||||
|
const mii = useMemo(() => {
|
||||||
|
const skin = SKIN_COLORS[(avatarId - 1) % SKIN_COLORS.length];
|
||||||
|
return {
|
||||||
|
skin,
|
||||||
|
hairColor: avatarId === 3 ? '#eab308' : avatarId === 9 ? '#a855f7' : avatarId === 5 ? '#f97316' : '#27272a',
|
||||||
|
hasGlasses: avatarId === 1 || avatarId === 8,
|
||||||
|
glassesColor: avatarId === 8 ? '#18181b' : '#ef4444',
|
||||||
|
hasCap: avatarId === 4,
|
||||||
|
capColor: '#3b82f6',
|
||||||
|
hasHat: avatarId === 10,
|
||||||
|
hatColor: '#b45309',
|
||||||
|
hairStyle: avatarId === 7 ? 'bald' : (avatarId % 3 === 0 ? 'long' : 'short'),
|
||||||
|
beard: avatarId === 7,
|
||||||
|
};
|
||||||
|
}, [avatarId]);
|
||||||
|
|
||||||
useFrame((state) => {
|
useFrame((state) => {
|
||||||
if (groupRef.current) {
|
if (groupRef.current) {
|
||||||
// Pequena oscilação flutuante sutil
|
// Pequena flutuação para dar vida
|
||||||
groupRef.current.position.y = props.position[1] + Math.sin(state.clock.getElapsedTime() * 2 + avatarId) * 0.015;
|
groupRef.current.position.y = props.position[1] + Math.sin(state.clock.getElapsedTime() * 2 + avatarId) * 0.012;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group ref={groupRef} {...props}>
|
<group ref={groupRef} {...props}>
|
||||||
{/* Nome do Participante acima da cabeça */}
|
{/* Balão com Nome acima da cabeça */}
|
||||||
<group position={[0, 0.28, 0]}>
|
<group position={[0, 0.28, 0]}>
|
||||||
{/* Painel do nome */}
|
|
||||||
<mesh position={[0, 0, -0.001]}>
|
<mesh position={[0, 0, -0.001]}>
|
||||||
<planeGeometry args={[0.22, 0.05]} />
|
<planeGeometry args={[0.22, 0.05]} />
|
||||||
<meshBasicMaterial color="#0f172a" transparent opacity={0.8} />
|
<meshBasicMaterial color="#0f172a" transparent opacity={0.7} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<Text fontSize={0.024} color="#ffffff" anchorX="center" anchorY="middle" fontStyle="bold">
|
<Text fontSize={0.024} color="#ffffff" anchorX="center" anchorY="middle" fontStyle="bold">
|
||||||
{username}
|
{username}
|
||||||
@@ -76,142 +94,136 @@ function ThreeAvatar({ avatarId, color, isTalking, username, ...props }: { avata
|
|||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Renderizadores dos avatares baseados em presets geométricos */}
|
{/* CABEÇA MII (Esfera ovalada de pele) */}
|
||||||
{avatarId === 1 && (
|
<mesh position={[0, 0.1, 0]} scale={[1, 1.15, 1]}>
|
||||||
// Cyber Helmet
|
<sphereGeometry args={[0.08, 32, 32]} />
|
||||||
<mesh position={[0, 0.1, 0]}>
|
<meshStandardMaterial color={mii.skin} roughness={0.6} />
|
||||||
<octahedronGeometry args={[0.1]} />
|
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.2} metalness={0.8} />
|
|
||||||
<mesh position={[0, 0.02, 0.06]}>
|
|
||||||
<boxGeometry args={[0.12, 0.025, 0.02]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
|
{/* CABELO */}
|
||||||
|
{mii.hairStyle === 'short' && (
|
||||||
|
<mesh position={[0, 0.15, -0.015]} scale={[1.05, 0.8, 1.05]}>
|
||||||
|
<sphereGeometry args={[0.08, 16, 16]} />
|
||||||
|
<meshStandardMaterial color={mii.hairColor} roughness={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{avatarId === 2 && (
|
{mii.hairStyle === 'long' && (
|
||||||
// Gold Mask
|
<group>
|
||||||
<mesh position={[0, 0.1, 0]} rotation={[0, 0, 0]}>
|
{/* Cabelo Superior */}
|
||||||
<dodecahedronGeometry args={[0.095]} />
|
<mesh position={[0, 0.16, -0.01]} scale={[1.05, 0.7, 1.05]}>
|
||||||
<meshStandardMaterial color="#b45309" roughness={0.1} metalness={0.9} />
|
<sphereGeometry args={[0.08, 16, 16]} />
|
||||||
<mesh position={[0, 0, 0.06]}>
|
<meshStandardMaterial color={mii.hairColor} roughness={0.8} />
|
||||||
<coneGeometry args={[0.03, 0.06, 4]} rotation={[Math.PI / 2, 0, 0]} />
|
|
||||||
<meshStandardMaterial color="#f59e0b" roughness={0.2} metalness={0.8} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
|
{/* Cabelo lateral caindo */}
|
||||||
|
<mesh position={[-0.075, 0.08, -0.02]} scale={[0.4, 1.2, 0.6]}>
|
||||||
|
<sphereGeometry args={[0.04, 16, 16]} />
|
||||||
|
<meshStandardMaterial color={mii.hairColor} roughness={0.8} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
<mesh position={[0.075, 0.08, -0.02]} scale={[0.4, 1.2, 0.6]}>
|
||||||
|
<sphereGeometry args={[0.04, 16, 16]} />
|
||||||
{avatarId === 3 && (
|
<meshStandardMaterial color={mii.hairColor} roughness={0.8} />
|
||||||
// Neon Ring
|
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh>
|
|
||||||
<sphereGeometry args={[0.075, 16, 16]} />
|
|
||||||
<meshStandardMaterial color="#27272a" roughness={0.5} />
|
|
||||||
</mesh>
|
|
||||||
<mesh rotation={[0.4, 0.2, 0.8]}>
|
|
||||||
<torusGeometry args={[0.1, 0.008, 8, 32]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{avatarId === 4 && (
|
{/* BONÉ */}
|
||||||
// Retro Bot
|
{mii.hasCap && (
|
||||||
<group position={[0, 0.1, 0]}>
|
<group position={[0, 0.17, 0.01]}>
|
||||||
<mesh>
|
{/* Copa do Boné */}
|
||||||
<boxGeometry args={[0.14, 0.14, 0.12]} />
|
<mesh scale={[1.05, 0.65, 1.05]}>
|
||||||
<meshStandardMaterial color="#3f3f46" roughness={0.4} />
|
<sphereGeometry args={[0.08, 16, 16]} />
|
||||||
|
<meshStandardMaterial color={mii.capColor} roughness={0.5} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Olhos */}
|
{/* Aba do Boné (virada para trás) */}
|
||||||
<mesh position={[-0.04, 0.02, 0.062]}>
|
<mesh position={[0, 0, -0.08]} rotation={[0.1, 0, 0]}>
|
||||||
|
<boxGeometry args={[0.11, 0.008, 0.08]} />
|
||||||
|
<meshStandardMaterial color={mii.capColor} roughness={0.5} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* CHAPÉU DE PALHA */}
|
||||||
|
{mii.hasHat && (
|
||||||
|
<group position={[0, 0.17, 0]}>
|
||||||
|
{/* Copa do Chapéu */}
|
||||||
|
<mesh>
|
||||||
|
<cylinderGeometry args={[0.055, 0.065, 0.05, 16]} />
|
||||||
|
<meshStandardMaterial color={mii.hatColor} roughness={0.8} />
|
||||||
|
</mesh>
|
||||||
|
{/* Aba do Chapéu */}
|
||||||
|
<mesh position={[0, -0.02, 0]}>
|
||||||
|
<cylinderGeometry args={[0.12, 0.12, 0.006, 16]} />
|
||||||
|
<meshStandardMaterial color={mii.hatColor} roughness={0.8} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* OLHOS */}
|
||||||
|
<mesh position={[-0.028, 0.11, 0.07]} rotation={[0.08, -0.15, 0]}>
|
||||||
|
<sphereGeometry args={[0.01, 8, 8]} />
|
||||||
|
<meshBasicMaterial color="#18181b" />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.028, 0.11, 0.07]} rotation={[0.08, 0.15, 0]}>
|
||||||
|
<sphereGeometry args={[0.01, 8, 8]} />
|
||||||
|
<meshBasicMaterial color="#18181b" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* SOBRANCELHAS */}
|
||||||
|
<mesh position={[-0.028, 0.125, 0.072]} rotation={[0.05, -0.15, 0.08]}>
|
||||||
|
<boxGeometry args={[0.028, 0.005, 0.005]} />
|
||||||
|
<meshBasicMaterial color="#18181b" />
|
||||||
|
</mesh>
|
||||||
|
<mesh position={[0.028, 0.125, 0.072]} rotation={[0.05, 0.15, -0.08]}>
|
||||||
|
<boxGeometry args={[0.028, 0.005, 0.005]} />
|
||||||
|
<meshBasicMaterial color="#18181b" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* NARIZ */}
|
||||||
|
<mesh position={[0, 0.09, 0.076]} scale={[1, 1.2, 1.2]}>
|
||||||
|
<sphereGeometry args={[0.008, 8, 8]} />
|
||||||
|
<meshStandardMaterial color={mii.skin} roughness={0.6} />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* BOCA (Sorriso) */}
|
||||||
|
<mesh position={[0, 0.065, 0.073]} rotation={[0.1, 0, 0]} scale={[1, 0.75, 1]}>
|
||||||
|
<torusGeometry args={[0.015, 0.004, 4, 16, Math.PI]} />
|
||||||
|
<meshBasicMaterial color="#b91c1c" />
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{/* BARBA E BIGODE */}
|
||||||
|
{mii.beard && (
|
||||||
|
<group>
|
||||||
|
{/* Bigode */}
|
||||||
|
<mesh position={[0, 0.075, 0.075]}>
|
||||||
|
<boxGeometry args={[0.04, 0.006, 0.005]} />
|
||||||
|
<meshBasicMaterial color="#713f12" />
|
||||||
|
</mesh>
|
||||||
|
{/* Barbicha */}
|
||||||
|
<mesh position={[0, 0.045, 0.072]} scale={[1.2, 1, 1]}>
|
||||||
<sphereGeometry args={[0.015, 8, 8]} />
|
<sphereGeometry args={[0.015, 8, 8]} />
|
||||||
<meshBasicMaterial color={colorObj} />
|
<meshStandardMaterial color="#713f12" roughness={0.8} />
|
||||||
</mesh>
|
|
||||||
<mesh position={[0.04, 0.02, 0.062]}>
|
|
||||||
<sphereGeometry args={[0.015, 8, 8]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{avatarId === 5 && (
|
{/* ÓCULOS */}
|
||||||
// Crystal Prism
|
{mii.hasGlasses && (
|
||||||
<mesh position={[0, 0.1, 0]}>
|
<group position={[0, 0.11, 0.072]}>
|
||||||
<coneGeometry args={[0.08, 0.18, 5]} />
|
{/* Lente esquerda */}
|
||||||
<meshStandardMaterial color="#6b21a8" roughness={0.1} metalness={0.7} transparent opacity={0.85} />
|
<mesh position={[-0.028, 0, 0.002]}>
|
||||||
<mesh position={[0, -0.02, 0.05]} scale={[1, 0.2, 1]}>
|
<ringGeometry args={[0.016, 0.019, 16]} />
|
||||||
<sphereGeometry args={[0.03]} />
|
<meshBasicMaterial color={mii.glassesColor} />
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
|
{/* Lente direita */}
|
||||||
|
<mesh position={[0.028, 0, 0.002]}>
|
||||||
|
<ringGeometry args={[0.016, 0.019, 16]} />
|
||||||
|
<meshBasicMaterial color={mii.glassesColor} />
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
{/* Ponte */}
|
||||||
|
<mesh position={[0, 0, 0.002]}>
|
||||||
{avatarId === 6 && (
|
<boxGeometry args={[0.028, 0.004, 0.002]} />
|
||||||
// Vector Visor
|
<meshBasicMaterial color={mii.glassesColor} />
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh>
|
|
||||||
<cylinderGeometry args={[0.065, 0.08, 0.14, 6]} />
|
|
||||||
<meshStandardMaterial color="#0f172a" roughness={0.2} metalness={0.9} />
|
|
||||||
</mesh>
|
|
||||||
<mesh position={[0, 0.03, 0.05]} rotation={[0.2, 0, 0]}>
|
|
||||||
<boxGeometry args={[0.08, 0.02, 0.04]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{avatarId === 7 && (
|
|
||||||
// Quantum Core
|
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh>
|
|
||||||
<sphereGeometry args={[0.045, 16, 16]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
|
||||||
<mesh rotation={[0.5, 0, 0]}>
|
|
||||||
<torusGeometry args={[0.09, 0.005, 8, 24]} />
|
|
||||||
<meshBasicMaterial color="#ffffff" transparent opacity={0.8} />
|
|
||||||
</mesh>
|
|
||||||
<mesh rotation={[0, 0.5, 1.2]}>
|
|
||||||
<torusGeometry args={[0.075, 0.005, 8, 24]} />
|
|
||||||
<meshBasicMaterial color={colorObj} transparent opacity={0.6} />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{avatarId === 8 && (
|
|
||||||
// Obsidian Shell
|
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh>
|
|
||||||
<icosahedronGeometry args={[0.09, 1]} />
|
|
||||||
<meshStandardMaterial color="#18181b" roughness={0.1} metalness={0.9} flatShading />
|
|
||||||
</mesh>
|
|
||||||
<mesh scale={[0.5, 0.5, 0.5]}>
|
|
||||||
<sphereGeometry args={[0.08]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{avatarId === 9 && (
|
|
||||||
// Synth Head
|
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh>
|
|
||||||
<sphereGeometry args={[0.08, 8, 8]} />
|
|
||||||
<meshStandardMaterial color="#09090b" roughness={0.5} flatShading />
|
|
||||||
</mesh>
|
|
||||||
<mesh position={[0, 0.015, 0.055]}>
|
|
||||||
<boxGeometry args={[0.13, 0.02, 0.01]} />
|
|
||||||
<meshBasicMaterial color={colorObj} />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{avatarId === 10 && (
|
|
||||||
// Cosmic Helix
|
|
||||||
<group position={[0, 0.1, 0]}>
|
|
||||||
<mesh rotation={[0, 0, Date.now() * 0.002]}>
|
|
||||||
<torusKnotGeometry args={[0.065, 0.015, 32, 4]} />
|
|
||||||
<meshStandardMaterial color={colorObj} roughness={0.2} metalness={0.7} />
|
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
)}
|
)}
|
||||||
@@ -219,7 +231,47 @@ function ThreeAvatar({ avatarId, color, isTalking, username, ...props }: { avata
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mesa, Cadeiras e Detalhes da Sala de Reunião
|
// Carregador e Escalador Inteligente do Modelo Real (Adaptado para 1 Metro de tamanho)
|
||||||
|
function HologramModel({ model }: { model: { url: string } }) {
|
||||||
|
const { scene } = useGLTF(model.url);
|
||||||
|
const clone = useMemo(() => scene.clone(), [scene]);
|
||||||
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!groupRef.current) return;
|
||||||
|
|
||||||
|
// Medir limites tridimensionais da maquete importada
|
||||||
|
const box = new THREE.Box3().setFromObject(clone);
|
||||||
|
const size = new THREE.Vector3();
|
||||||
|
box.getSize(size);
|
||||||
|
|
||||||
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||||||
|
|
||||||
|
if (maxDim > 0) {
|
||||||
|
// Ajusta a escala de forma inteligente para ter aproximadamente 1 metro (ex: 0.95m)
|
||||||
|
const targetSize = 0.95;
|
||||||
|
const scaleFactor = targetSize / maxDim;
|
||||||
|
|
||||||
|
groupRef.current.scale.setScalar(scaleFactor);
|
||||||
|
|
||||||
|
// Centralizar o pivot em (0,0,0)
|
||||||
|
const center = new THREE.Vector3();
|
||||||
|
box.getCenter(center);
|
||||||
|
|
||||||
|
// Centraliza e levanta ligeiramente acima da mesa
|
||||||
|
clone.position.copy(center).multiplyScalar(-scaleFactor);
|
||||||
|
clone.position.y += 0.02;
|
||||||
|
}
|
||||||
|
}, [clone]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef}>
|
||||||
|
<primitive object={clone} />
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cenário Claro, Limpo e Iluminado da Sala de Reunião
|
||||||
function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any }) {
|
function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any }) {
|
||||||
const tableGeom = useMemo(() => {
|
const tableGeom = useMemo(() => {
|
||||||
// Geometria da mesa em U
|
// Geometria da mesa em U
|
||||||
@@ -244,46 +296,46 @@ function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{/* Piso Industrial */}
|
{/* Piso de Laca Clara (Sala muito mais clara e iluminada!) */}
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>
|
||||||
<planeGeometry args={[12, 8]} />
|
<planeGeometry args={[12, 8]} />
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.85} metalness={0.1} />
|
<meshStandardMaterial color="#e2e8f0" roughness={0.4} metalness={0.1} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Tapete Central */}
|
{/* Tapete Central */}
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.001, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.001, 0]}>
|
||||||
<planeGeometry args={[4.8, 3.2]} />
|
<planeGeometry args={[4.8, 3.2]} />
|
||||||
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
<meshStandardMaterial color="#94a3b8" roughness={0.7} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Paredes e Painéis de Fundo */}
|
{/* Paredes Claras */}
|
||||||
<mesh position={[0, 2, -4]}>
|
<mesh position={[0, 2, -4]}>
|
||||||
<planeGeometry args={[12, 4]} />
|
<planeGeometry args={[12, 4]} />
|
||||||
<meshStandardMaterial color="#0f172a" roughness={0.9} />
|
<meshStandardMaterial color="#f1f5f9" roughness={0.9} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Janelas panorâmicas com vista simulada (gradiente brilhante nas laterais) */}
|
{/* Janelas Panorâmicas Ensolaradas */}
|
||||||
<mesh position={[-6, 2, 0]} rotation={[0, Math.PI / 2, 0]}>
|
<mesh position={[-6, 2, 0]} rotation={[0, Math.PI / 2, 0]}>
|
||||||
<planeGeometry args={[8, 4]} />
|
<planeGeometry args={[8, 4]} />
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.3} emmissive="#0284c7" />
|
<meshStandardMaterial color="#bae6fd" roughness={0.1} emissive="#bae6fd" emissiveIntensity={0.4} />
|
||||||
</mesh>
|
</mesh>
|
||||||
<mesh position={[6, 2, 0]} rotation={[0, -Math.PI / 2, 0]}>
|
<mesh position={[6, 2, 0]} rotation={[0, -Math.PI / 2, 0]}>
|
||||||
<planeGeometry args={[8, 4]} />
|
<planeGeometry args={[8, 4]} />
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.3} emmissive="#0284c7" />
|
<meshStandardMaterial color="#bae6fd" roughness={0.1} emissive="#bae6fd" emissiveIntensity={0.4} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Mesa Elíptica em U */}
|
{/* Mesa Elíptica em U (Madeira Mel/Carvalho Clara e Acolhedora) */}
|
||||||
<group position={[0, 0.8, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
<group position={[0, 0.8, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<mesh geometry={tableGeom}>
|
<mesh geometry={tableGeom}>
|
||||||
<meshStandardMaterial color="#451a03" roughness={0.1} metalness={0.15} />
|
<meshStandardMaterial color="#d97706" roughness={0.2} metalness={0.1} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
{/* Pés da mesa (suporte) */}
|
{/* Pés da mesa (Cromados) */}
|
||||||
{[-1.3, 1.3].map((x) =>
|
{[-1.3, 1.3].map((x) =>
|
||||||
[-0.7, 0.7].map((z) => (
|
[-0.7, 0.7].map((z) => (
|
||||||
<mesh key={`${x}-${z}`} position={[x, 0.4, z]}>
|
<mesh key={`${x}-${z}`} position={[x, 0.4, z]}>
|
||||||
<cylinderGeometry args={[0.04, 0.04, 0.8]} />
|
<cylinderGeometry args={[0.035, 0.035, 0.8]} />
|
||||||
<meshStandardMaterial color="#1e293b" metalness={0.8} roughness={0.2} />
|
<meshStandardMaterial color="#d4d4d8" metalness={0.9} roughness={0.1} />
|
||||||
</mesh>
|
</mesh>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -292,28 +344,32 @@ function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any
|
|||||||
<group position={[0, 0.78, 0]}>
|
<group position={[0, 0.78, 0]}>
|
||||||
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
<cylinderGeometry args={[0.26, 0.28, 0.05, 32]} />
|
<cylinderGeometry args={[0.26, 0.28, 0.05, 32]} />
|
||||||
<meshStandardMaterial color="#0f172a" metalness={0.9} roughness={0.1} />
|
<meshStandardMaterial color="#1e293b" metalness={0.8} roughness={0.2} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Placa metálica brilhante */}
|
{/* Anel de luz azul brilhante */}
|
||||||
<mesh position={[0, 0.026, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh position={[0, 0.026, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
<ringGeometry args={[0, 0.22, 32]} />
|
<ringGeometry args={[0, 0.22, 32]} />
|
||||||
<meshBasicMaterial color="#0ea5e9" transparent opacity={0.6} />
|
<meshBasicMaterial color="#0ea5e9" transparent opacity={0.7} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Feixe de Luz Holográfico */}
|
{/* Feixe de Luz Holográfico */}
|
||||||
<mesh position={[0, 0.45, 0]}>
|
<mesh position={[0, 0.45, 0]}>
|
||||||
<cylinderGeometry args={[0.28, 0.2, 0.9, 32, 1, true]} />
|
<cylinderGeometry args={[0.28, 0.2, 0.9, 32, 1, true]} />
|
||||||
<meshBasicMaterial color="#0ea5e9" transparent opacity={0.12} side={THREE.DoubleSide} />
|
<meshBasicMaterial color="#0ea5e9" transparent opacity={0.15} side={THREE.DoubleSide} />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
{/* Modelo 3D da Maquete Holográfica (Representação/Pivot) */}
|
{/* Modelo 3D da Maquete Holográfica */}
|
||||||
<group position={[0, 0.45, 0]} rotation={[0, Date.now() * 0.00015, 0]}>
|
<group position={[0, 0.45, 0]} rotation={[0, Date.now() * 0.00012, 0]}>
|
||||||
{currentActiveModel ? (
|
<Suspense fallback={
|
||||||
|
// Carregador holográfico temporário
|
||||||
<mesh>
|
<mesh>
|
||||||
<dodecahedronGeometry args={[0.25]} />
|
<sphereGeometry args={[0.1, 16, 16]} />
|
||||||
<meshStandardMaterial color="#00f5ff" roughness={0.1} metalness={0.8} transparent opacity={0.8} emissive="#0ea5e9" emissiveIntensity={0.6} wireframe />
|
<meshBasicMaterial color="#00f5ff" wireframe />
|
||||||
</mesh>
|
</mesh>
|
||||||
|
}>
|
||||||
|
{currentActiveModel ? (
|
||||||
|
<HologramModel model={currentActiveModel} />
|
||||||
) : (
|
) : (
|
||||||
// Holograma Padrão (Estrutura geométrica representativa de maquete)
|
// Holograma Padrão Geométrico se não houver maquete carregada
|
||||||
<group>
|
<group>
|
||||||
<mesh position={[0, 0, 0]}>
|
<mesh position={[0, 0, 0]}>
|
||||||
<octahedronGeometry args={[0.22]} />
|
<octahedronGeometry args={[0.22]} />
|
||||||
@@ -325,6 +381,7 @@ function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any
|
|||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
)}
|
)}
|
||||||
|
</Suspense>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
@@ -334,31 +391,31 @@ function ThreeMeetingRoomScene({ currentActiveModel }: { currentActiveModel: any
|
|||||||
{/* Base giratória metálica */}
|
{/* Base giratória metálica */}
|
||||||
<mesh position={[0, 0.25, 0]}>
|
<mesh position={[0, 0.25, 0]}>
|
||||||
<cylinderGeometry args={[0.015, 0.015, 0.5]} />
|
<cylinderGeometry args={[0.015, 0.015, 0.5]} />
|
||||||
<meshStandardMaterial color="#71717a" metalness={0.9} roughness={0.1} />
|
<meshStandardMaterial color="#a1a1aa" metalness={0.9} roughness={0.1} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Pés base */}
|
{/* Pés base */}
|
||||||
<mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
<mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}>
|
||||||
<ringGeometry args={[0, 0.22, 4]} />
|
<ringGeometry args={[0, 0.22, 4]} />
|
||||||
<meshStandardMaterial color="#3f3f46" metalness={0.85} roughness={0.2} />
|
<meshStandardMaterial color="#71717a" metalness={0.85} roughness={0.2} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Assento */}
|
{/* Assento */}
|
||||||
<mesh position={[0, 0.52, 0]}>
|
<mesh position={[0, 0.52, 0]}>
|
||||||
<boxGeometry args={[0.28, 0.05, 0.26]} />
|
<boxGeometry args={[0.28, 0.05, 0.26]} />
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.7} />
|
<meshStandardMaterial color="#334155" roughness={0.7} />
|
||||||
</mesh>
|
</mesh>
|
||||||
{/* Encosto */}
|
{/* Encosto */}
|
||||||
<mesh position={[0, 0.78, -0.11]} rotation={[0.08, 0, 0]}>
|
<mesh position={[0, 0.78, -0.11]} rotation={[0.08, 0, 0]}>
|
||||||
<boxGeometry args={[0.26, 0.44, 0.04]} />
|
<boxGeometry args={[0.26, 0.44, 0.04]} />
|
||||||
<meshStandardMaterial color="#1e293b" roughness={0.7} />
|
<meshStandardMaterial color="#334155" roughness={0.7} />
|
||||||
</mesh>
|
</mesh>
|
||||||
</group>
|
</group>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Iluminação da Sala */}
|
{/* Iluminação Super Clara e Acolhedora da Sala */}
|
||||||
<ambientLight intensity={0.4} />
|
<hemisphereLight skyColor="#ffffff" groundColor="#64748b" intensity={1.4} />
|
||||||
<directionalLight position={[5, 8, 5]} intensity={1.0} castShadow />
|
<directionalLight position={[6, 12, 6]} intensity={1.8} castShadow />
|
||||||
<directionalLight position={[-5, 5, -5]} intensity={0.3} />
|
<directionalLight position={[-6, 8, -6]} intensity={0.5} />
|
||||||
<pointLight position={[0, 3.5, 0]} intensity={1.5} color="#0ea5e9" distance={6} />
|
<pointLight position={[0, 3.5, 0]} intensity={2.0} color="#0ea5e9" distance={8} />
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -543,22 +600,22 @@ export default function MeetingRoom() {
|
|||||||
Lobby de <span className="text-primary">Reunião Virtual</span>
|
Lobby de <span className="text-primary">Reunião Virtual</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1.5 text-xs font-mono uppercase tracking-wider text-muted-foreground">
|
<p className="mt-1.5 text-xs font-mono uppercase tracking-wider text-muted-foreground">
|
||||||
Prepare seu avatar e escolha seu assento
|
Escolha seu visual Mii e selecione seu assento
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-8 md:grid-cols-12">
|
<div className="grid grid-cols-1 gap-8 md:grid-cols-12">
|
||||||
{/* Seletor de Avatar (Lado Esquerdo - 5 colunas) */}
|
{/* Seletor de Avatar (Lado Esquerdo - 5 colunas) */}
|
||||||
<div className="flex flex-col items-center justify-center rounded-xl border bg-muted/30 p-6 md:col-span-5 relative">
|
<div className="flex flex-col items-center justify-center rounded-xl border bg-muted/30 p-6 md:col-span-5 relative">
|
||||||
<span className="absolute top-3 left-4 font-mono text-[9px] uppercase tracking-widest text-muted-foreground">Escolha o Avatar</span>
|
<span className="absolute top-3 left-4 font-mono text-[9px] uppercase tracking-widest text-muted-foreground">Modelo de Avatar</span>
|
||||||
|
|
||||||
{/* Modelo Virtual do Avatar no Centro */}
|
{/* Modelo Virtual do Mii no Centro */}
|
||||||
<div className="h-44 w-full flex items-center justify-center relative bg-slate-950/40 rounded-lg overflow-hidden border border-border">
|
<div className="h-44 w-full flex items-center justify-center relative bg-slate-900/60 rounded-lg overflow-hidden border border-border">
|
||||||
<Canvas camera={{ position: [0, 0.15, 0.35], fov: 50 }}>
|
<Canvas camera={{ position: [0, 0.16, 0.28], fov: 40 }}>
|
||||||
<ambientLight intensity={0.5} />
|
<ambientLight intensity={0.8} />
|
||||||
<directionalLight position={[2, 3, 2]} intensity={1.5} />
|
<directionalLight position={[2, 3, 2]} intensity={1.5} />
|
||||||
<pointLight position={[0, 0.15, 0.2]} intensity={1} color={currentAvatar.color} />
|
<pointLight position={[0, 0.15, 0.2]} intensity={1} color={currentAvatar.color} />
|
||||||
<ThreeAvatar avatarId={selectedAvatar} color={currentAvatar.color} username={name || 'Avatar'} position={[0, 0, 0]} isTalking={true} />
|
<MiiAvatar avatarId={selectedAvatar} username={name || 'Mii'} position={[0, 0, 0]} isTalking={true} />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
{/* Navegadores de carrossel de avatar */}
|
{/* Navegadores de carrossel de avatar */}
|
||||||
@@ -572,7 +629,7 @@ export default function MeetingRoom() {
|
|||||||
|
|
||||||
{/* Informações do avatar */}
|
{/* Informações do avatar */}
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<span className="font-mono text-xs font-bold uppercase tracking-wider" style={{ color: currentAvatar.color }}>
|
<span className="font-mono text-xs font-bold uppercase tracking-wider text-foreground">
|
||||||
{currentAvatar.name}
|
{currentAvatar.name}
|
||||||
</span>
|
</span>
|
||||||
<p className="text-[10px] text-muted-foreground mt-0.5">{currentAvatar.desc}</p>
|
<p className="text-[10px] text-muted-foreground mt-0.5">{currentAvatar.desc}</p>
|
||||||
@@ -668,10 +725,10 @@ export default function MeetingRoom() {
|
|||||||
// --- Renderização da Sala de Reunião Ativa ---
|
// --- Renderização da Sala de Reunião Ativa ---
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen overflow-hidden bg-slate-950 text-white relative">
|
<div className="flex h-screen overflow-hidden bg-slate-950 text-white relative animate-in fade-in duration-300">
|
||||||
{/* 3D Canvas - Sala Virtual */}
|
{/* 3D Canvas - Sala Virtual Clara */}
|
||||||
<div className="flex-1 h-full relative">
|
<div className="flex-1 h-full relative">
|
||||||
<Canvas camera={{ position: [0, 2.5, 2.8], fov: 45 }} shadows>
|
<Canvas camera={{ position: [0, 2.5, 2.8], fof: 45 }} shadows>
|
||||||
<ThreeMeetingRoomScene currentActiveModel={activeModel} />
|
<ThreeMeetingRoomScene currentActiveModel={activeModel} />
|
||||||
|
|
||||||
{/* Renderizar avatares dos outros participantes na sala */}
|
{/* Renderizar avatares dos outros participantes na sala */}
|
||||||
@@ -683,16 +740,13 @@ export default function MeetingRoom() {
|
|||||||
// Não renderiza a nós mesmos na cena como avatar, nossa câmera já está na cadeira!
|
// Não renderiza a nós mesmos na cena como avatar, nossa câmera já está na cadeira!
|
||||||
if (peer.chairId === selectedChair) return null;
|
if (peer.chairId === selectedChair) return null;
|
||||||
|
|
||||||
const avatarPreset = AVATAR_PRESETS[peer.avatarId - 1] || AVATAR_PRESETS[0];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThreeAvatar
|
<MiiAvatar
|
||||||
key={peer.presence_ref}
|
key={peer.presence_ref}
|
||||||
avatarId={peer.avatarId}
|
avatarId={peer.avatarId}
|
||||||
color={avatarPreset.color}
|
|
||||||
username={peer.username}
|
username={peer.username}
|
||||||
position={chairConf.pos}
|
position={chairConf.pos}
|
||||||
isTalking={isTalking && Math.random() > 0.5} // Simulação simples de fala para os peers
|
isTalking={isTalking && Math.random() > 0.5} // Simulação de fala
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -715,7 +769,7 @@ export default function MeetingRoom() {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="font-mono text-[9px] text-muted-foreground uppercase">Reunião Conectada</span>
|
<span className="font-mono text-[9px] text-muted-foreground uppercase">Reunião Conectada</span>
|
||||||
<span className="font-mono text-xs font-semibold text-foreground">
|
<span className="font-mono text-xs font-semibold text-foreground">
|
||||||
Cadeiras Ocupadas: {activeUsersCount}/8
|
Membros Online: {activeUsersCount}/8
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -759,9 +813,9 @@ export default function MeetingRoom() {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="pointer-events-auto rounded-xl border bg-slate-900/90 backdrop-blur-md px-4 py-2 flex flex-col justify-center shadow-2xl">
|
<div className="pointer-events-auto rounded-xl border bg-slate-900/90 backdrop-blur-md px-4 py-2 flex flex-col justify-center shadow-2xl">
|
||||||
<span className="font-mono text-[8px] text-muted-foreground uppercase">Dica Imersiva</span>
|
<span className="font-mono text-[8px] text-muted-foreground uppercase">Dica de Visão</span>
|
||||||
<span className="text-[10px] text-foreground font-mono">
|
<span className="text-[10px] text-foreground font-mono">
|
||||||
Clique e arraste com o botão esquerdo do mouse para girar a maquete e a sala
|
Clique e arraste com o botão esquerdo para girar a mesa e olhar os avatares
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -773,9 +827,9 @@ export default function MeetingRoom() {
|
|||||||
<div className="border-b border-slate-800 p-4 flex items-center justify-between">
|
<div className="border-b border-slate-800 p-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<MessageSquare className="h-4.5 w-4.5 text-primary" />
|
<MessageSquare className="h-4.5 w-4.5 text-primary" />
|
||||||
<span className="font-mono text-xs font-bold uppercase tracking-wider">Chat & Notas</span>
|
<span className="font-mono text-xs font-bold uppercase tracking-wider">Chat da Reunião</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="font-mono text-[10px] bg-primary/20 text-primary px-1.5 py-0.5 rounded">Realtime</span>
|
<span className="font-mono text-[10px] bg-primary/20 text-primary px-1.5 py-0.5 rounded">Tempo Real</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Histórico do Chat */}
|
{/* Histórico do Chat */}
|
||||||
|
|||||||
Reference in New Issue
Block a user