Enhance collaboration features by adding user targeting and camera control functionality

- Implement user following functionality in Header component
- Update FollowPerson component to handle target and rotation
- Modify CollabUserIcon to include target data for selected users
- Adjust setCameraView function to utilize target for camera positioning
- Extend user types to include position, rotation, and target properties
This commit is contained in:
2025-05-07 15:31:07 +05:30
parent ad2b6b96f3
commit 8b7c28e9c0
8 changed files with 303 additions and 223 deletions

View File

@@ -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 = () => {
<div className="other-guest">+{guestUsers.length - 3}</div>
)}
{guestUsers.slice(0, 3).map((user, index) => (
<div
key={index}
<button
key={`${index}-${user.userName}`}
className="user-profile"
style={{ background: getAvatarColor(index, user.userName) }}
onClick={() => {
handleUserFollow(user, index);
}}
>
{user.userName[0]}
</div>
</button>
))}
</div>
<div className="user-profile-container">

View File

@@ -13,13 +13,13 @@ const FollowPerson: React.FC = () => {
// eslint-disable-next-line
<div
className="follow-person-container"
onClick={() => {
onPointerDown={() => {
clearSelectedUser();
setCamMode("FirstPerson");
}}
style={{ "--user-color": selectedUser.color } as React.CSSProperties}
>
<div className="follower-name">{selectedUser.name}</div>
<div className="follower-name">Viewing through {selectedUser.name}s eyes</div>
</div>
)}
</RenderOverlay>

View File

@@ -11,6 +11,7 @@ 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();
@@ -19,6 +20,7 @@ const CamModelsGroup = () => {
const { setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const { activeModule } = useModuleStore();
const { selectedUser } = useSelectedUserStore();
// eslint-disable-next-line react-hooks/exhaustive-deps
const loader = new GLTFLoader();
@@ -27,7 +29,16 @@ const CamModelsGroup = () => {
loader.setDRACOLoader(dracoLoader);
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler, target: THREE.Vector3 }>>({});
const [models, setModels] = useState<
Record<
string,
{
targetPosition: THREE.Vector3;
targetRotation: THREE.Euler;
target: THREE.Vector3;
}
>
>({});
const dedupeCams = (cams: any[]) => {
const seen = new Set();
@@ -81,6 +92,11 @@ const CamModelsGroup = () => {
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) =>
@@ -103,7 +119,6 @@ const CamModelsGroup = () => {
});
socket.on("cameraUpdateResponse", (data: any) => {
if (
!groupRef.current ||
socket.id === data.socketId ||
@@ -191,6 +206,10 @@ const CamModelsGroup = () => {
cam.rotation.z
);
newModel.userData = cam.userData;
cam.userData.position = newModel.position;
cam.userData.rotation = newModel.rotation;
newModel.userData.target = cam.target;
return newModel;
});
@@ -203,13 +222,17 @@ const CamModelsGroup = () => {
}, []);
return (
<group
ref={groupRef}
name="Cam-Model-Group"
visible={activeModule !== "visualization" ? true : false}
>
<group ref={groupRef} name="Cam-Model-Group">
{cams.map((cam, index) => (
<primitive key={cam.uuid} object={cam}>
<primitive
key={cam.uuid}
//eslint-disable-next-line
object={cam}
visible={
selectedUser?.name !== cam.userData.userName &&
activeModule !== "visualization"
}
>
<Html
as="div"
center
@@ -229,6 +252,7 @@ const CamModelsGroup = () => {
color={getAvatarColor(index, cam.userData.userName)}
position={cam.position}
rotation={cam.rotation}
target={cam.userData.target}
/>
</Html>
</primitive>

View File

@@ -17,6 +17,11 @@ interface CollabUserIconProps {
y: number;
z: number;
};
target?: {
x: number;
y: number;
z: number;
};
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
@@ -25,6 +30,7 @@ const CollabUserIcon: React.FC<CollabUserIconProps> = ({
color,
position,
rotation,
target,
}) => {
const { setSelectedUser } = useSelectedUserStore();
const { setCamMode } = useCamMode();
@@ -33,10 +39,13 @@ const CollabUserIcon: React.FC<CollabUserIconProps> = ({
<button
className="user-image-container"
onClick={() => {
if(!position || !rotation) return;
if (!position || !rotation || !target) return;
// Set the selected user in the store
setSelectedUser({ color: color, name: userName, location: { position, rotation } });
setSelectedUser({
color: color,
name: userName,
location: { position, rotation, target },
});
setCamMode("FollowPerson");
}}
>

View File

@@ -11,16 +11,18 @@ const Collaboration: React.FC = () => {
const { camera, controls } = useThree(); // Access R3F camera and controls
useEffect(() => {
if(camMode !== "FollowPerson") return;
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 } = selectedUser.location;
const { position, rotation, target } = selectedUser.location;
if (rotation && target)
setCameraView({
controls,
camera,
position,
rotation,
target,
username: selectedUser.name,
});
}

View File

@@ -19,13 +19,16 @@ export default async function setCameraView({
}: SetCameraViewProps) {
if (!controls || !camera) return;
if (target == null) return;
// Normalize position
const newPosition = position instanceof THREE.Vector3
? position
: new THREE.Vector3(position.x, position.y, position.z);
if (controls.setTarget) {
controls?.setLookAt(...newPosition.toArray(), newPosition.x, 0, newPosition.z, true);
}
const newTarget = target instanceof THREE.Vector3 ? target : new THREE.Vector3(target.x, target.y, target.z);
if (controls.setTarget) {
controls?.setLookAt(...newPosition.toArray(), ...newTarget.toArray(), true);
}
}

View File

@@ -9,7 +9,12 @@ interface SelectedUser {
y: number;
z: number;
};
rotation: {
rotation?: {
x: number;
y: number;
z: number;
};
target?: {
x: number;
y: number;
z: number;

View File

@@ -15,4 +15,7 @@ export type ActiveUser = {
userName: string;
email: string;
activeStatus?: string; // Optional property
position?: { x: number; y: number; z: number; };
rotation?: { x: number; y: number; z: number; };
target?: { x: number; y: number; z: number; };
};