🚀 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 ---
const CHAIRS = [
{ id: '1', name: 'Assento 1', pos: [-1.2, 0.95, -1.45], lookAt: [0, 0.95, 0] },
{ id: '2', name: 'Assento 2', pos: [-0.4, 0.95, -1.45], lookAt: [0, 0.95, 0] },
{ id: '3', name: 'Assento 3', pos: [0.4, 0.95, -1.45], lookAt: [0, 0.95, 0] },
{ id: '4', name: 'Assento 4', pos: [1.2, 0.95, -1.45], lookAt: [0, 0.95, 0] },
{ id: '5', name: 'Assento 5', pos: [1.2, 0.95, 1.45], lookAt: [0, 0.95, 0] },
{ id: '6', name: 'Assento 6', pos: [0.4, 0.95, 1.45], lookAt: [0, 0.95, 0] },
{ id: '7', name: 'Assento 7', pos: [-0.4, 0.95, 1.45], lookAt: [0, 0.95, 0] },
{ id: '8', name: 'Assento 8', 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], rot: [0, 0, 0], 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], rot: [0, 0, 0], 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], rot: [0, Math.PI, 0], 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], rot: [0, Math.PI, 0], lookAt: [0, 0.95, 0] },
];
const AVATAR_PRESETS: AvatarPreset[] = [
@@ -123,8 +123,33 @@ const getAvatarBgClass = (avatarId: number): string => {
// --- Subcomponentes 3D ---
// 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 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 skin = SKIN_COLORS[(avatarId - 1) % SKIN_COLORS.length];
@@ -143,9 +168,38 @@ function MiiAvatar({ avatarId, username, isTalking, scale = 1, ...props }: { ava
}, [avatarId]);
useFrame((state) => {
// Animação de flutuação original
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;
}
// 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(() => {
@@ -175,11 +229,15 @@ function MiiAvatar({ avatarId, username, isTalking, scale = 1, ...props }: { ava
</Text>
</group>
{/* Indicador de Fala (anel pulsante) */}
{isTalking && (
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.12, 0]}>
<ringGeometry args={[0.13, 0.15, 32]} />
<meshBasicMaterial color="#3b82f6" transparent opacity={0.6 + Math.sin(Date.now() * 0.01) * 0.3} side={THREE.DoubleSide} />
{/* Indicador de Fala & Chat (anel abaixo da cabeça e 50% menor) */}
{(isTalking || isLastChatSender) && (
<mesh
ref={ringRef}
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>
)}
@@ -735,6 +793,7 @@ export default function MeetingRoom() {
const [peers, setPeers] = useState<Record<string, PeerData>>({});
const [chatMessages, setChatMessages] = useState<{ id: string; sender: string; text: string; time: string }[]>([]);
const [newMessage, setNewMessage] = useState('');
const [lastChatSender, setLastChatSender] = useState<string | null>(null);
const [isMuted, setIsMuted] = useState(false);
const [isTalking, setIsTalking] = useState(false);
const [activeUsersCount, setActiveUsersCount] = useState(1);
@@ -972,6 +1031,7 @@ export default function MeetingRoom() {
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)
@@ -1045,14 +1105,25 @@ export default function MeetingRoom() {
// Enviar Mensagem de Chat
const handleSendMessage = () => {
if (!newMessage.trim() || !channelRef.current) return;
const senderName = name.substring(0, 10);
channelRef.current.send({
type: 'broadcast',
event: 'chat_msg',
payload: {
sender: name.substring(0, 10),
sender: senderName,
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('');
};
@@ -1295,7 +1366,9 @@ export default function MeetingRoom() {
avatarId={peer.avatarId}
username={isLocalUser ? `${peer.username} (Você)` : peer.username}
position={chairConf.pos}
rotation={chairConf.rot}
isTalking={isLocalUser ? isTalking : (isTalking && Math.random() > 0.5)}
isLastChatSender={peer.username === lastChatSender}
scale={1.2}
/>
);