add initial components and utility functions for simulation and builder modules

This commit is contained in:
2025-03-25 14:00:03 +05:30
parent 61b3c4ee2c
commit 2303682a15
164 changed files with 13967 additions and 52 deletions

View File

@@ -0,0 +1,202 @@
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import gsap from 'gsap';
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import { toast } from 'react-toastify';
import * as Types from "../../../types/world/worldTypes";
import { getFloorItems } from '../../../services/factoryBuilder/assest/floorAsset/getFloorItemsApi';
import { initializeDB, retrieveGLTF, storeGLTF } from '../../../utils/indexDB/idbUtils';
import { getCamera } from '../../../services/factoryBuilder/camera/getCameraApi';
async function loadInitialFloorItems(
itemsGroup: Types.RefGroup,
setFloorItems: Types.setFloorItemSetState
): Promise<void> {
if (!itemsGroup.current) return;
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const email = localStorage.getItem('email');
const organization = (email!.split("@")[1]).split(".")[0];
const items = await getFloorItems(organization);
localStorage.setItem("FloorItems", JSON.stringify(items));
await initializeDB();
if (items) {
const storedFloorItems: Types.FloorItems = items;
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
let modelsLoaded = 0;
const modelsToLoad = storedFloorItems.length;
const camData = await getCamera(organization, localStorage.getItem('userId')!);
let storedPosition;
if (camData && camData.position) {
storedPosition = camData?.position;
} else {
storedPosition = new THREE.Vector3(0, 40, 30);
}
if (!storedPosition) return;
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
storedFloorItems.sort((a, b) => {
const aPosition = new THREE.Vector3(a.position[0], a.position[1], a.position[2]);
const bPosition = new THREE.Vector3(b.position[0], b.position[1], b.position[2]);
return cameraPosition.distanceTo(aPosition) - cameraPosition.distanceTo(bPosition);
});
for (const item of storedFloorItems) {
if (!item.modelfileID) return;
const itemPosition = new THREE.Vector3(item.position[0], item.position[1], item.position[2]);
let storedPosition;
if (localStorage.getItem("cameraPosition")) {
storedPosition = JSON.parse(localStorage.getItem("cameraPosition")!);
} else {
storedPosition = new THREE.Vector3(0, 40, 30);
}
const cameraPosition = new THREE.Vector3(storedPosition.x, storedPosition.y, storedPosition.z);
if (cameraPosition.distanceTo(itemPosition) < 50) {
await new Promise<void>(async (resolve) => {
// Check Three.js Cache
const cachedModel = THREE.Cache.get(item.modelfileID!);
if (cachedModel) {
// console.log(`[Cache] Fetching ${item.modelname}`);
processLoadedModel(cachedModel.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
return;
}
// Check IndexedDB
const indexedDBModel = await retrieveGLTF(item.modelfileID!);
if (indexedDBModel) {
// console.log(`[IndexedDB] Fetching ${item.modelname}`);
const blobUrl = URL.createObjectURL(indexedDBModel);
loader.load(
blobUrl,
(gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(item.modelfileID!, gltf);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
undefined,
(error) => {
toast.error(`[IndexedDB] Error loading ${item.modelname}:`);
URL.revokeObjectURL(blobUrl);
resolve();
}
);
return;
}
// Fetch from Backend
// console.log(`[Backend] Fetching ${item.modelname}`);
const modelUrl = `${url_Backend_dwinzo}/api/v1/AssetFile/${item.modelfileID!}`;
loader.load(
modelUrl,
async (gltf) => {
const modelBlob = await fetch(modelUrl).then((res) => res.blob());
await storeGLTF(item.modelfileID!, modelBlob);
THREE.Cache.add(item.modelfileID!, gltf);
processLoadedModel(gltf.scene.clone(), item, itemsGroup, setFloorItems);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, resolve);
},
undefined,
(error) => {
toast.error(`[Backend] Error loading ${item.modelname}:`);
resolve();
}
);
});
} else {
// console.log(`Item ${item.modelname} is not near`);
setFloorItems((prevItems) => [
...(prevItems || []),
{
modeluuid: item.modeluuid,
modelname: item.modelname,
position: item.position,
rotation: item.rotation,
modelfileID: item.modelfileID,
isLocked: item.isLocked,
isVisible: item.isVisible,
},
]);
modelsLoaded++;
checkLoadingCompletion(modelsLoaded, modelsToLoad, dracoLoader, () => { });
}
}
// Dispose loader after all models
dracoLoader.dispose();
}
}
function processLoadedModel(
gltf: any,
item: Types.FloorItemType,
itemsGroup: Types.RefGroup,
setFloorItems: Types.setFloorItemSetState
) {
const model = gltf;
model.uuid = item.modeluuid;
model.scale.set(...CONSTANTS.assetConfig.defaultScaleBeforeGsap);
model.userData = { name: item.modelname, modelId: item.modelfileID };
model.position.set(...item.position);
model.rotation.set(item.rotation.x, item.rotation.y, item.rotation.z);
model.traverse((child: any) => {
if (child.isMesh) {
// Clone the material to ensure changes are independent
// child.material = child.material.clone();
child.castShadow = true;
child.receiveShadow = true;
}
});
itemsGroup?.current?.add(model);
setFloorItems((prevItems) => [
...(prevItems || []),
{
modeluuid: item.modeluuid,
modelname: item.modelname,
position: item.position,
rotation: item.rotation,
modelfileID: item.modelfileID,
isLocked: item.isLocked,
isVisible: item.isVisible,
},
]);
gsap.to(model.position, { y: item.position[1], duration: 1.5, ease: 'power2.out' });
gsap.to(model.scale, { x: 1, y: 1, z: 1, duration: 1.5, ease: 'power2.out' });
}
function checkLoadingCompletion(
modelsLoaded: number,
modelsToLoad: number,
dracoLoader: DRACOLoader,
resolve: () => void
) {
if (modelsLoaded === modelsToLoad) {
toast.success("Models Loaded!");
dracoLoader.dispose();
}
resolve();
}
export default loadInitialFloorItems;

View File

@@ -0,0 +1,30 @@
import addLineToScene from '../../builder/geomentries/lines/addLineToScene';
import * as CONSTANTS from '../../../types/world/worldConstants';
import * as Types from "../../../types/world/worldTypes";
function loadInitialLine(
floorPlanGroupLine: Types.RefGroup,
lines: Types.RefLines
): void {
if (!floorPlanGroupLine.current) return
////////// Load the Lines initially if there are any //////////
floorPlanGroupLine.current.children = [];
lines.current.forEach((line) => {
let colour;
if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.wallName) {
colour = CONSTANTS.lineConfig.wallColor;
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.floorName) {
colour = CONSTANTS.lineConfig.floorColor;
} else if (line[0][3] && line[1][3] === CONSTANTS.lineConfig.aisleName) {
colour = CONSTANTS.lineConfig.aisleColor;
}
if (colour) {
addLineToScene(line[0][0], line[1][0], colour, line, floorPlanGroupLine);
}
});
}
export default loadInitialLine;

View File

@@ -0,0 +1,87 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
import * as Types from "../../../types/world/worldTypes";
////////// Load the Boxes initially if there are any //////////
function loadInitialPoint(
lines: Types.RefLines,
floorPlanGroupPoint: Types.RefGroup,
currentLayerPoint: Types.RefMeshArray,
dragPointControls: Types.RefDragControl
): void {
if (!floorPlanGroupPoint.current) return
floorPlanGroupPoint.current.children = [];
currentLayerPoint.current = [];
lines.current.forEach((line) => {
const colour = getPointColor(line[0][3]);
line.forEach((pointData) => {
const [point, id] = pointData;
/////////// Check if a box with this id already exists //////////
const existingBox = floorPlanGroupPoint.current?.getObjectByProperty('uuid', id);
if (existingBox) {
return;
}
const geometry = new THREE.BoxGeometry(...CONSTANTS.pointConfig.boxScale);
const material = new THREE.ShaderMaterial({
uniforms: {
uColor: { value: new THREE.Color(colour) }, // Blue color for the border
uInnerColor: { value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor) }, // White color for the inner square
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uColor;
uniform vec3 uInnerColor;
void main() {
// Define the size of the white square as a proportion of the face
float borderThickness = 0.2; // Adjust this value for border thickness
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
} else {
gl_FragColor = vec4(uColor, 1.0); // Blue border
}
}
`,
});
const box = new THREE.Mesh(geometry, material);
box.name = "point";
box.uuid = id;
box.userData = { type: line[0][3], color: colour };
box.position.set(point.x, point.y, point.z);
currentLayerPoint.current.push(box);
floorPlanGroupPoint.current?.add(box);
});
});
function getPointColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName: return CONSTANTS.pointConfig.wallOuterColor;
case CONSTANTS.lineConfig.floorName: return CONSTANTS.pointConfig.floorOuterColor;
case CONSTANTS.lineConfig.aisleName: return CONSTANTS.pointConfig.aisleOuterColor;
default: return CONSTANTS.pointConfig.defaultOuterColor;
}
}
if (dragPointControls.current) {
dragPointControls.current!.objects = currentLayerPoint.current;
}
}
export default loadInitialPoint;

View File

@@ -0,0 +1,54 @@
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import * as Types from "../../../types/world/worldTypes";
import { getWallItems } from '../../../services/factoryBuilder/assest/wallAsset/getWallItemsApi';
////////// Load the Wall Items's intially of there is any //////////
async function loadInitialWallItems(
setWallItems: Types.setWallItemSetState,
AssetConfigurations: Types.AssetConfigurations
): Promise<void> {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
const items = await getWallItems(organization);
localStorage.setItem("WallItems", JSON.stringify(items));
if (items.length > 0) {
const storedWallItems: Types.wallItems = items;
const loadedWallItems = await Promise.all(storedWallItems.map(async (item) => {
const loader = new GLTFLoader();
return new Promise<Types.WallItem>((resolve) => {
loader.load(AssetConfigurations[item.modelname!].modelUrl, (gltf) => {
const model = gltf.scene;
model.uuid = item.modeluuid!;
model.children[0].children.forEach((child: any) => {
if (child.name !== "CSG_REF") {
child.castShadow = true;
child.receiveShadow = true;
}
});
resolve({
type: item.type,
model: model,
modelname: item.modelname,
scale: item.scale,
csgscale: item.csgscale,
csgposition: item.csgposition,
position: item.position,
quaternion: item.quaternion,
});
});
});
}));
setWallItems(loadedWallItems);
}
}
export default loadInitialWallItems;

View File

@@ -0,0 +1,89 @@
import { useFrame, useThree } from '@react-three/fiber';
import React, { useEffect, useState } from 'react';
import * as CONSTANTS from '../../../types/world/worldConstants';
import { useCamMode, useToggleView } from '../../../store/store';
import { useKeyboardControls } from '@react-three/drei';
import switchToThirdPerson from './switchToThirdPerson';
import switchToFirstPerson from './switchToFirstPerson';
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();
const [, get] = useKeyboardControls()
const [isTransitioning, setIsTransitioning] = useState(false);
const state: any = useThree();
const { toggleView } = useToggleView();
useEffect(() => {
const handlePointerLockChange = async () => {
if (document.pointerLockElement && !toggleView) {
// console.log('Pointer is locked');
} else {
// console.log('Pointer is unlocked');
if (camMode === "FirstPerson" && !toggleView) {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
}
};
document.addEventListener('pointerlockchange', handlePointerLockChange);
return () => {
document.removeEventListener('pointerlockchange', handlePointerLockChange);
};
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
useEffect(() => {
const handleKeyPress = async (event: any) => {
if (!state.controls) return;
if (event.key === "/" && !isTransitioning && !toggleView) {
setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
if (camMode === 'ThirdPerson') {
setCamMode("FirstPerson");
await switchToFirstPerson(state.controls, state.camera);
} else if (camMode === "FirstPerson") {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning(false);
}
};
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [camMode, isTransitioning, toggleView, state.controls, state.camera, setCamMode]);
useFrame(() => {
const { forward, backward, left, right } = get();
if (!state.controls) return
if (!state.controls || camMode === "ThirdPerson" || !document.pointerLockElement) return;
if (forward) {
state.controls.forward(CONSTANTS.firstPersonControls.forwardSpeed, true)
}
if (backward) {
state.controls.forward(CONSTANTS.firstPersonControls.backwardSpeed, true)
}
if (left) {
state.controls.truck(CONSTANTS.firstPersonControls.leftSpeed, 0, true)
}
if (right) {
state.controls.truck(CONSTANTS.firstPersonControls.rightSpeed, 0, true)
}
});
return (
<></>
);
};
export default CamMode;

View File

@@ -0,0 +1,25 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
export default async function switchToFirstPerson(
controls: any,
camera: any
) {
if (!controls) return;
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
cameraDirection.normalize();
await controls.setPosition(camera.position.x, 2, camera.position.z, true);
controls.setTarget(camera.position.x, 2, camera.position.z, true);
controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
controls.lockPointer();
controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
}

View File

@@ -0,0 +1,29 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
export default async function switchToThirdPerson(
controls: any,
camera: any
) {
if (!controls) return;
controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
controls.unlockPointer();
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.threeDimension.minDistance;
controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
}

View File

@@ -0,0 +1,70 @@
import * as THREE from "three";
import { useEffect, useRef } from "react";
import { useToggleView } from "../../../store/store";
import { useThree } from "@react-three/fiber";
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import * as CONSTANTS from '../../../types/world/worldConstants';
export default function SwitchView() {
const { toggleView } = useToggleView();
const state: any = useThree();
const { set } = useThree();
const perspectiveCamera = useRef<THREE.PerspectiveCamera | null>(null);
const orthoCamera = useRef<THREE.OrthographicCamera | null>(null);
orthoCamera.current = new THREE.OrthographicCamera(-window.innerWidth / 2, window.innerWidth / 2, window.innerHeight / 2, -window.innerHeight / 2, 0.01, 1000);
perspectiveCamera.current = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1000);
useEffect(() => {
if (!perspectiveCamera.current || !orthoCamera.current) return;
if (toggleView) {
orthoCamera.current.zoom = 10;
orthoCamera.current.position.set(...CONSTANTS.twoDimension.defaultPosition);
orthoCamera.current.lookAt(new THREE.Vector3(...CONSTANTS.twoDimension.defaultTarget));
orthoCamera.current.updateProjectionMatrix();
set({ camera: orthoCamera.current });
orthoCamera.current.updateProjectionMatrix();
} else if (!toggleView) {
perspectiveCamera.current.position.set(...CONSTANTS.threeDimension.defaultPosition);
perspectiveCamera.current.lookAt(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget));
set({ camera: perspectiveCamera.current });
}
}, [toggleView, set]);
useEffect(() => {
if (toggleView && state.controls) {
state.controls.mouseButtons.left = CONSTANTS.twoDimension.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else {
try {
const email = localStorage.getItem('email');
const organization = (email!.split("@")[1]).split(".")[0];
getCamera(organization, localStorage.getItem('userId')!).then((data) => {
if (data && data.position && data.target) {
state.controls?.setPosition(data.position.x, data.position.y, data.position.z);
state.controls?.setTarget(data.target.x, data.target.y, data.target.z);
localStorage.setItem("cameraPosition", JSON.stringify(data.position));
localStorage.setItem("controlTarget", JSON.stringify(data.target));
} else {
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
}
});
} catch (error) {
console.error("Failed to retrieve camera position or target:", error);
state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
if (state.controls) {
state.controls.mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.threeDimension.rightMouse;
}
}
}, [toggleView, state.controls]);
return (
<></>
);
}

View File

@@ -0,0 +1,26 @@
import { Socket } from "socket.io-client";
import * as THREE from 'three';
export default function updateCamPosition(
controls: any,
socket: Socket,
position: THREE.Vector3,
rotation: THREE.Euler
) {
if (!controls.current) return;
const target = controls.current.getTarget(new THREE.Vector3());
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
const camData = {
organization: organization,
userId: localStorage.getItem("userId")!,
position: position,
target: new THREE.Vector3(target.x, 0, target.z),
rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
socketId: socket.id,
};
socket.emit("v1:Camera:set", camData);
localStorage.setItem("cameraPosition", JSON.stringify(position));
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(target.x, 0, target.z)));
}

View File

@@ -0,0 +1,136 @@
import { CameraControls } from "@react-three/drei";
import { useRef, useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as CONSTANTS from '../../../types/world/worldConstants';
import { useSocketStore, useToggleView, useResetCamera } from "../../../store/store";
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import updateCamPosition from "../camera/updateCameraPosition";
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
const { toggleView } = useToggleView();
const { resetCamera, setResetCamera } = useResetCamera();
const { socket } = useSocketStore();
const state = useThree();
useEffect(() => {
if (controlsRef.current) {
(controlsRef.current as any).mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
(controlsRef.current as any).mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
}
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
getCamera(organization, localStorage.getItem("userId")!).then((data) => {
if (data && data.position && data.target) {
controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z);
controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z);
} else {
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
})
.catch((error) => console.error("Failed to fetch camera data:", error));
}, []);
useEffect(() => {
if (resetCamera) {
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
localStorage.setItem("cameraPosition", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition)));
localStorage.setItem("controlTarget", JSON.stringify(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget)));
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
const camData = {
organization: organization,
userId: localStorage.getItem('userId')!,
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
socketId: socket.id
};
socket.emit('v1:Camera:set', camData)
setResetCamera(false);
}
}, [resetCamera]);
useEffect(() => {
controlsRef.current?.setBoundary(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)));
// state.scene.add(new THREE.Box3Helper(new THREE.Box3(new THREE.Vector3(...CONSTANTS.threeDimension.boundaryBottom), new THREE.Vector3(...CONSTANTS.threeDimension.boundaryTop)), 0xffff00));
let hasInteracted = false;
let intervalId: NodeJS.Timeout | null = null;
const handleRest = () => {
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView) {
const position = state.camera.position;
if (position.x === 0 && position.y === 0 && position.z === 0) return;
updateCamPosition(controlsRef, socket, position, state.camera.rotation);
stopInterval();
}
};
const startInterval = () => {
hasInteracted = true;
if (!intervalId) {
intervalId = setInterval(() => {
if (controlsRef.current && !toggleView) {
handleRest();
}
}, CONSTANTS.camPositionUpdateInterval);
}
};
const stopInterval = () => {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};
const controls = controlsRef.current;
if (controls) {
controls.addEventListener("sleep", handleRest);
controls.addEventListener("control", startInterval);
controls.addEventListener("controlend", stopInterval);
}
return () => {
if (controls) {
controls.removeEventListener("sleep", handleRest);
controls.removeEventListener("control", startInterval);
controls.removeEventListener("controlend", stopInterval);
}
stopInterval();
};
}, [toggleView, state, socket]);
return (
<>
<CameraControls
makeDefault
ref={controlsRef}
minDistance={toggleView ? CONSTANTS.twoDimension.minDistance : CONSTANTS.threeDimension.minDistance}
maxDistance={CONSTANTS.thirdPersonControls.maxDistance}
minZoom={CONSTANTS.thirdPersonControls.minZoom}
maxZoom={CONSTANTS.thirdPersonControls.maxZoom}
maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle}
camera={state.camera}
verticalDragToForward={true}
boundaryEnclosesCamera={true}
dollyToCursor={toggleView}
>
<SwitchView />
<CamMode />
</CameraControls>
</>
);
}

View File

@@ -0,0 +1,62 @@
import { Line } from "@react-three/drei";
import { useMemo } from "react";
import * as THREE from "three";
import { useSelectedAssets } from "../../../../store/store";
const BoundingBox = ({ boundingBoxRef }: any) => {
const { selectedAssets } = useSelectedAssets();
const { points, boxProps } = useMemo(() => {
if (selectedAssets.length === 0) return { points: [], boxProps: {} };
const box = new THREE.Box3();
selectedAssets.forEach((obj: any) => box.expandByObject(obj.clone()));
const size = new THREE.Vector3();
box.getSize(size);
const center = new THREE.Vector3();
box.getCenter(center);
const halfSize = size.clone().multiplyScalar(0.5);
const min = center.clone().sub(halfSize);
const max = center.clone().add(halfSize);
const points: any = [
[min.x, min.y, min.z], [max.x, min.y, min.z],
[max.x, min.y, min.z], [max.x, max.y, min.z],
[max.x, max.y, min.z], [min.x, max.y, min.z],
[min.x, max.y, min.z], [min.x, min.y, min.z],
[min.x, min.y, max.z], [max.x, min.y, max.z],
[max.x, min.y, max.z], [max.x, max.y, max.z],
[max.x, max.y, max.z], [min.x, max.y, max.z],
[min.x, max.y, max.z], [min.x, min.y, max.z],
[min.x, min.y, min.z], [min.x, min.y, max.z],
[max.x, min.y, min.z], [max.x, min.y, max.z],
[max.x, max.y, min.z], [max.x, max.y, max.z],
[min.x, max.y, min.z], [min.x, max.y, max.z],
];
return {
points,
boxProps: { position: center.toArray(), args: size.toArray() }
};
}, [selectedAssets]);
return (
<>
{points.length > 0 && (
<>
<Line points={points} color="yellow" lineWidth={2.5} segments />
<mesh ref={boundingBoxRef} visible={false} position={boxProps.position}>
<boxGeometry args={boxProps.args} />
<meshBasicMaterial />
</mesh>
</>
)}
</>
);
};
export default BoundingBox;

View File

@@ -0,0 +1,209 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
const CopyPasteControls = ({ itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, selectionGroup, setDuplicatedObjects, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore()
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && pastedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addPastedObjects();
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.ctrlKey && event.key.toLowerCase() === "c" && movedObjects.length === 0 && rotatedObjects.length === 0) {
copySelection();
}
if (event.ctrlKey && event.key.toLowerCase() === "v" && copiedObjects.length > 0 && pastedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
pasteCopiedObjects();
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (pastedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
pastedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const copySelection = () => {
if (selectedAssets.length > 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
setCopiedObjects(newClones);
toast.info("Objects copied!");
}
};
const pasteCopiedObjects = () => {
if (copiedObjects.length > 0 && pastedObjects.length === 0) {
const newClones = copiedObjects.map((obj: THREE.Object3D) => {
const clone = obj.clone();
clone.position.copy(obj.position);
return clone;
});
selectionGroup.current.add(...newClones);
setpastedObjects([...newClones]);
setSelectedAssets([...newClones]);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
newClones.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
};
const addPastedObjects = () => {
if (pastedObjects.length === 0) return;
pastedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v1:FloorItems:set", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object added!");
clearSelection();
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return (
<></>
);
};
export default CopyPasteControls;

View File

@@ -0,0 +1,190 @@
import * as THREE from "three";
import { useEffect, useMemo } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
import { toast } from "react-toastify";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
const DuplicationControls = ({ itemsGroupRef, duplicatedObjects, setDuplicatedObjects, setpastedObjects, selectionGroup, movedObjects, setMovedObjects, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && duplicatedObjects.length > 0 && event.button === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
event.preventDefault();
addDuplicatedAssets();
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "d") {
event.preventDefault();
if (event.ctrlKey && event.key.toLowerCase() === "d" && selectedAssets.length > 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (duplicatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
} else {
const box = new THREE.Box3();
duplicatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj.clone()));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.set(point.x - (center.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (center.z - selectionGroup.current.position.z));
}
}
}
});
const duplicateSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const newClones = selectedAssets.map((asset: any) => {
const clone = asset.clone();
clone.position.copy(asset.position);
return clone;
});
selectionGroup.current.add(...newClones);
setDuplicatedObjects(newClones);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
const position = new THREE.Vector3();
boundingBoxRef.current?.getWorldPosition(position)
selectionGroup.current.position.set(point.x - (position.x - selectionGroup.current.position.x), selectionGroup.current.position.y, point.z - (position.z - selectionGroup.current.position.z));
}
}
};
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
duplicatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v1:FloorItems:set", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object duplicated!");
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return (
<></>
);
};
export default DuplicationControls;

View File

@@ -0,0 +1,239 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedAssets();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "g") {
if (selectedAssets.length > 0) {
moveAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
movedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setMovedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects]);
const gridSize = 0.25;
const moveSpeed = 0.25;
const isGridSnap = false;
useFrame(() => {
if (movedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
let targetX = point.x;
let targetZ = point.z;
if (isGridSnap) {
targetX = Math.round(point.x / gridSize) * gridSize;
targetZ = Math.round(point.z / gridSize) * gridSize;
}
const position = new THREE.Vector3();
if (boundingBoxRef.current) {
boundingBoxRef.current.getWorldPosition(position);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (position.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (position.z - selectionGroup.current.position.z)
),
moveSpeed
);
} else {
const box = new THREE.Box3();
movedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
selectionGroup.current.position.lerp(
new THREE.Vector3(
targetX - (center.x - selectionGroup.current.position.x),
selectionGroup.current.position.y,
targetZ - (center.z - selectionGroup.current.position.z)
),
moveSpeed
);
}
}
}
});
const moveAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
setMovedObjects(selectedAssets);
selectedAssets.forEach((asset: any) => { selectionGroup.current.attach(asset); });
}
const placeMovedAssets = () => {
if (movedObjects.length === 0) return;
movedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
obj.getWorldPosition(worldPosition);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v1:FloorItems:set", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object moved!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return (
<></>
)
}
export default MoveControls

View File

@@ -0,0 +1,242 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
// import { setFloorItemApi } from '../../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi';
import { toast } from "react-toastify";
import * as Types from "../../../../types/world/worldTypes";
function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const itemsData = useRef<Types.FloorItems>([]);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
useEffect(() => {
if (!camera || !scene || toggleView || !itemsGroupRef.current) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && rotatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeRotatedAssets();
}
if (!isMoving && rotatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || movedObjects.length > 0) return;
if (event.key.toLowerCase() === "r") {
if (selectedAssets.length > 0) {
rotateAssets();
itemsData.current = floorItems.filter((item: { modeluuid: string }) => selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
}
}
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
rotatedObjects.forEach((asset: any) => {
if (itemsGroupRef.current) {
itemsGroupRef.current.attach(asset);
}
});
setFloorItems([...floorItems, ...itemsData.current]);
setRotatedObjects([]);
itemsData.current = [];
}
};
if (!toggleView) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedAssets, socket, floorItems, pastedObjects, duplicatedObjects, rotatedObjects, movedObjects]);
useFrame(() => {
if (rotatedObjects.length > 0) {
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point && prevPointerPosition.current) {
const box = new THREE.Box3();
rotatedObjects.forEach((obj: THREE.Object3D) => box.expandByObject(obj));
const center = new THREE.Vector3();
box.getCenter(center);
const delta = new THREE.Vector3().subVectors(point, center);
const prevPointerPosition3D = new THREE.Vector3(prevPointerPosition.current.x, 0, prevPointerPosition.current.y);
const angle = Math.atan2(delta.z, delta.x) - Math.atan2(prevPointerPosition3D.z - center.z, prevPointerPosition3D.x - center.x);
selectionGroup.current.rotation.y += -angle;
selectionGroup.current.position.sub(center);
selectionGroup.current.position.applyAxisAngle(new THREE.Vector3(0, 1, 0), -angle);
selectionGroup.current.position.add(center);
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
}
});
const rotateAssets = () => {
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedAssets.some((asset: any) => asset.uuid === item.modeluuid));
setFloorItems(updatedItems);
const box = new THREE.Box3();
selectedAssets.forEach((asset: any) => box.expandByObject(asset));
const center = new THREE.Vector3();
box.getCenter(center);
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const point = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (point) {
prevPointerPosition.current = new THREE.Vector2(point.x, point.z);
}
selectedAssets.forEach((asset: any) => {
selectionGroup.current.attach(asset);
});
setRotatedObjects(selectedAssets);
};
const placeRotatedAssets = () => {
if (rotatedObjects.length === 0) return;
rotatedObjects.forEach(async (obj: THREE.Object3D) => {
const worldPosition = new THREE.Vector3();
const worldQuaternion = new THREE.Quaternion();
obj.getWorldPosition(worldPosition);
obj.getWorldQuaternion(worldQuaternion);
selectionGroup.current.remove(obj);
obj.position.copy(worldPosition);
obj.quaternion.copy(worldQuaternion);
if (itemsGroupRef.current) {
const newFloorItem: Types.FloorItemType = {
modeluuid: obj.uuid,
modelname: obj.userData.name,
modelfileID: obj.userData.modelId,
position: [worldPosition.x, worldPosition.y, worldPosition.z],
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z, },
isLocked: false,
isVisible: true
};
setFloorItems((prevItems: Types.FloorItems) => {
const updatedItems = [...(prevItems || []), newFloorItem];
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
const email = localStorage.getItem("email");
const organization = email ? email.split("@")[1].split(".")[0] : "default";
//REST
// await setFloorItemApi(
// organization,
// obj.uuid,
// obj.userData.name,
// [worldPosition.x, worldPosition.y, worldPosition.z],
// { "x": obj.rotation.x, "y": obj.rotation.y, "z": obj.rotation.z },
// obj.userData.modelId,
// false,
// true,
// );
//SOCKET
const data = {
organization,
modeluuid: newFloorItem.modeluuid,
modelname: newFloorItem.modelname,
modelfileID: newFloorItem.modelfileID,
position: newFloorItem.position,
rotation: { x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
};
socket.emit("v1:FloorItems:set", data);
itemsGroupRef.current.add(obj);
}
});
toast.success("Object rotated!");
itemsData.current = [];
clearSelection();
}
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
}
return (
<></>
)
}
export default RotateControls

View File

@@ -0,0 +1,237 @@
import * as THREE from "three";
import { useEffect, useMemo, useRef, useState } from "react";
import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox";
import { SelectionHelper } from "./selectionHelper";
import { useFrame, useThree } from "@react-three/fiber";
import { useFloorItems, useSelectedAssets, useSocketStore, useToggleView } from "../../../../store/store";
import BoundingBox from "./boundingBoxHelper";
import { toast } from "react-toastify";
// import { deleteFloorItem } from '../../../../services/factoryBuilder/assest/floorAsset/deleteFloorItemApi';
import * as Types from "../../../../types/world/worldTypes";
import DuplicationControls from "./duplicationControls";
import CopyPasteControls from "./copyPasteControls";
import MoveControls from "./moveControls";
import RotateControls from "./rotateControls";
const SelectionControls: React.FC = () => {
const { camera, controls, gl, scene, pointer } = useThree();
const itemsGroupRef = useRef<THREE.Group | undefined>(undefined);
const selectionGroup = useRef() as Types.RefGroup;
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const [movedObjects, setMovedObjects] = useState<THREE.Object3D[]>([]);
const [rotatedObjects, setRotatedObjects] = useState<THREE.Object3D[]>([]);
const [copiedObjects, setCopiedObjects] = useState<THREE.Object3D[]>([]);
const [pastedObjects, setpastedObjects] = useState<THREE.Object3D[]>([]);
const [duplicatedObjects, setDuplicatedObjects] = useState<THREE.Object3D[]>([]);
const boundingBoxRef = useRef<THREE.Mesh>();
const { floorItems, setFloorItems } = useFloorItems();
const { socket } = useSocketStore();
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
const itemsGroup: any = scene.getObjectByName("itemsGroup");
itemsGroupRef.current = itemsGroup;
let isSelecting = false;
let isCtrlSelecting = false;
const helper = new SelectionHelper(gl);
if (!itemsGroup) {
toast.warn("itemsGroup not found in the scene.");
return;
}
const onPointerDown = (event: PointerEvent) => {
if (event.button !== 0) return
isSelecting = false;
isCtrlSelecting = event.ctrlKey;
if (event.ctrlKey && duplicatedObjects.length === 0) {
if (controls) (controls as any).enabled = false;
selectionBox.startPoint.set(pointer.x, pointer.y, 0);
}
};
const onPointerMove = (event: PointerEvent) => {
isSelecting = true;
if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) {
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
}
};
const onPointerUp = (event: PointerEvent) => {
if (isSelecting && isCtrlSelecting) {
isCtrlSelecting = false;
isSelecting = false;
if (event.ctrlKey && duplicatedObjects.length === 0) {
selectAssets();
}
} else if (!isSelecting && selectedAssets.length > 0 && ((pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
clearSelection();
helper.enabled = true;
isCtrlSelecting = false;
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (movedObjects.length > 0 || rotatedObjects.length > 0) return;
if (event.key.toLowerCase() === "escape") {
event.preventDefault();
clearSelection();
}
if (event.key.toLowerCase() === "delete") {
event.preventDefault();
deleteSelection();
}
};
const onContextMenu = (event: MouseEvent) => {
event.preventDefault();
clearSelection();
}
if (!toggleView) {
helper.enabled = true;
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("contextmenu", onContextMenu);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("contextmenu", onContextMenu);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
helper.enabled = false;
helper.dispose();
};
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, floorItems, rotatedObjects]);
useFrame(() => {
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
selectionGroup.current.position.set(0, 0, 0);
}
});
const selectAssets = () => {
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
if (controls) (controls as any).enabled = true;
let selectedObjects = selectionBox.select();
let Objects = new Set<THREE.Object3D>();
selectedObjects.map((object) => {
let currentObject: THREE.Object3D | null = object;
while (currentObject) {
if (currentObject.userData.modelId) {
Objects.add(currentObject);
break;
}
currentObject = currentObject.parent || null;
}
})
if (Objects.size === 0) {
clearSelection();
return;
}
const updatedSelections = new Set(selectedAssets);
Objects.forEach((obj) => {
updatedSelections.has(obj) ? updatedSelections.delete(obj) : updatedSelections.add(obj);
});
const selected = Array.from(updatedSelections);
setSelectedAssets(selected);
};
const clearSelection = () => {
selectionGroup.current.children = [];
selectionGroup.current.position.set(0, 0, 0);
selectionGroup.current.rotation.set(0, 0, 0);
setpastedObjects([]);
setDuplicatedObjects([]);
setSelectedAssets([]);
}
const deleteSelection = () => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const email = localStorage.getItem('email');
const organization = (email!.split("@")[1]).split(".")[0];
const storedItems = JSON.parse(localStorage.getItem("FloorItems") || '[]');
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
const updatedStoredItems = storedItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
localStorage.setItem("FloorItems", JSON.stringify(updatedStoredItems));
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
//REST
// const response = await deleteFloorItem(organization, selectedMesh.uuid, selectedMesh.userData.name);
//SOCKET
const data = {
organization: organization,
modeluuid: selectedMesh.uuid,
modelname: selectedMesh.userData.name,
socketId: socket.id
};
socket.emit('v1:FloorItems:delete', data);
selectedMesh.traverse((child: THREE.Object3D) => {
if (child instanceof THREE.Mesh) {
if (child.geometry) child.geometry.dispose();
if (Array.isArray(child.material)) {
child.material.forEach((material) => {
if (material.map) material.map.dispose();
material.dispose();
});
} else if (child.material) {
if (child.material.map) child.material.map.dispose();
child.material.dispose();
}
}
});
itemsGroupRef.current?.remove(selectedMesh);
});
const updatedItems = floorItems.filter((item: { modeluuid: string }) => !selectedUUIDs.includes(item.modeluuid));
setFloorItems(updatedItems);
}
toast.success("Selected models removed!");
clearSelection();
};
return (
<>
<group name="SelectionGroup" >
<group ref={selectionGroup} name="selectionAssetGroup" >
<BoundingBox boundingBoxRef={boundingBoxRef} />
</group>
</group>
<MoveControls movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
<RotateControls rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} selectionGroup={selectionGroup} boundingBoxRef={boundingBoxRef} />
<DuplicationControls itemsGroupRef={itemsGroupRef} duplicatedObjects={duplicatedObjects} setDuplicatedObjects={setDuplicatedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
<CopyPasteControls itemsGroupRef={itemsGroupRef} copiedObjects={copiedObjects} setCopiedObjects={setCopiedObjects} pastedObjects={pastedObjects} setpastedObjects={setpastedObjects} selectionGroup={selectionGroup} setDuplicatedObjects={setDuplicatedObjects} movedObjects={movedObjects} setMovedObjects={setMovedObjects} rotatedObjects={rotatedObjects} setRotatedObjects={setRotatedObjects} boundingBoxRef={boundingBoxRef} />
</>
);
};
export default SelectionControls;

View File

@@ -0,0 +1,115 @@
import { Vector2, WebGLRenderer } from 'three';
class SelectionHelper {
element: HTMLDivElement;
renderer: WebGLRenderer;
startPoint: Vector2;
pointTopLeft: Vector2;
pointBottomRight: Vector2;
isDown: boolean;
enabled: boolean;
constructor(renderer: WebGLRenderer) {
this.element = document.createElement('div');
this.element.style.position = 'fixed';
this.element.style.border = '1px solid #55aaff';
this.element.style.backgroundColor = 'rgba(75, 160, 255, 0.3)';
this.element.style.pointerEvents = 'none';
this.element.style.display = 'none';
this.renderer = renderer;
this.startPoint = new Vector2();
this.pointTopLeft = new Vector2();
this.pointBottomRight = new Vector2();
this.isDown = false;
this.enabled = true;
this.onPointerDown = this.onPointerDown.bind(this);
this.onPointerMove = this.onPointerMove.bind(this);
this.onPointerUp = this.onPointerUp.bind(this);
this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown);
this.renderer.domElement.addEventListener('pointermove', this.onPointerMove);
this.renderer.domElement.addEventListener('pointerup', this.onPointerUp);
window.addEventListener("blur", this.cleanup.bind(this));
}
dispose() {
this.enabled = false;
this.isDown = false;
this.cleanup();
this.renderer.domElement.removeEventListener("pointerdown", this.onPointerDown);
this.renderer.domElement.removeEventListener("pointermove", this.onPointerMove);
this.renderer.domElement.removeEventListener("pointerup", this.onPointerUp);
window.removeEventListener("blur", this.cleanup);
}
private cleanup() {
this.isDown = false;
this.element.style.display = 'none';
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
onPointerDown(event: PointerEvent) {
if (!this.enabled || !event.ctrlKey || event.button !== 0) return;
this.isDown = true;
this.onSelectStart(event);
}
onPointerMove(event: PointerEvent) {
if (!this.enabled || !this.isDown || !event.ctrlKey) return;
this.onSelectMove(event);
}
onPointerUp() {
if (!this.enabled) return;
this.isDown = false;
this.onSelectOver();
}
onSelectStart(event: PointerEvent) {
this.element.style.display = 'none';
this.renderer.domElement.parentElement?.appendChild(this.element);
this.element.style.left = `${event.clientX}px`;
this.element.style.top = `${event.clientY}px`;
this.element.style.width = '0px';
this.element.style.height = '0px';
this.startPoint.x = event.clientX;
this.startPoint.y = event.clientY;
}
onSelectMove(event: PointerEvent) {
if (!this.isDown) return;
this.element.style.display = 'block';
this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX);
this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY);
this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX);
this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY);
this.element.style.left = `${this.pointTopLeft.x}px`;
this.element.style.top = `${this.pointTopLeft.y}px`;
this.element.style.width = `${this.pointBottomRight.x - this.pointTopLeft.x}px`;
this.element.style.height = `${this.pointBottomRight.y - this.pointTopLeft.y}px`;
}
onSelectOver() {
this.element.style.display = 'none';
if (this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
}
}
export { SelectionHelper };

View File

@@ -0,0 +1,120 @@
import { TransformControls } from "@react-three/drei";
import * as THREE from "three";
import { useselectedFloorItem, useObjectPosition, useObjectScale, useObjectRotation, useTransformMode, useFloorItems, useSocketStore, useActiveTool } from "../../../store/store";
import { useThree } from "@react-three/fiber";
import * as Types from '../../../types/world/worldTypes';
import { useEffect } from "react";
export default function TransformControl() {
const state = useThree();
const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem();
const { objectPosition, setObjectPosition } = useObjectPosition();
const { objectScale, setObjectScale } = useObjectScale();
const { objectRotation, setObjectRotation } = useObjectRotation();
const { transformMode, setTransformMode } = useTransformMode();
const { floorItems, setFloorItems } = useFloorItems();
const { activeTool, setActiveTool } = useActiveTool();
const { socket } = useSocketStore();
function handleObjectChange() {
if (selectedFloorItem && transformMode) {
setObjectPosition(selectedFloorItem.position);
setObjectScale(selectedFloorItem.scale);
setObjectRotation({
x: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.x),
y: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.y),
z: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.z),
});
}
}
function handleMouseUp() {
if (selectedFloorItem) {
setObjectPosition(selectedFloorItem.position);
setObjectScale(selectedFloorItem.scale);
setObjectRotation({
x: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.x),
y: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.y),
z: THREE.MathUtils.radToDeg(selectedFloorItem.rotation.z),
});
}
setFloorItems((prevItems: Types.FloorItems) => {
if (!prevItems) {
return
}
let updatedItem: any = null;
const updatedItems = prevItems.map((item) => {
if (item.modeluuid === selectedFloorItem?.uuid) {
updatedItem = {
...item,
position: [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z,] as [number, number, number],
rotation: { x: selectedFloorItem.rotation.x, y: selectedFloorItem.rotation.y, z: selectedFloorItem.rotation.z, },
};
return updatedItem;
}
return item;
});
if (updatedItem && selectedFloorItem) {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
//REST
// setFloorItemApi(
// organization,
// updatedItem.modeluuid,
// updatedItem.modelname,
// [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z,],
// { "x": selectedFloorItem.rotation.x, "y": selectedFloorItem.rotation.y, "z": selectedFloorItem.rotation.z },
// false,
// true,
// );
//SOCKET
const data = {
organization: organization,
modeluuid: updatedItem.modeluuid,
modelname: updatedItem.modelname,
position: [selectedFloorItem.position.x, selectedFloorItem.position.y, selectedFloorItem.position.z],
rotation: { "x": selectedFloorItem.rotation.x, "y": selectedFloorItem.rotation.y, "z": selectedFloorItem.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id
}
socket.emit('v1:FloorItems:set', data);
}
localStorage.setItem("FloorItems", JSON.stringify(updatedItems));
return updatedItems;
});
}
useEffect(() => {
if (activeTool === "Add pillar" || activeTool === "Delete") {
if (state.controls) {
const target = (state.controls as any).getTarget(new THREE.Vector3());
(state.controls as any).setTarget(target.x, 0, target.z, true);
}
setselectedFloorItem(null);
{
setObjectPosition({ x: undefined, y: undefined, z: undefined });
setObjectScale({ x: undefined, y: undefined, z: undefined });
setObjectRotation({ x: undefined, y: undefined, z: undefined });
}
}
}, [activeTool]);
return (
<>
{(selectedFloorItem && transformMode) &&
<TransformControls
object={selectedFloorItem}
mode={transformMode}
onObjectChange={handleObjectChange}
onMouseUp={handleMouseUp}
/>
}
</>
);
}

View File

@@ -0,0 +1,22 @@
import * as THREE from 'three';
import { useToggleView } from '../../../store/store';
import * as CONSTANTS from '../../../types/world/worldConstants';
const Ground = ({ grid, plane }: any) => {
const { toggleView, setToggleView } = useToggleView();
return (
<mesh name="Ground">
<mesh ref={grid} name="Grid" position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}>
<gridHelper args={[CONSTANTS.gridConfig.size, CONSTANTS.gridConfig.divisions, CONSTANTS.gridConfig.primaryColor, CONSTANTS.gridConfig.secondaryColor]} />
</mesh>
<mesh ref={plane} rotation-x={CONSTANTS.planeConfig.rotation} position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D} name="Plane" receiveShadow>
<planeGeometry args={[CONSTANTS.planeConfig.width, CONSTANTS.planeConfig.height]} />
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
</mesh>
</mesh>
)
}
export default Ground;

View File

@@ -0,0 +1,84 @@
import { useRef, useEffect} from 'react';
import { useThree } from '@react-three/fiber';
import * as THREE from 'three';
import { useAzimuth, useElevation, useShadows, useSunPosition, useFloorItems, useWallItems } from '../../../store/store';
import * as CONSTANTS from '../../../types/world/worldConstants';
const shadowWorker = new Worker(new URL('../../../services/factoryBuilder/webWorkers/shadowWorker', import.meta.url));
export default function Shadows() {
const { shadows, setShadows } = useShadows();
const { sunPosition, setSunPosition } = useSunPosition();
const lightRef = useRef<THREE.DirectionalLight | null>(null);
const targetRef = useRef<THREE.Object3D | null>(null);
const { controls, gl } = useThree();
const { elevation, setElevation } = useElevation();
const { azimuth, setAzimuth } = useAzimuth();
const { floorItems } = useFloorItems();
const { wallItems } = useWallItems();
useEffect(() => {
gl.shadowMap.enabled = true;
gl.shadowMap.type = THREE.PCFShadowMap;
}, [gl, floorItems, wallItems]);
useEffect(() => {
if (lightRef.current && targetRef.current) {
lightRef.current.target = targetRef.current;
}
}, []);
useEffect(() => {
shadowWorker.onmessage = (event) => {
const { lightPosition, controlsTarget } = event.data;
if (lightRef.current && targetRef.current && controls) {
lightRef.current.position.copy(lightPosition);
targetRef.current.position.copy(controlsTarget);
}
};
}, [shadowWorker, controls]);
const updateShadows = () => {
if (controls && shadowWorker) {
const offsetDistance = CONSTANTS.shadowConfig.shadowOffset;
const controlsTarget = (controls as any).getTarget();
shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance });
}
};
useEffect(() => {
if (controls && shadows) {
updateShadows();
(controls as any).addEventListener('update', updateShadows);
return () => {
(controls as any).removeEventListener('update', updateShadows);
};
}
}, [controls, elevation, azimuth, shadows]);
return (
<>
{/* {(lightRef.current?.shadow) &&
<cameraHelper visible={shadows} args={[lightRef.current.shadow.camera]} />
} */}
<directionalLight
ref={lightRef}
castShadow={shadows}
shadow-mapSize-width={CONSTANTS.shadowConfig.shadowmapSizewidth}
shadow-mapSize-height={CONSTANTS.shadowConfig.shadowmapSizeheight}
shadow-camera-far={CONSTANTS.shadowConfig.shadowcamerafar}
shadow-camera-near={CONSTANTS.shadowConfig.shadowcameranear}
shadow-camera-top={CONSTANTS.shadowConfig.shadowcameratop}
shadow-camera-bottom={CONSTANTS.shadowConfig.shadowcamerabottom}
shadow-camera-left={CONSTANTS.shadowConfig.shadowcameraleft}
shadow-camera-right={CONSTANTS.shadowConfig.shadowcameraright}
shadow-bias={CONSTANTS.shadowConfig.shadowbias}
shadow-normalBias={CONSTANTS.shadowConfig.shadownormalBias}
/>
<object3D ref={targetRef} />
<mesh position={CONSTANTS.shadowConfig.shadowMaterialPosition} rotation={CONSTANTS.shadowConfig.shadowMaterialRotation} receiveShadow>
<planeGeometry args={[CONSTANTS.planeConfig.width, CONSTANTS.planeConfig.height]} />
<shadowMaterial opacity={CONSTANTS.shadowConfig.shadowMaterialOpacity} transparent />
</mesh>
</>
);
}

View File

@@ -0,0 +1,49 @@
import * as THREE from 'three';
import { Sky } from "@react-three/drei";
import { useAzimuth, useElevation, useSunPosition } from "../../../store/store";
import { useEffect, useRef, useState } from "react";
import * as CONSTANTS from '../../../types/world/worldConstants';
export default function Sun() {
const { elevation, setElevation } = useElevation();
const { sunPosition, setSunPosition } = useSunPosition();
const { azimuth, setAzimuth } = useAzimuth();
const [turbidity, setTurbidity] = useState(CONSTANTS.skyConfig.defaultTurbidity);
const sunPositionRef = useRef(new THREE.Vector3(0, 0, 0));
const [_, forceUpdate] = useState(0);
const maxTurbidity = CONSTANTS.skyConfig.maxTurbidity;
const minTurbidity = CONSTANTS.skyConfig.minTurbidity;
useEffect(() => {
const phi = THREE.MathUtils.degToRad(90 - elevation);
const theta = THREE.MathUtils.degToRad(azimuth);
const computedTurbidity = minTurbidity + ((elevation - 2) / (90 - 2)) * (maxTurbidity - minTurbidity);
setTurbidity(computedTurbidity);
sunPositionRef.current.setFromSphericalCoords(1, phi, theta);
setSunPosition(sunPositionRef.current);
forceUpdate(prev => prev + 1);
}, [elevation, azimuth]);
return (
<>
{(azimuth !== undefined && elevation !== undefined) && (
<>
<Sky
distance={CONSTANTS.skyConfig.skyDistance}
sunPosition={[
sunPositionRef.current.x,
sunPositionRef.current.y,
sunPositionRef.current.z,
]}
turbidity={turbidity}
rayleigh={CONSTANTS.skyConfig.defaultRayleigh}
mieCoefficient={CONSTANTS.skyConfig.mieCoefficient}
mieDirectionalG={CONSTANTS.skyConfig.mieDirectionalG}
/>
</>
)}
</>
);
}

View File

@@ -0,0 +1,109 @@
import { Html } from "@react-three/drei";
import * as THREE from "three";
import * as Types from "../../../types/world/worldTypes";
import { useDrieTemp, useDrieUIValue } from "../../../store/store"
import UI from "./ui";
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
export default function DrieHtmlTemp({ itemsGroup }: { itemsGroup: Types.RefGroup }) {
const { drieTemp, setDrieTemp } = useDrieTemp();
const { drieUIValue, setDrieUIValue } = useDrieUIValue();
const state = useThree();
const { camera, raycaster } = state;
useEffect(() => {
const canvasElement = state.gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
};
const onMouseUp = (evt: any) => {
if (evt.button === 0) {
isLeftMouseDown = false;
if (drag) return;
if (!itemsGroup.current) return
let intersects = raycaster.intersectObjects(itemsGroup.current.children, true);
if (intersects.length > 0) {
let currentObject = intersects[0].object;
while (currentObject) {
if (currentObject.name === "Scene") {
break;
}
currentObject = currentObject.parent as THREE.Object3D;
}
if (currentObject && (currentObject.userData.name === "SV2 Controll pannel" || currentObject.userData.name === "forklift")) {
const worldPos = new THREE.Vector3();
currentObject.getWorldPosition(worldPos);
const rightOffset = new THREE.Vector3(1, 0, 0);
const upOffset = new THREE.Vector3(0, 1, 0);
currentObject.localToWorld(rightOffset);
currentObject.localToWorld(upOffset);
const finalPosition = worldPos.clone().addScaledVector(rightOffset.sub(currentObject.position).normalize(), 2.5).addScaledVector(upOffset.sub(currentObject.position).normalize(), 2.3);
setDrieTemp(finalPosition);
} else {
setDrieTemp(undefined);
}
}
else {
setDrieTemp(undefined);
}
}
};
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
};
}, [])
return (
<>
{drieTemp &&
<mesh position={[drieTemp.x, drieTemp.y, drieTemp.z]}>
<Html
as="div"
center
zIndexRange={[1, 0]}
transform
sprite
style={{
padding: "10px",
color: "white",
borderRadius: "8px",
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
scale={[0.3, 0.3, 0.3]}
// occlude
>
<UI temperature={drieUIValue.temperature} humidity={drieUIValue.humidity} touch={drieUIValue.touch} header={""} />
</Html>
</mesh>
}
</>
)
}

View File

@@ -0,0 +1,141 @@
export default function UI({ temperature, humidity, touch, header }) {
return (
<div
className="temp-visualization-wrapper"
style={{
padding: "24px",
width: "fit-content",
background: "white",
borderRadius: "20px",
color: "#282829",
// transform: "translate(0, -100%)"
}}
>
<div
className="header"
style={{ paddingBottom: "22px", fontWeight: "600" }}
>
{header ? header : "Sensor Details"}
</div>
<div className="container-1" style={{ display: "flex", gap: "24px" }}>
<div
className="temperature-container"
style={{
padding: "12px",
borderRadius: "12px",
background: "white",
boxShadow: "7px 7px 14px #e3e3e3, -7px -7px 14px #f4f4f4",
display: "flex",
gap: "6px",
flexDirection: "column",
width: "92px",
}}
>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "12px" }}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.73109 11.6758L9 11.5357V11.2324V4C9 2.61929 10.1193 1.5 11.5 1.5C12.8807 1.5 14 2.61929 14 4V11.2324V11.5357L14.2689 11.6758C16.1901 12.6771 17.5 14.6861 17.5 17.0002C17.5 20.3139 14.8137 23.0002 11.5 23.0002C8.18629 23.0002 5.5 20.3139 5.5 17.0002C5.5 14.6861 6.80994 12.6771 8.73109 11.6758Z"
stroke="#FE4519"
/>
<path d="M11.5 7V16" stroke="#FE4519" strokeLinecap="round" />
<circle cx="11.5" cy="17" r="3" fill="#FE4519" />
</svg>
</div>
<div className="key" style={{ fontSize: "12px" }}>
Temperature
</div>
<div
className="value"
style={{ fontSize: "18px", fontWeight: "600" }}
>
{temperature}
</div>
</div>
<div
className="humidity-container"
style={{
padding: "12px",
borderRadius: "12px",
background: "white",
boxShadow: "7px 7px 14px #e3e3e3, -7px -7px 14px #f4f4f4",
display: "flex",
gap: "6px",
flexDirection: "column",
width: "92px",
}}
>
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "12px" }}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.8041 19.1765C15.2714 17.4843 14.6826 15.891 12.6962 13.7257C12.3217 13.3175 11.6786 13.3192 11.305 13.7284C9.1738 16.0628 8.77326 17.5784 9.16555 19.0737C9.32805 19.6931 9.79837 20.1765 10.3593 20.4854C11.742 21.2468 12.2655 21.3361 13.7514 20.4639C14.2463 20.1734 14.6514 19.7296 14.8041 19.1765Z"
stroke="#0F96F5"
strokeWidth="1.5"
/>
<path
d="M20.8104 9.0293C21.2043 7.39129 20.5932 5.82808 18.6645 3.72574C18.2899 3.31747 17.6469 3.3192 17.2733 3.72838C15.1959 6.00386 14.7629 7.50129 15.1056 8.96027C15.2684 9.65314 15.8159 10.18 16.4679 10.4655C17.7279 11.0173 18.291 11.0385 19.5446 10.4598C20.1511 10.1799 20.6542 9.6787 20.8104 9.0293Z"
stroke="#0F96F5"
strokeWidth="1.5"
/>
<path
d="M8.81041 9.0293C9.20431 7.39129 8.59319 5.82808 6.66448 3.72574C6.28992 3.31747 5.64687 3.3192 5.27331 3.72838C3.19591 6.00386 2.76287 7.50129 3.1056 8.96027C3.26837 9.65314 3.81593 10.18 4.46789 10.4655C5.72785 11.0173 6.29105 11.0385 7.54464 10.4598C8.15106 10.1799 8.65424 9.6787 8.81041 9.0293Z"
stroke="#0F96F5"
strokeWidth="1.5"
/>
</svg>
</div>
<div className="key" style={{ fontSize: "12px" }}>
Humidity
</div>
<div
className="value"
style={{ fontSize: "18px", fontWeight: "600" }}
>
{humidity}
</div>
</div>
</div>
<div className="container-2">
<div
className="touch-container"
style={{
display: "flex",
borderRadius: "12px",
background: "white",
boxShadow: "7px 7px 14px #e3e3e3, -7px -7px 14px #f4f4f4",
padding: "16px",
marginTop: "16px",
gap: "18px",
alignItems: "center",
fontWeight: "600",
}}
>
<div className="key" style={{ fontSize: "14px" }}>
Touch Sensor
</div>
<div
className="value"
style={
touch === "True"
? { color: "#2AA553", fontWeight: 500 }
: { color: "#FE4519", fontWeight: 500 }
}
>
{touch === "True" ? "Active" : "In active"}
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,111 @@
import * as THREE from 'three'
import { EffectComposer, N8AO, Outline } from '@react-three/postprocessing'
import { BlendFunction } from 'postprocessing'
import { useDeletableFloorItem, useSelectedEventSphere, useSelectedPath, useSelectedWallItem, useselectedFloorItem } from '../../../store/store';
import * as Types from '../../../types/world/worldTypes'
import * as CONSTANTS from '../../../types/world/worldConstants';
export default function PostProcessing() {
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
const { selectedFloorItem, setselectedFloorItem } = useselectedFloorItem();
const { selectedEventSphere } = useSelectedEventSphere();
const { selectedPath } = useSelectedPath();
function flattenChildren(children: any[]) {
const allChildren: any[] = [];
children.forEach(child => {
allChildren.push(child);
if (child.children && child.children.length > 0) {
allChildren.push(...flattenChildren(child.children));
}
});
return allChildren;
}
return (
<>
<EffectComposer autoClear={false}>
<N8AO color="black" aoRadius={20} intensity={7} distanceFalloff={4} aoSamples={32} denoiseRadius={6} denoiseSamples={16} />
{deletableFloorItem &&
<Outline
selection={flattenChildren(deletableFloorItem.children)}
selectionLayer={10}
width={3000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor}
blur={true}
xRay={true}
/>
}
{selectedWallItem &&
<Outline
selection={
selectedWallItem.children[1].children[0].children.filter(
(child: Types.Mesh) => child.name !== "CSG_REF"
)
}
selectionLayer={10}
width={3000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
blur={true}
xRay={true}
/>}
{selectedFloorItem &&
<Outline
selection={flattenChildren(selectedFloorItem.children)}
selectionLayer={10}
width={3000}
blendFunction={BlendFunction.ALPHA}
edgeStrength={5}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
blur={true}
xRay={true}
/>
}
{selectedEventSphere &&
<Outline
selection={[selectedEventSphere.point]}
selectionLayer={10}
width={750}
blendFunction={BlendFunction.ALPHA}
edgeStrength={15}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
hiddenEdgeColor={CONSTANTS.outlineConfig.assetSelectColor}
blur={true}
xRay={true}
/>
}
{selectedPath &&
<Outline
selection={flattenChildren(selectedPath.group.children)}
selectionLayer={10}
width={750}
blendFunction={BlendFunction.ALPHA}
edgeStrength={15}
resolutionScale={2}
pulseSpeed={0}
visibleEdgeColor={0x6f42c1}
hiddenEdgeColor={0x6f42c1}
blur={true}
xRay={true}
/>
}
</EffectComposer>
</>
)
}

View File

@@ -0,0 +1,56 @@
import { useMemo } from "react";
import { Canvas } from "@react-three/fiber";
import { Environment, KeyboardControls } from "@react-three/drei";
import World from "./world/world";
import Controls from "./controls/controls";
import TransformControl from "./controls/transformControls";
import PostProcessing from "./postProcessing/postProcessing"
import Sun from "./environment/sky";
import CamModelsGroup from "../collaboration/collabCams";
import Shadows from "./environment/shadow";
import MqttEvents from "../../services/factoryBuilder/mqtt/mqttEvents";
import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
import SelectionControls from "./controls/selection/selectionControls";
import MeasurementTool from "./tools/measurementTool";
import Simulation from "../simulation/simulation";
// import Simulation from "./simulationtemp/simulation";
export default function Scene() {
const map = useMemo(() => [
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
{ name: "right", keys: ["ArrowRight", "d", "D"] },
// { name: "jump", keys: ["Space"] },
], [])
return (
<KeyboardControls map={map}>
<Canvas
style={{ width: "100vw", height: "100vh" }}
eventPrefix="client"
gl={{ powerPreference: "high-performance", antialias: true }}
onContextMenu={(e) => {
e.preventDefault();
}}
>
<Controls />
<TransformControl />
<SelectionControls />
<MeasurementTool />
<World />
{/* <Simulation /> */}
<Simulation />
<PostProcessing />
<Sun />
<Shadows />
<CamModelsGroup />
<MqttEvents />
<Environment files={background} environmentIntensity={1.5} />
</Canvas>
</KeyboardControls>
);
}

View File

@@ -0,0 +1,190 @@
import * as THREE from 'three';
import { useEffect, useRef, useState } from 'react';
import { useThree, useFrame } from '@react-three/fiber';
import { useToolMode } from '../../../store/store';
import { Html } from '@react-three/drei';
const MeasurementTool = () => {
const { gl, raycaster, pointer, camera, scene } = useThree();
const { toolMode } = useToolMode();
const [points, setPoints] = useState<THREE.Vector3[]>([]);
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(null);
const groupRef = useRef<THREE.Group>(null);
const [startConePosition, setStartConePosition] = useState<THREE.Vector3 | null>(null);
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(null);
const [startConeQuaternion, setStartConeQuaternion] = useState(new THREE.Quaternion());
const [endConeQuaternion, setEndConeQuaternion] = useState(new THREE.Quaternion());
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = () => {
isLeftMouseDown = true;
drag = false;
};
const onMouseUp = (evt: any) => {
isLeftMouseDown = false;
if (evt.button === 0 && !drag) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !(intersect.object.type === "GridHelper"));
if (intersects.length > 0) {
const intersectionPoint = intersects[0].point.clone();
if (points.length < 2) {
setPoints([...points, intersectionPoint]);
} else {
setPoints([intersectionPoint]);
}
}
}
};
const onMouseMove = () => {
if (isLeftMouseDown) drag = true;
};
const onContextMenu = (evt: any) => {
evt.preventDefault();
if (!drag) {
evt.preventDefault();
setPoints([]);
setTubeGeometry(null);
}
};
if (toolMode === "MeasurementScale") {
canvasElement.addEventListener("pointerdown", onMouseDown);
canvasElement.addEventListener("pointermove", onMouseMove);
canvasElement.addEventListener("pointerup", onMouseUp);
canvasElement.addEventListener("contextmenu", onContextMenu);
} else {
resetMeasurement();
setPoints([]);
}
return () => {
canvasElement.removeEventListener("pointerdown", onMouseDown);
canvasElement.removeEventListener("pointermove", onMouseMove);
canvasElement.removeEventListener("pointerup", onMouseUp);
canvasElement.removeEventListener("contextmenu", onContextMenu);
};
}, [toolMode, camera, raycaster, pointer, scene, points]);
useFrame(() => {
if (points.length === 1) {
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true).filter(intersect => !intersect.object.name.includes("Roof") && !intersect.object.name.includes("MeasurementReference") && !(intersect.object.type === "GridHelper"));
if (intersects.length > 0) {
updateMeasurement(points[0], intersects[0].point);
}
} else if (points.length === 2) {
updateMeasurement(points[0], points[1]);
} else {
resetMeasurement();
}
});
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
const distance = start.distanceTo(end);
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
setConeSize({ radius: coneRadius, height: coneHeight });
const direction = new THREE.Vector3().subVectors(end, start).normalize();
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
let tubeStart = start.clone().add(offset);
let tubeEnd = end.clone().sub(offset);
tubeStart.y = Math.max(tubeStart.y, 0);
tubeEnd.y = Math.max(tubeEnd.y, 0);
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
setStartConePosition(tubeStart);
setEndConePosition(tubeEnd);
setStartConeQuaternion(getArrowOrientation(start, end));
setEndConeQuaternion(getArrowOrientation(end, start));
};
const resetMeasurement = () => {
setTubeGeometry(null);
setStartConePosition(null);
setEndConePosition(null);
};
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
const direction = new THREE.Vector3().subVectors(end, start).normalize().negate();
const quaternion = new THREE.Quaternion();
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
return quaternion;
};
useEffect(() => {
if (points.length === 2) {
console.log(points[0].distanceTo(points[1]));
}
}, [points])
return (
<group ref={groupRef} name="MeasurementGroup">
{startConePosition && (
<mesh name='MeasurementReference' position={startConePosition} quaternion={startConeQuaternion}>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{endConePosition && (
<mesh name='MeasurementReference' position={endConePosition} quaternion={endConeQuaternion}>
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
<meshBasicMaterial color="yellow" />
</mesh>
)}
{tubeGeometry && (
<mesh name='MeasurementReference' geometry={tubeGeometry}>
<meshBasicMaterial color="yellow" />
</mesh>
)}
{startConePosition && endConePosition && (
<Html
as="div"
center
zIndexRange={[1, 0]}
style={{
padding: "10px",
color: "white",
borderRadius: "8px",
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
transform
sprite
scale={THREE.MathUtils.clamp(startConePosition.distanceTo(endConePosition) * 0.25, 0, 10)}
position={[(startConePosition.x + endConePosition.x) / 2, (startConePosition.y + endConePosition.y) / 2, (startConePosition.z + endConePosition.z) / 2]}
>
<div style={{ color: "black" }} >{startConePosition.distanceTo(endConePosition).toFixed(2)} m</div>
</Html>
)}
</group>
);
};
export default MeasurementTool;

View File

@@ -0,0 +1,336 @@
////////// Three and React Three Fiber Imports //////////
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";
////////// Component Imports //////////
import DistanceText from "../../builder/geomentries/lines/distanceText";
import ReferenceDistanceText from "../../builder/geomentries/lines/referenceDistanceText";
////////// Assests Imports //////////
import arch from "../../../assets/gltf-glb/arch.glb";
import door from "../../../assets/gltf-glb/door.glb";
import Window from "../../../assets/gltf-glb/window.glb";
////////// Zustand State Imports //////////
import {
useToggleView,
useDeletePointOrLine,
useMovePoint,
useActiveLayer,
useSocketStore,
useWallVisibility,
useRoofVisibility,
useShadows,
useUpdateScene,
useWalls,
useToolMode
} from "../../../store/store";
////////// 3D Function Imports //////////
import loadWalls from "../../builder/geomentries/walls/loadWalls";
import * as Types from "../../../types/world/worldTypes";
import SocketResponses from "../../collaboration/socketResponses.dev";
import FloorItemsGroup from "../../builder/groups/floorItemsGroup";
import FloorPlanGroup from "../../builder/groups/floorPlanGroup";
import FloorGroup from "../../builder/groups/floorGroup";
import FloorGroupAilse from "../../builder/groups/floorGroupAisle";
import Draw from "../../builder/functions/draw";
import WallsAndWallItems from "../../builder/groups/wallsAndWallItems";
import Ground from "../environment/ground";
// import ZoneGroup from "../groups/zoneGroup1";
import { findEnvironment } from "../../../services/factoryBuilder/environment/findEnvironment";
import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility";
import DrieHtmlTemp from "../mqttTemp/drieHtmlTemp";
import ZoneGroup from "../../builder/groups/zoneGroup";
export default function World() {
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
const csg = useRef(); // Reference for CSG object, used for 3D modeling.
const CSGGroup = useRef() as Types.RefMesh; // Reference to a group of CSG objects.
const scene = useRef() as Types.RefScene; // Reference to the scene.
const camera = useRef() as Types.RefCamera; // Reference to the camera object.
const controls = useRef<any>(); // Reference to the controls object.
const raycaster = useRef() as Types.RefRaycaster; // Reference for raycaster used for detecting objects being pointed at in the scene.
const dragPointControls = useRef() as Types.RefDragControl; // Reference for drag point controls, an array for drag control.
// Assigning the scene and camera from the Three.js state to the references.
scene.current = state.scene;
camera.current = state.camera;
controls.current = state.controls;
raycaster.current = state.raycaster;
const plane = useRef<THREE.Mesh>(null); // Reference for a plane object for raycaster reference.
const grid = useRef() as any; // Reference for a grid object for raycaster reference.
const snappedPoint = useRef() as Types.RefVector3; // Reference for storing a snapped point at the (end = isSnapped) and (start = ispreSnapped) of the line.
const isSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (end).
const anglesnappedPoint = useRef() as Types.RefVector3; // Reference for storing an angle-snapped point when the line is in 90 degree etc...
const isAngleSnapped = useRef(false) as Types.RefBoolean; // Boolean to indicate if angle snapping is active.
const isSnappedUUID = useRef() as Types.RefString; // UUID reference to identify the snapped point.
const ispreSnapped = useRef(false) as Types.RefBoolean; // Boolean reference to indicate if an object is snapped at the (start).
const tempLoader = useRef() as Types.RefMesh; // Reference for a temporary loader for the floor items.
const isTempLoader = useRef() as Types.RefBoolean; // Reference to check if a temporary loader is active.
const Tube = useRef() as Types.RefTubeGeometry; // Reference for tubes used for reference line creation and updation.
const line = useRef([]) as Types.RefLine; // Reference for line which stores the current line that is being drawn.
const lines = useRef([]) as Types.RefLines; // Reference for lines which stores all the lines that are ever drawn.
const onlyFloorline = useRef<Types.OnlyFloorLine>([]); // Reference for floor lines which does not have walls or roof and have only floor used to store the current line that is being drawn.
const onlyFloorlines = useRef<Types.OnlyFloorLines>([]); // Reference for all the floor lines that are ever drawn.
const ReferenceLineMesh = useRef() as Types.RefMesh; // Reference for storing the mesh of the reference line for moving it during draw.
const LineCreated = useRef(false) as Types.RefBoolean; // Boolean to track whether the reference line is created or not.
const referencePole = useRef() as Types.RefMesh; // Reference for a pole that is used as the reference for the user to show where it is placed.
const itemsGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the floor items (Gltf).
const floorGroup = useRef() as Types.RefGroup; // Reference to the THREE.Group that has the roofs and the floors.
const AttachedObject = useRef() as Types.RefMesh; // Reference for an object that is attached using dbl click for transform controls rotation.
const floorPlanGroup = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines group and the points group.
const floorPlanGroupLine = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the lines that are drawn.
const floorPlanGroupPoint = useRef() as Types.RefGroup; // Reference for a THREE.Group that has the points that are created.
const floorGroupAisle = useRef() as Types.RefGroup;
const zoneGroup = useRef() as Types.RefGroup;
const currentLayerPoint = useRef([]) as Types.RefMeshArray; // Reference for points that re in the current layer used to update the points in drag controls.
const hoveredDeletablePoint = useRef() as Types.RefMesh; // Reference for the currently hovered point that can be deleted.
const hoveredDeletableLine = useRef() as Types.RefMesh; // Reference for the currently hovered line that can be deleted.
const hoveredDeletableFloorItem = useRef() as Types.RefMesh; // Reference for the currently hovered floor item that can be deleted.
const hoveredDeletableWallItem = useRef() as Types.RefMesh; // Reference for the currently hovered wall item that can be deleted.
const hoveredDeletablePillar = useRef() as Types.RefMesh; // Reference for the currently hovered pillar that can be deleted.
const currentWallItem = useRef() as Types.RefMesh; // Reference for the currently selected wall item that can be scaled, dragged etc...
const cursorPosition = new THREE.Vector3(); // 3D vector for storing the cursor position.
const [selectedItemsIndex, setSelectedItemsIndex] = useState<Types.Number | null>(null); // State for tracking the index of the selected item.
const { activeLayer, setActiveLayer } = useActiveLayer(); // State that changes based on which layer the user chooses in Layers.jsx.
const { toggleView, setToggleView } = useToggleView(); // State for toggling between 2D and 3D.
const { toolMode, setToolMode } = useToolMode();
const { movePoint, setMovePoint } = useMovePoint(); // State that stores a boolean which represents whether the move mode is active or not.
const { deletePointOrLine, setDeletePointOrLine } = useDeletePointOrLine();
const { socket } = useSocketStore();
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
const { wallVisibility, setWallVisibility } = useWallVisibility();
const { shadows, setShadows } = useShadows();
const { updateScene, setUpdateScene } = useUpdateScene();
const { walls, setWalls } = useWalls();
const [RefTextupdate, setRefTextUpdate] = useState(-1000);
// const loader = new GLTFLoader();
// const dracoLoader = new DRACOLoader();
// dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
// loader.setDRACOLoader(dracoLoader);
////////// Assest Configuration Values //////////
const AssetConfigurations: Types.AssetConfigurations = {
arch: {
modelUrl: arch,
scale: [0.75, 0.75, 0.75],
csgscale: [2, 4, 0.5],
csgposition: [0, 2, 0],
positionY: () => 0,
type: "Fixed-Move",
},
door: {
modelUrl: door,
scale: [0.75, 0.75, 0.75],
csgscale: [2, 4, 0.5],
csgposition: [0, 2, 0],
positionY: () => 0,
type: "Fixed-Move",
},
window: {
modelUrl: Window,
scale: [0.75, 0.75, 0.75],
csgscale: [5, 3, 0.5],
csgposition: [0, 1.5, 0],
positionY: (intersectionPoint) => intersectionPoint.point.y,
type: "Free-Move",
},
};
////////// All Toggle's //////////
useEffect(() => {
setRefTextUpdate((prevUpdate) => prevUpdate - 1);
if (dragPointControls.current) {
dragPointControls.current.enabled = false;
}
if (toggleView) {
Layer2DVisibility(activeLayer, floorPlanGroup, floorPlanGroupLine, floorPlanGroupPoint, currentLayerPoint, dragPointControls);
} else {
setToolMode(null);
setDeletePointOrLine(false);
setMovePoint(false);
loadWalls(lines, setWalls);
setUpdateScene(true);
line.current = [];
}
}, [toggleView]);
useEffect(() => {
THREE.Cache.clear();
THREE.Cache.enabled = true;
}, []);
useEffect(() => {
const email = localStorage.getItem('email')
const organization = (email!.split("@")[1]).split(".")[0];
async function fetchVisibility() {
const visibility = await findEnvironment(organization, localStorage.getItem('userId')!);
if (visibility) {
setRoofVisibility(visibility.roofVisibility);
setWallVisibility(visibility.wallVisibility);
setShadows(visibility.shadowVisibility);
}
}
fetchVisibility();
}, [])
////////// UseFrame is Here //////////
useFrame(() => {
if (toolMode) {
Draw(state, plane, cursorPosition, floorPlanGroupPoint, floorPlanGroupLine, snappedPoint, isSnapped, isSnappedUUID, line, lines, ispreSnapped, floorPlanGroup, ReferenceLineMesh, LineCreated, setRefTextUpdate, Tube, anglesnappedPoint, isAngleSnapped, toolMode)
}
});
////////// Return //////////
return (
<>
<Ground grid={grid} plane={plane} />
<DistanceText key={toggleView} />
<ReferenceDistanceText
key={RefTextupdate}
line={ReferenceLineMesh.current}
/>
<SocketResponses
floorPlanGroup={floorPlanGroup}
lines={lines}
floorGroup={floorGroup}
floorGroupAisle={floorGroupAisle}
scene={scene}
onlyFloorlines={onlyFloorlines}
AssetConfigurations={AssetConfigurations}
itemsGroup={itemsGroup}
isTempLoader={isTempLoader}
tempLoader={tempLoader}
currentLayerPoint={currentLayerPoint}
floorPlanGroupPoint={floorPlanGroupPoint}
floorPlanGroupLine={floorPlanGroupLine}
zoneGroup={zoneGroup}
dragPointControls={dragPointControls}
/>
<WallsAndWallItems
CSGGroup={CSGGroup}
AssetConfigurations={AssetConfigurations}
setSelectedItemsIndex={setSelectedItemsIndex}
selectedItemsIndex={selectedItemsIndex}
currentWallItem={currentWallItem}
csg={csg}
lines={lines}
hoveredDeletableWallItem={hoveredDeletableWallItem}
/>
<FloorItemsGroup
itemsGroup={itemsGroup}
hoveredDeletableFloorItem={hoveredDeletableFloorItem}
AttachedObject={AttachedObject}
floorGroup={floorGroup}
tempLoader={tempLoader}
isTempLoader={isTempLoader}
plane={plane}
/>
<FloorGroup
floorGroup={floorGroup}
lines={lines}
referencePole={referencePole}
hoveredDeletablePillar={hoveredDeletablePillar}
/>
<FloorPlanGroup
floorPlanGroup={floorPlanGroup}
floorPlanGroupLine={floorPlanGroupLine}
floorPlanGroupPoint={floorPlanGroupPoint}
floorGroup={floorGroup}
currentLayerPoint={currentLayerPoint}
dragPointControls={dragPointControls}
hoveredDeletablePoint={hoveredDeletablePoint}
hoveredDeletableLine={hoveredDeletableLine}
plane={plane}
line={line}
lines={lines}
onlyFloorline={onlyFloorline}
onlyFloorlines={onlyFloorlines}
ReferenceLineMesh={ReferenceLineMesh}
LineCreated={LineCreated}
isSnapped={isSnapped}
ispreSnapped={ispreSnapped}
snappedPoint={snappedPoint}
isSnappedUUID={isSnappedUUID}
isAngleSnapped={isAngleSnapped}
anglesnappedPoint={anglesnappedPoint}
/>
{/* <ZoneGroup
zoneGroup={zoneGroup}
plane={plane}
floorPlanGroupLine={floorPlanGroupLine}
floorPlanGroupPoint={floorPlanGroupPoint}
line={line}
lines={lines}
currentLayerPoint={currentLayerPoint}
dragPointControls={dragPointControls}
floorPlanGroup={floorPlanGroup}
ReferenceLineMesh={ReferenceLineMesh}
LineCreated={LineCreated}
isSnapped={isSnapped}
ispreSnapped={ispreSnapped}
snappedPoint={snappedPoint}
isSnappedUUID={isSnappedUUID}
isAngleSnapped={isAngleSnapped}
anglesnappedPoint={anglesnappedPoint}
/> */}
<ZoneGroup />
<FloorGroupAilse
floorGroupAisle={floorGroupAisle}
plane={plane}
floorPlanGroupLine={floorPlanGroupLine}
floorPlanGroupPoint={floorPlanGroupPoint}
line={line}
lines={lines}
currentLayerPoint={currentLayerPoint}
dragPointControls={dragPointControls}
floorPlanGroup={floorPlanGroup}
ReferenceLineMesh={ReferenceLineMesh}
LineCreated={LineCreated}
isSnapped={isSnapped}
ispreSnapped={ispreSnapped}
snappedPoint={snappedPoint}
isSnappedUUID={isSnappedUUID}
isAngleSnapped={isAngleSnapped}
anglesnappedPoint={anglesnappedPoint}
/>
<DrieHtmlTemp
itemsGroup={itemsGroup}
/>
</>
);
}