311 lines
9.4 KiB
TypeScript
311 lines
9.4 KiB
TypeScript
import * as THREE from "three";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useFrame, useThree } 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, useCamMode, 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/collaboration/useCollabStore";
|
|
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
|
import setCameraView from "../functions/setCameraView";
|
|
|
|
const CamModelsGroup = () => {
|
|
const navigate = useNavigate();
|
|
const groupRef = useRef<THREE.Group>(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 { camMode } = useCamMode();
|
|
const { camera, controls } = useThree(); // Access R3F camera and controls
|
|
|
|
useEffect(() => {
|
|
if (camMode !== "FollowPerson") return;
|
|
// If a user is selected, set the camera view to their location
|
|
// and update the camera and controls accordingly
|
|
if (selectedUser?.location) {
|
|
const { position, rotation, target } = selectedUser.location;
|
|
if (rotation && target)
|
|
setCameraView({
|
|
controls,
|
|
camera,
|
|
position,
|
|
rotation,
|
|
target,
|
|
username: selectedUser.name,
|
|
});
|
|
}
|
|
}, [selectedUser, camera, controls, camMode]);
|
|
|
|
const [cams, setCams] = useState<any[]>([]);
|
|
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("v1:camera:Response:update", (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("v1:camera:Response:update");
|
|
};
|
|
// 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 (
|
|
<group ref={groupRef} name="Cam-Model-Group">
|
|
{cams.map((cam, index) => (
|
|
<primitive
|
|
key={cam.uuid}
|
|
//eslint-disable-next-line
|
|
object={cam}
|
|
visible={
|
|
selectedUser?.name !== cam.userData.userName &&
|
|
activeModule !== "visualization" &&
|
|
!isPlaying
|
|
}
|
|
>
|
|
<Html
|
|
as="div"
|
|
center
|
|
zIndexRange={[1, 0]}
|
|
sprite
|
|
style={{
|
|
color: "white",
|
|
textAlign: "center",
|
|
fontFamily: "Arial, sans-serif",
|
|
display: `${activeModule !== "visualization" ? "" : "none"}`,
|
|
opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying
|
|
? 1
|
|
: 0
|
|
}`,
|
|
transition: "opacity .2s ease",
|
|
}}
|
|
position={[-0.015, 0, 0.7]}
|
|
>
|
|
<CollabUserIcon
|
|
userImage={cam.userData.userImage ?? ""}
|
|
userName={cam.userData.userName}
|
|
id={cam.userData._id}
|
|
color={getAvatarColor(index, cam.userData.userName)}
|
|
position={cam.position}
|
|
rotation={cam.rotation}
|
|
target={cam.userData.target}
|
|
/>
|
|
</Html>
|
|
</primitive>
|
|
))}
|
|
</group>
|
|
);
|
|
};
|
|
|
|
export default CamModelsGroup;
|