🚀 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 ---
|
// --- 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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user