diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx
index a88baee..02917b4 100644
--- a/app/src/components/layout/sidebarRight/Header.tsx
+++ b/app/src/components/layout/sidebarRight/Header.tsx
@@ -1,10 +1,10 @@
import React, { useState } from "react";
-import { AppDockIcon } from "../../icons/HeaderIcons";
import orgImg from "../../../assets/orgTemp.png";
-import { useActiveUsers } from "../../../store/store";
+import { useActiveUsers, useCamMode } from "../../../store/store";
import { ActiveUser } from "../../../types/users";
import CollaborationPopup from "../../templates/CollaborationPopup";
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
+import { useSelectedUserStore } from "../../../store/useCollabStore";
const Header: React.FC = () => {
const { activeUsers } = useActiveUsers();
@@ -15,6 +15,37 @@ const Header: React.FC = () => {
);
const [userManagement, setUserManagement] = useState(false);
+ const { setSelectedUser } = useSelectedUserStore();
+ const { setCamMode } = useCamMode();
+
+ function handleUserFollow(user: any, index: number) {
+ const position = {
+ x: user.position?.x!,
+ y: user.position?.y!,
+ z: user.position?.z!,
+ };
+ const target = {
+ x: user.target?.x!,
+ y: user.target?.y!,
+ z: user.target?.z!,
+ };
+ const rotation = {
+ x: user.rotation?.x!,
+ y: user.rotation?.y!,
+ z: user.rotation?.z!,
+ };
+
+ // retun on no data
+ if (!position || !target || !rotation) return;
+
+ // Set the selected user in the store
+ setSelectedUser({
+ color: getAvatarColor(index, user.userName),
+ name: user.userName,
+ location: { position, rotation, target },
+ });
+ setCamMode("FollowPerson");
+ }
return (
<>
@@ -42,13 +73,16 @@ const Header: React.FC = () => {
+{guestUsers.length - 3}
)}
{guestUsers.slice(0, 3).map((user, index) => (
- {
+ handleUserFollow(user, index);
+ }}
>
{user.userName[0]}
-
+
))}
diff --git a/app/src/components/templates/FollowPerson.tsx b/app/src/components/templates/FollowPerson.tsx
index a85d0c9..832c7f1 100644
--- a/app/src/components/templates/FollowPerson.tsx
+++ b/app/src/components/templates/FollowPerson.tsx
@@ -13,13 +13,13 @@ const FollowPerson: React.FC = () => {
// eslint-disable-next-line
{
+ onPointerDown={() => {
clearSelectedUser();
setCamMode("FirstPerson");
}}
style={{ "--user-color": selectedUser.color } as React.CSSProperties}
>
-
{selectedUser.name}
+
Viewing through {selectedUser.name}’s eyes
)}
diff --git a/app/src/modules/collaboration/camera/collabCams.tsx b/app/src/modules/collaboration/camera/collabCams.tsx
index 461cb7f..a3e4eb4 100644
--- a/app/src/modules/collaboration/camera/collabCams.tsx
+++ b/app/src/modules/collaboration/camera/collabCams.tsx
@@ -11,230 +11,254 @@ 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";
const CamModelsGroup = () => {
- const navigate = useNavigate();
- const groupRef = useRef
(null);
- const email = localStorage.getItem("email");
- const { setActiveUsers } = useActiveUsers();
- const { socket } = useSocketStore();
- const { activeModule } = useModuleStore();
+ const navigate = useNavigate();
+ const groupRef = useRef(null);
+ const email = localStorage.getItem("email");
+ const { setActiveUsers } = useActiveUsers();
+ const { socket } = useSocketStore();
+ const { activeModule } = useModuleStore();
+ const { selectedUser } = useSelectedUserStore();
- // 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);
+ // 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>({});
+ 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 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;
- });
- };
+ 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("/");
+ useEffect(() => {
+ if (!email) navigate("/");
- if (!socket) return;
- const organization = email!.split("@")[1].split(".")[0];
+ 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;
+ 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);
- }
+ 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;
+ 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])
- );
- });
- });
+ 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;
+ 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)
- );
- });
+ 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;
+ socket.on("cameraUpdateResponse", (data: any) => {
+ if (
+ !groupRef.current ||
+ socket.id === data.socketId ||
+ organization !== data.organization
+ )
+ return;
- 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
- ),
- },
- }));
- });
+ 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");
- };
- }, [email, loader, navigate, setActiveUsers, socket]);
+ return () => {
+ socket.off("userConnectResponse");
+ socket.off("userDisConnectResponse");
+ socket.off("cameraUpdateResponse");
+ };
+ }, [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;
+ 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
- );
- });
- });
+ 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];
+ 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
- );
+ 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;
- return newModel;
- });
+ 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;
- const users = filteredData.map((cam: any) => cam.userData);
- setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
- setCams((prev) => dedupeCams([...prev, ...newCams]));
- });
- }
- });
- }, []);
+ return newModel;
+ });
- return (
-
- {cams.map((cam, index) => (
-
-
-
-
-
- ))}
-
- );
+ const users = filteredData.map((cam: any) => cam.userData);
+ setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
+ setCams((prev) => dedupeCams([...prev, ...newCams]));
+ });
+ }
+ });
+ }, []);
+
+ return (
+
+ {cams.map((cam, index) => (
+
+
+
+
+
+ ))}
+
+ );
};
export default CamModelsGroup;
diff --git a/app/src/modules/collaboration/camera/collabUserIcon.tsx b/app/src/modules/collaboration/camera/collabUserIcon.tsx
index dcdb73b..885c06f 100644
--- a/app/src/modules/collaboration/camera/collabUserIcon.tsx
+++ b/app/src/modules/collaboration/camera/collabUserIcon.tsx
@@ -17,6 +17,11 @@ interface CollabUserIconProps {
y: number;
z: number;
};
+ target?: {
+ x: number;
+ y: number;
+ z: number;
+ };
}
const CollabUserIcon: React.FC = ({
@@ -25,6 +30,7 @@ const CollabUserIcon: React.FC = ({
color,
position,
rotation,
+ target,
}) => {
const { setSelectedUser } = useSelectedUserStore();
const { setCamMode } = useCamMode();
@@ -33,10 +39,13 @@ const CollabUserIcon: React.FC = ({