import * as THREE from "three"; import { useEffect, useRef, useState } from "react"; import { useFrame } from "@react-three/fiber"; import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; import camModel from "../../../assets/gltf-glb/camera face 2.gltf"; import getActiveUsersData from "../../../services/factoryBuilder/collab/getActiveUsers"; import { useActiveUsers, useSocketStore } from "../../../store/builder/store"; import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; import { useNavigate } from "react-router-dom"; import { Html } from "@react-three/drei"; import CollabUserIcon from "./collabUserIcon"; import useModuleStore from "../../../store/useModuleStore"; import { getAvatarColor } from "../functions/getAvatarColor"; import { useSelectedUserStore } from "../../../store/useCollabStore"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; const CamModelsGroup = () => { const navigate = useNavigate(); const groupRef = useRef(null); const email = localStorage.getItem("email"); const { setActiveUsers } = useActiveUsers(); const { socket } = useSocketStore(); const { activeModule } = useModuleStore(); const { selectedUser, setSelectedUser } = useSelectedUserStore(); const { isPlaying } = usePlayButtonStore(); // eslint-disable-next-line react-hooks/exhaustive-deps const loader = new GLTFLoader(); const dracoLoader = new DRACOLoader(); dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); loader.setDRACOLoader(dracoLoader); const [cams, setCams] = useState([]); const [models, setModels] = useState< Record< string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler; target: THREE.Vector3; } > >({}); const dedupeCams = (cams: any[]) => { const seen = new Set(); return cams.filter((cam) => { if (seen.has(cam.uuid)) return false; seen.add(cam.uuid); return true; }); }; const dedupeUsers = (users: any[]) => { const seen = new Set(); return users.filter((user) => { if (seen.has(user._id)) return false; seen.add(user._id); return true; }); }; useEffect(() => { if (!email) navigate("/"); if (!socket) return; const organization = email!.split("@")[1].split(".")[0]; socket.on("userConnectResponse", (data: any) => { if (!groupRef.current) return; if (data.data.userData.email === email) return; if (socket.id === data.socketId || organization !== data.organization) return; const model = groupRef.current.getObjectByProperty( "uuid", data.data.userData._id ); if (model) { groupRef.current.remove(model); } loader.load(camModel, (gltf) => { const newModel = gltf.scene.clone(); newModel.uuid = data.data.userData._id; newModel.position.set( data.data.position.x, data.data.position.y, data.data.position.z ); newModel.rotation.set( data.data.rotation.x, data.data.rotation.y, data.data.rotation.z ); newModel.userData = data.data.userData; newModel.userData.target = new THREE.Vector3( data.data.target.x, data.data.target.y, data.data.target.z ); setCams((prev) => dedupeCams([...prev, newModel])); setActiveUsers((prev: any) => dedupeUsers([...prev, data.data.userData]) ); }); }); socket.on("userDisConnectResponse", (data: any) => { if (!groupRef.current) return; if (socket.id === data.socketId || organization !== data.organization) return; setCams((prev) => prev.filter((cam) => cam.uuid !== data.data.userData._id) ); setActiveUsers((prev: any) => prev.filter((user: any) => user._id !== data.data.userData._id) ); }); socket.on("cameraUpdateResponse", (data: any) => { if ( !groupRef.current || socket.id === data.socketId || organization !== data.organization ) return; if (selectedUser && selectedUser?.id === data.data.userId) { setSelectedUser({ color: selectedUser.color, name: selectedUser.name, id: selectedUser.id, location: { position: data.data.position, rotation: data.data.rotation, target: data.data.target, }, }); } setModels((prev) => ({ ...prev, [data.data.userId]: { targetPosition: new THREE.Vector3( data.data.position.x, data.data.position.y, data.data.position.z ), targetRotation: new THREE.Euler( data.data.rotation.x, data.data.rotation.y, data.data.rotation.z ), target: new THREE.Vector3( data.data.target.x, data.data.target.y, data.data.target.z ), }, })); }); return () => { socket.off("userConnectResponse"); socket.off("userDisConnectResponse"); socket.off("cameraUpdateResponse"); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [email, loader, navigate, setActiveUsers, socket]); useFrame(() => { if (!groupRef.current) return; Object.keys(models).forEach((uuid) => { const model = groupRef.current!.getObjectByProperty("uuid", uuid); if (!model) return; const { targetPosition, targetRotation } = models[uuid]; model.position.lerp(targetPosition, 0.1); model.rotation.x = THREE.MathUtils.lerp( model.rotation.x, targetRotation.x, 0.1 ); model.rotation.y = THREE.MathUtils.lerp( model.rotation.y, targetRotation.y, 0.1 ); model.rotation.z = THREE.MathUtils.lerp( model.rotation.z, targetRotation.z, 0.1 ); }); }); useEffect(() => { if (!groupRef.current) return; const organization = email!.split("@")[1].split(".")[0]; getActiveUsersData(organization).then((data) => { const filteredData = data.cameraDatas.filter( (camera: any) => camera.userData.email !== email ); if (filteredData.length > 0) { loader.load(camModel, (gltf) => { const newCams = filteredData.map((cam: any) => { const newModel = gltf.scene.clone(); newModel.uuid = cam.userData._id; newModel.position.set( cam.position.x, cam.position.y, cam.position.z ); newModel.rotation.set( cam.rotation.x, cam.rotation.y, cam.rotation.z ); newModel.userData = cam.userData; cam.userData.position = newModel.position; cam.userData.rotation = newModel.rotation; newModel.userData.target = cam.target; return newModel; }); const users = filteredData.map((cam: any) => cam.userData); setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); setCams((prev) => dedupeCams([...prev, ...newCams])); }); } }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( {cams.map((cam, index) => ( ))} ); }; export default CamModelsGroup;