🚀 Auto-deploy: melhoria no snap e medição AR em 25/05/2026 11:35:38
This commit is contained in:
+88
-15
@@ -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}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user