🚀 Auto-deploy: melhoria no snap e medição AR em 25/05/2026 11:35:38

This commit is contained in:
2026-05-25 11:35:38 +00:00
parent 00854f0255
commit b949a7c227
+88 -15
View File
@@ -78,14 +78,14 @@ interface PeerData {
// --- Configurações da Mesa de Reunião --- // --- Configurações da Mesa de Reunião ---
const CHAIRS = [ const CHAIRS = [
{ id: '1', name: 'Assento 1', pos: [-1.2, 0.95, -1.45], lookAt: [0, 0.95, 0] }, { id: '1', name: 'Assento 1', pos: [-1.2, 0.95, -1.45], rot: [0, 0, 0], lookAt: [0, 0.95, 0] },
{ id: '2', name: 'Assento 2', pos: [-0.4, 0.95, -1.45], lookAt: [0, 0.95, 0] }, { id: '2', name: 'Assento 2', pos: [-0.4, 0.95, -1.45], rot: [0, 0, 0], lookAt: [0, 0.95, 0] },
{ id: '3', name: 'Assento 3', pos: [0.4, 0.95, -1.45], lookAt: [0, 0.95, 0] }, { id: '3', name: 'Assento 3', pos: [0.4, 0.95, -1.45], rot: [0, 0, 0], lookAt: [0, 0.95, 0] },
{ id: '4', name: 'Assento 4', pos: [1.2, 0.95, -1.45], lookAt: [0, 0.95, 0] }, { id: '4', name: 'Assento 4', pos: [1.2, 0.95, -1.45], rot: [0, 0, 0], lookAt: [0, 0.95, 0] },
{ id: '5', name: 'Assento 5', pos: [1.2, 0.95, 1.45], lookAt: [0, 0.95, 0] }, { id: '5', name: 'Assento 5', pos: [1.2, 0.95, 1.45], rot: [0, Math.PI, 0], lookAt: [0, 0.95, 0] },
{ id: '6', name: 'Assento 6', pos: [0.4, 0.95, 1.45], lookAt: [0, 0.95, 0] }, { id: '6', name: 'Assento 6', pos: [0.4, 0.95, 1.45], rot: [0, Math.PI, 0], lookAt: [0, 0.95, 0] },
{ id: '7', name: 'Assento 7', pos: [-0.4, 0.95, 1.45], lookAt: [0, 0.95, 0] }, { id: '7', name: 'Assento 7', pos: [-0.4, 0.95, 1.45], rot: [0, Math.PI, 0], lookAt: [0, 0.95, 0] },
{ id: '8', name: 'Assento 8', pos: [-1.2, 0.95, 1.45], lookAt: [0, 0.95, 0] }, { id: '8', name: 'Assento 8', pos: [-1.2, 0.95, 1.45], rot: [0, Math.PI, 0], lookAt: [0, 0.95, 0] },
]; ];
const AVATAR_PRESETS: AvatarPreset[] = [ const AVATAR_PRESETS: AvatarPreset[] = [
@@ -123,8 +123,33 @@ const getAvatarBgClass = (avatarId: number): string => {
// --- Subcomponentes 3D --- // --- Subcomponentes 3D ---
// Avatar estilo Mii (Nintendo Wii) - Versão Melhorada (Mais Feliz e Expressivo) // Avatar estilo Mii (Nintendo Wii) - Versão Melhorada (Mais Feliz e Expressivo)
function MiiAvatar({ avatarId, username, isTalking, scale = 1, ...props }: { avatarId: number; username: string; isTalking?: boolean; scale?: number } & Record<string, unknown>) { function MiiAvatar({
avatarId,
username,
isTalking,
scale = 1,
isLastChatSender = false,
...props
}: {
avatarId: number;
username: string;
isTalking?: boolean;
scale?: number;
isLastChatSender?: boolean;
} & Record<string, unknown>) {
const groupRef = useRef<THREE.Group>(null); const groupRef = useRef<THREE.Group>(null);
const ringRef = useRef<THREE.Mesh>(null);
const lastSenderRef = useRef(isLastChatSender);
const pulseEndTimeRef = useRef(0);
// Monitora quando este avatar se torna o último a escrever no chat
useEffect(() => {
if (isLastChatSender && !lastSenderRef.current) {
// Pulso rápido de 1.5 segundos (pisca 3 vezes rápido)
pulseEndTimeRef.current = Date.now() + 1500;
}
lastSenderRef.current = isLastChatSender;
}, [isLastChatSender]);
const mii = useMemo(() => { const mii = useMemo(() => {
const skin = SKIN_COLORS[(avatarId - 1) % SKIN_COLORS.length]; const skin = SKIN_COLORS[(avatarId - 1) % SKIN_COLORS.length];
@@ -143,9 +168,38 @@ function MiiAvatar({ avatarId, username, isTalking, scale = 1, ...props }: { ava
}, [avatarId]); }, [avatarId]);
useFrame((state) => { useFrame((state) => {
// Animação de flutuação original
if (groupRef.current && props.position && Array.isArray(props.position) && props.position.length >= 2) { if (groupRef.current && props.position && Array.isArray(props.position) && props.position.length >= 2) {
groupRef.current.position.y = (props.position[1] as number) + Math.sin(state.clock.getElapsedTime() * 2 + avatarId) * 0.012; groupRef.current.position.y = (props.position[1] as number) + Math.sin(state.clock.getElapsedTime() * 2 + avatarId) * 0.012;
} }
// Animação avançada do anel de fala / chat
if (ringRef.current) {
const now = Date.now();
const material = ringRef.current.material as THREE.MeshBasicMaterial;
if (isTalking) {
// Modo Microfone: Azul
material.color.set('#3b82f6');
material.opacity = 0.6 + Math.sin(state.clock.getElapsedTime() * 10) * 0.3;
ringRef.current.scale.set(1, 1, 1);
} else if (isLastChatSender) {
// Modo Chat: Amarelo
material.color.set('#eab308');
const isPulsing = now < pulseEndTimeRef.current;
if (isPulsing) {
// Pulsação rápida inicial
material.opacity = 0.5 + Math.sin(state.clock.getElapsedTime() * 25) * 0.4;
const s = 1.0 + Math.sin(state.clock.getElapsedTime() * 25) * 0.15;
ringRef.current.scale.set(s, s, 1);
} else {
// Amarelo constante pós-pulsação
material.opacity = 0.75;
ringRef.current.scale.set(1, 1, 1);
}
}
}
}); });
const positionVector = useMemo(() => { const positionVector = useMemo(() => {
@@ -175,11 +229,15 @@ function MiiAvatar({ avatarId, username, isTalking, scale = 1, ...props }: { ava
</Text> </Text>
</group> </group>
{/* Indicador de Fala (anel pulsante) */} {/* Indicador de Fala & Chat (anel abaixo da cabeça e 50% menor) */}
{isTalking && ( {(isTalking || isLastChatSender) && (
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.12, 0]}> <mesh
<ringGeometry args={[0.13, 0.15, 32]} /> ref={ringRef}
<meshBasicMaterial color="#3b82f6" transparent opacity={0.6 + Math.sin(Date.now() * 0.01) * 0.3} side={THREE.DoubleSide} /> rotation={[-Math.PI / 2, 0, 0]}
position={[0, -0.01, 0]}
>
<ringGeometry args={[0.065, 0.075, 32]} />
<meshBasicMaterial color="#3b82f6" transparent opacity={0.7} side={THREE.DoubleSide} />
</mesh> </mesh>
)} )}
@@ -735,6 +793,7 @@ export default function MeetingRoom() {
const [peers, setPeers] = useState<Record<string, PeerData>>({}); const [peers, setPeers] = useState<Record<string, PeerData>>({});
const [chatMessages, setChatMessages] = useState<{ id: string; sender: string; text: string; time: string }[]>([]); const [chatMessages, setChatMessages] = useState<{ id: string; sender: string; text: string; time: string }[]>([]);
const [newMessage, setNewMessage] = useState(''); const [newMessage, setNewMessage] = useState('');
const [lastChatSender, setLastChatSender] = useState<string | null>(null);
const [isMuted, setIsMuted] = useState(false); const [isMuted, setIsMuted] = useState(false);
const [isTalking, setIsTalking] = useState(false); const [isTalking, setIsTalking] = useState(false);
const [activeUsersCount, setActiveUsersCount] = useState(1); const [activeUsersCount, setActiveUsersCount] = useState(1);
@@ -972,6 +1031,7 @@ export default function MeetingRoom() {
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
}, },
]); ]);
setLastChatSender(payload.payload.sender);
}); });
// Escuta atualizações de Apresentação da Maquete via Broadcast (apenas para convidados) // Escuta atualizações de Apresentação da Maquete via Broadcast (apenas para convidados)
@@ -1045,14 +1105,25 @@ export default function MeetingRoom() {
// Enviar Mensagem de Chat // Enviar Mensagem de Chat
const handleSendMessage = () => { const handleSendMessage = () => {
if (!newMessage.trim() || !channelRef.current) return; if (!newMessage.trim() || !channelRef.current) return;
const senderName = name.substring(0, 10);
channelRef.current.send({ channelRef.current.send({
type: 'broadcast', type: 'broadcast',
event: 'chat_msg', event: 'chat_msg',
payload: { payload: {
sender: name.substring(0, 10), sender: senderName,
text: newMessage.trim(), text: newMessage.trim(),
}, },
}); });
setChatMessages((prev) => [
...prev,
{
id: Math.random().toString(),
sender: senderName,
text: newMessage.trim(),
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
},
]);
setLastChatSender(senderName);
setNewMessage(''); setNewMessage('');
}; };
@@ -1295,7 +1366,9 @@ export default function MeetingRoom() {
avatarId={peer.avatarId} avatarId={peer.avatarId}
username={isLocalUser ? `${peer.username} (Você)` : peer.username} username={isLocalUser ? `${peer.username} (Você)` : peer.username}
position={chairConf.pos} position={chairConf.pos}
rotation={chairConf.rot}
isTalking={isLocalUser ? isTalking : (isTalking && Math.random() > 0.5)} isTalking={isLocalUser ? isTalking : (isTalking && Math.random() > 0.5)}
isLastChatSender={peer.username === lastChatSender}
scale={1.2} scale={1.2}
/> />
); );