diff --git a/app/package-lock.json b/app/package-lock.json index b12c166..485c1cb 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -4206,16 +4206,15 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "license": "MIT", "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", - "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", + "picocolors": "1.1.1", "pretty-format": "^27.0.2" }, "engines": { diff --git a/app/package.json b/app/package.json index eb7152a..8514111 100644 --- a/app/package.json +++ b/app/package.json @@ -53,9 +53,7 @@ "prestart": "tsc scripts/git-prompt.ts && node scripts/git-prompt.js", "start": "react-scripts start", "build": "GENERATE_SOURCEMAP=false react-scripts build", - "test": "jest", - "cypress:open": "cypress open", - "cypress:run": "cypress run" + "test": "jest" }, "eslintConfig": { "extends": [ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg new file mode 100644 index 0000000..6f7991e Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg differ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.png b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.png new file mode 100644 index 0000000..f3f3248 Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.png differ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Metalic.001.png b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Metalic.001.png new file mode 100644 index 0000000..48cc96b Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Metalic.001.png differ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_MetallicRoughness.001.jpg b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_MetallicRoughness.001.jpg new file mode 100644 index 0000000..e181405 Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_MetallicRoughness.001.jpg differ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Normal.001.jpg b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Normal.001.jpg new file mode 100644 index 0000000..462c044 Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Normal.001.jpg differ diff --git a/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Roughness.png b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Roughness.png new file mode 100644 index 0000000..c3af94e Binary files /dev/null and b/app/src/assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Roughness.png differ diff --git a/app/src/assets/textures/floor/tex2/MI_FloorMats01_Normal.png b/app/src/assets/textures/floor/tex2/MI_FloorMats01_Normal.png new file mode 100644 index 0000000..b10551b Binary files /dev/null and b/app/src/assets/textures/floor/tex2/MI_FloorMats01_Normal.png differ diff --git a/app/src/assets/textures/floor/tex2/MI_FloorMats01_baseColor.png b/app/src/assets/textures/floor/tex2/MI_FloorMats01_baseColor.png new file mode 100644 index 0000000..cf12929 Binary files /dev/null and b/app/src/assets/textures/floor/tex2/MI_FloorMats01_baseColor.png differ diff --git a/app/src/assets/textures/floor/tex2/MI_FloorMats01_occlusionRoughnessMetallic.png b/app/src/assets/textures/floor/tex2/MI_FloorMats01_occlusionRoughnessMetallic.png new file mode 100644 index 0000000..cc84470 Binary files /dev/null and b/app/src/assets/textures/floor/tex2/MI_FloorMats01_occlusionRoughnessMetallic.png differ diff --git a/app/src/assets/textures/floor/tex3/metal_plate_diff_1k.jpg b/app/src/assets/textures/floor/tex3/metal_plate_diff_1k.jpg new file mode 100644 index 0000000..e3da12a Binary files /dev/null and b/app/src/assets/textures/floor/tex3/metal_plate_diff_1k.jpg differ diff --git a/app/src/assets/textures/floor/tex3/metal_plate_metal_1k.png b/app/src/assets/textures/floor/tex3/metal_plate_metal_1k.png new file mode 100644 index 0000000..5d7594e Binary files /dev/null and b/app/src/assets/textures/floor/tex3/metal_plate_metal_1k.png differ diff --git a/app/src/assets/textures/floor/tex3/metal_plate_nor_gl_1k.png b/app/src/assets/textures/floor/tex3/metal_plate_nor_gl_1k.png new file mode 100644 index 0000000..aa89562 Binary files /dev/null and b/app/src/assets/textures/floor/tex3/metal_plate_nor_gl_1k.png differ diff --git a/app/src/assets/textures/floor/tex3/metal_plate_rough_1k.png b/app/src/assets/textures/floor/tex3/metal_plate_rough_1k.png new file mode 100644 index 0000000..f454a84 Binary files /dev/null and b/app/src/assets/textures/floor/tex3/metal_plate_rough_1k.png differ diff --git a/app/src/assets/textures/hdr/autoshop_01_1k.hdr b/app/src/assets/textures/hdr/autoshop_01_1k.hdr new file mode 100644 index 0000000..3abce56 Binary files /dev/null and b/app/src/assets/textures/hdr/autoshop_01_1k.hdr differ diff --git a/app/src/assets/textures/hdr/empty_warehouse_01_1k.hdr b/app/src/assets/textures/hdr/empty_warehouse_01_1k.hdr new file mode 100644 index 0000000..5066e1e Binary files /dev/null and b/app/src/assets/textures/hdr/empty_warehouse_01_1k.hdr differ diff --git a/app/src/assets/textures/hdr/industrial_pipe_and_valve_01_1k.hdr b/app/src/assets/textures/hdr/industrial_pipe_and_valve_01_1k.hdr new file mode 100644 index 0000000..d9e9bcb Binary files /dev/null and b/app/src/assets/textures/hdr/industrial_pipe_and_valve_01_1k.hdr differ diff --git a/app/src/assets/textures/hdr/machine_shop_02_1k.hdr b/app/src/assets/textures/hdr/machine_shop_02_1k.hdr new file mode 100644 index 0000000..22c4758 Binary files /dev/null and b/app/src/assets/textures/hdr/machine_shop_02_1k.hdr differ diff --git a/app/src/components/layout/sidebarRight/properties/FloorProperties.tsx b/app/src/components/layout/sidebarRight/properties/FloorProperties.tsx index 41e8d41..c5938f7 100644 --- a/app/src/components/layout/sidebarRight/properties/FloorProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/FloorProperties.tsx @@ -4,6 +4,9 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import defaultTexture from '../../../../assets/textures/floor/white.png'; import flootTexture1 from '../../../../assets/textures/floor/factory wall texture.jpg'; +import flootTexture2 from '../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg'; +import flootTexture3 from '../../../../assets/textures/floor/tex2/MI_FloorMats01_baseColor.png'; +import flootTexture4 from '../../../../assets/textures/floor/tex3/metal_plate_diff_1k.jpg'; import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; @@ -13,9 +16,12 @@ type Material = { textureName: string; }; -const materials = [ +export const materials = [ { texture: defaultTexture, textureId: "Default Material", textureName: "Default Material" }, - { texture: flootTexture1, textureId: "Material 1", textureName: "Grunge Concrete Wall" } + { texture: flootTexture1, textureId: "Material 1", textureName: "Grunge Concrete Wall" }, + { texture: flootTexture2, textureId: "Material 2", textureName: "Tiled Floor" }, + { texture: flootTexture3, textureId: "Material 3", textureName: "Metal Floor" }, + { texture: flootTexture4, textureId: "Material 4", textureName: "Metal Floor 2" }, ]; const FloorProperties = () => { @@ -30,7 +36,7 @@ const FloorProperties = () => { top: materials.find((mat) => mat.textureId === topMaterial) || null, side: materials.find((mat) => mat.textureId === sideMaterial) || null, }); - }, []); + }, [sideMaterial, topMaterial]); const handleDepthChange = (val: string) => { setFloorDepth(parseFloat(val)); diff --git a/app/src/components/layout/sidebarRight/properties/SelectedFloorProperties.tsx b/app/src/components/layout/sidebarRight/properties/SelectedFloorProperties.tsx index 8df2a88..21fbdc7 100644 --- a/app/src/components/layout/sidebarRight/properties/SelectedFloorProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/SelectedFloorProperties.tsx @@ -2,15 +2,13 @@ import { useEffect, useState } from "react"; import InputWithDropDown from "../../../ui/inputs/InputWithDropDown"; import InputToggle from "../../../ui/inputs/InputToggle"; -import defaultTexture from '../../../../assets/textures/floor/white.png'; -import floorTexture1 from '../../../../assets/textures/floor/factory wall texture.jpg'; - import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; import { useSceneContext } from "../../../../modules/scene/sceneContext"; import { useVersionContext } from "../../../../modules/builder/version/versionContext"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../../functions/getUserData"; import { useSocketStore } from "../../../../store/builder/store"; +import { materials } from "./FloorProperties"; // import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi"; @@ -29,11 +27,6 @@ const SelectedFloorProperties = () => { const [activeSurface, setActiveSurface] = useState<"top" | "side">("top"); - const materials = [ - { texture: defaultTexture, textureId: "Default Material", textureName: "Default Material" }, - { texture: floorTexture1, textureId: "Material 1", textureName: "Grunge Concrete" } - ]; - const floor = selectedFloor ? getFloorById(selectedFloor.userData.floorUuid) : null; useEffect(() => { diff --git a/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts b/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts deleted file mode 100644 index 80fdcb9..0000000 --- a/app/src/modules/builder/IntialLoad/loadInitialWallItems.ts +++ /dev/null @@ -1,110 +0,0 @@ -// import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader"; -// import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"; -// import * as THREE from "three"; -// import * as Types from "../../../types/world/worldTypes"; -// import { getWallItems } from "../../../services/factoryBuilder/asset/wallAsset/getWallItemsApi"; -// import { retrieveGLTF, storeGLTF } from "../../../utils/indexDB/idbUtils"; -// import { getUserData } from "../../../functions/getUserData"; - -// async function loadInitialWallItems( -// setWallItems: Types.setWallItemSetState, -// projectId?: string, -// versionId?: string -// ): Promise { -// if (!projectId || !versionId) return; -// try { -// const { organization, email } = getUserData(); - -// if (!email) { -// console.error("No email found in localStorage"); -// } - -// const items = await getWallItems(organization, projectId, versionId); - -// let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; - -// if (!items || items.length === 0) { -// localStorage.removeItem("WallItems"); -// return; -// } - -// localStorage.setItem("WallItems", JSON.stringify(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); - -// const loadedWallItems = await Promise.all( -// items.map(async (item: Types.WallItem) => { -// // Check THREE.js cache first -// const cachedModel = THREE.Cache.get(item.assetId!); -// if (cachedModel) { -// return processModel(cachedModel, item); -// } - -// // Check IndexedDB cache -// const cachedModelBlob = await retrieveGLTF(item.assetId!); -// if (cachedModelBlob) { -// const blobUrl = URL.createObjectURL(cachedModelBlob); -// return new Promise((resolve) => { -// loader.load(blobUrl, (gltf) => { -// URL.revokeObjectURL(blobUrl); -// THREE.Cache.add(item.assetId!, gltf); -// resolve(processModel(gltf, item)); -// }); -// }); -// } - -// // Load from original URL if not cached -// const modelUrl = `${url_Backend_dwinzo}/api/v2/AssetFile/${item.assetId!}`; -// return new Promise((resolve) => { -// loader.load(modelUrl, async (gltf) => { -// try { -// // Cache the model -// const modelBlob = await fetch(modelUrl).then((res) => res.blob()); -// await storeGLTF(item.assetId!, modelBlob); -// THREE.Cache.add(item.assetId!, gltf); -// resolve(processModel(gltf, item)); -// } catch (error) { -// console.error("Failed to cache model:", error); -// resolve(processModel(gltf, item)); -// } -// }); -// }); -// }) -// ); - -// setWallItems(loadedWallItems); -// } catch (error) { -// console.error("Failed to load wall items:", error); -// } -// } - -// function processModel(gltf: GLTF, item: Types.WallItem): Types.WallItem { -// const model = gltf.scene.clone(); -// model.uuid = item.modelUuid!; - -// model.children[0]?.children?.forEach((child: THREE.Object3D) => { -// if (child.name !== "CSG_REF") { -// child.castShadow = true; -// child.receiveShadow = true; -// } -// }); - -// return { -// type: item.type, -// model: model, -// modelName: item.modelName, -// assetId: item.assetId, -// scale: item.scale, -// csgscale: item.csgscale, -// csgposition: item.csgposition, -// position: item.position, -// quaternion: item.quaternion, -// }; -// } - -// export default loadInitialWallItems; diff --git a/app/src/modules/builder/aisle/Instances/aisleInstances.tsx b/app/src/modules/builder/aisle/Instances/aisleInstances.tsx index 7683457..cb4ca35 100644 --- a/app/src/modules/builder/aisle/Instances/aisleInstances.tsx +++ b/app/src/modules/builder/aisle/Instances/aisleInstances.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import { useToggleView } from '../../../../store/builder/store'; import AisleInstance from './instance/aisleInstance'; import Point from '../../point/point'; diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx index 8bc6fd5..6908cf3 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/arrowsAisle.tsx @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { useMemo, useRef } from 'react'; -import { Extrude } from '@react-three/drei'; +import { Instances, Instance } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; @@ -10,8 +10,16 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { const { toolMode } = useToolMode(); const { setSelectedAisle, hoveredPoint } = useBuilderStore(); - const arrows = useMemo(() => { - if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return []; + const { arrowGeometry, arrowInstances } = useMemo(() => { + const result = { + arrowGeometry: null as THREE.ExtrudeGeometry | null, + arrowInstances: [] as { + position: [number, number, number]; + rotation: [number, number, number]; + }[], + }; + + if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); @@ -24,69 +32,67 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { direction.normalize(); const count = Math.floor((length + spacing) / (arrowLength + spacing)); + const angle = Math.atan2(direction.x, direction.z) + Math.PI; - const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = []; + const shape = new THREE.Shape(); + const w = width * 0.8; + const h = arrowLength; + + shape.moveTo(0, 0); + shape.lineTo(w, h * 0.6); + shape.lineTo(w * 0.4, h * 0.6); + shape.lineTo(w * 0.4, h); + shape.lineTo(-w * 0.4, h); + shape.lineTo(-w * 0.4, h * 0.6); + shape.lineTo(-w, h * 0.6); + shape.lineTo(0, 0); + + result.arrowGeometry = new THREE.ExtrudeGeometry(shape, { + depth: 0.01, + bevelEnabled: false, + }); + + result.arrowGeometry.rotateX(Math.PI / 2); for (let i = 0; i < count; i++) { - const initialOffset = arrowLength; - const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing)); + const offset = arrowLength + i * (arrowLength + spacing); + const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset); - const shape = new THREE.Shape(); - const w = width * 0.8; - const h = arrowLength; - - shape.moveTo(0, 0); - shape.lineTo(w, h * 0.6); - shape.lineTo(w * 0.4, h * 0.6); - shape.lineTo(w * 0.4, h); - shape.lineTo(-w * 0.4, h); - shape.lineTo(-w * 0.4, h * 0.6); - shape.lineTo(-w, h * 0.6); - shape.lineTo(0, 0); - - const angle = Math.atan2(direction.x, direction.z) + Math.PI; - - arrowShapes.push({ shape, position: center, rotationY: angle }); + result.arrowInstances.push({ + position: [center.x, 0, center.z], + rotation: [0, angle, 0], + }); } - return arrowShapes; + return result; }, [aisle]); const handleClick = () => { if (toolMode === 'move' && !hoveredPoint) { setSelectedAisle(aisleRef.current); } - } + }; - if (arrows.length === 0) return null; + if (!arrowGeometry || arrowInstances.length === 0) return null; return ( { setSelectedAisle(null); }} > - {arrows.map(({ shape, position, rotationY }, index) => ( - - - - - - ))} + + + {arrowInstances.map(({ position, rotation }, i) => ( + + ))} + ); } diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx index bbdad90..72fb3c7 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dashedAisle.tsx @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { useMemo, useRef } from 'react'; -import { Extrude } from '@react-three/drei'; +import { Instances, Instance } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; @@ -10,82 +10,71 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) { const { toolMode } = useToolMode(); const { setSelectedAisle, hoveredPoint } = useBuilderStore(); - const shapes = useMemo(() => { + const dashInstances = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return []; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; const dashLength = aisle.type.dashLength || 0.5; const gapLength = aisle.type.gapLength || 0.3; - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); - - const totalLength = new THREE.Vector3().subVectors(end, start).length(); + const totalVec = new THREE.Vector3().subVectors(end, start); + const totalLength = totalVec.length(); + const direction = totalVec.clone().normalize(); const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength)); - const shapes = []; - const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); + const instances = []; for (let i = 0; i < segmentCount; i++) { - const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength)); - const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength); + const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2); - const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2); - const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2); - const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2); - const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2); + const rotationY = Math.atan2(direction.x, direction.z); - const shape = new THREE.Shape(); - shape.moveTo(leftStart.x, leftStart.z); - shape.lineTo(leftEnd.x, leftEnd.z); - shape.lineTo(rightEnd.x, rightEnd.z); - shape.lineTo(rightStart.x, rightStart.z); - shape.closePath(); - - shapes.push(shape); + instances.push({ + position: [center.x, 0, center.z] as [number, number, number], + scale: [width, 0.001, dashLength] as [number, number, number], + rotation: [0, rotationY, 0] as [number, number, number], + }); } - return shapes; + return instances; }, [aisle]); const handleClick = () => { if (toolMode === 'move' && !hoveredPoint) { setSelectedAisle(aisleRef.current); } - } + }; - if (shapes.length === 0) return null; + if (dashInstances.length === 0) return null; return ( { setSelectedAisle(null); }} > - {shapes.map((shape, index) => ( - - + + + {dashInstances.map((inst, i) => ( + - - ))} + ))} + ); } -export default DashedAisle; \ No newline at end of file +export default DashedAisle; diff --git a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx index bf17d08..e2b264a 100644 --- a/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx +++ b/app/src/modules/builder/aisle/Instances/instance/aisleTypes/dottedAisle.tsx @@ -1,6 +1,6 @@ import * as THREE from 'three'; import { useMemo, useRef } from 'react'; -import { Extrude } from '@react-three/drei'; +import { Instance, Instances } from '@react-three/drei'; import * as Constants from '../../../../../../types/world/worldConstants'; import { useToolMode } from '../../../../../../store/builder/store'; import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore'; @@ -10,31 +10,20 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) { const { toolMode } = useToolMode(); const { setSelectedAisle, hoveredPoint } = useBuilderStore(); - const shapes = useMemo(() => { + const dotPositions = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return []; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); - const width = aisle.type.dotRadius || 0.1; const dotSpacing = aisle.type.gapLength || 0.5; - const dotRadius = width * 0.6; const totalLength = new THREE.Vector3().subVectors(end, start).length(); const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing); - - const shapes = []; const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); - for (let i = 0; i < dotCount; i++) { - const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2); - - const shape = new THREE.Shape(); - shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false); - - shapes.push(shape); - } - - return shapes; + return Array.from({ length: dotCount }, (_, i) => { + return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2); + }); }, [aisle]); const handleClick = () => { @@ -43,7 +32,14 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) { } } - if (shapes.length === 0) return null; + const { dotRadius, color } = useMemo(() => { + return { + dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06, + color: aisle.type.aisleColor || '#ffffff' + }; + }, [aisle]); + + if (dotPositions.length === 0) return null; return ( { setSelectedAisle(null); }} > - {shapes.map((shape, index) => ( - - + + + {dotPositions.map((position, index) => ( + - - ))} + ))} + ); } diff --git a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx index af08226..aea7cd2 100644 --- a/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/aisleCreator.tsx @@ -19,8 +19,9 @@ function AisleCreator() { const { toolMode } = useToolMode(); const { activeLayer } = useActiveLayer(); const { socket } = useSocketStore(); - const { aisleStore } = useSceneContext(); + const { aisleStore, undoRedo2DStore } = useSceneContext(); const { addAisle, getAislePointById } = aisleStore(); + const { push2D } = undoRedo2DStore(); const drag = useRef(false); const isLeftMouseDown = useRef(false); const { selectedVersionStore } = useVersionContext(); @@ -107,7 +108,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -145,7 +162,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -182,7 +215,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -218,7 +267,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -256,7 +321,23 @@ function AisleCreator() { gapLength: gapLength } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -293,7 +374,23 @@ function AisleCreator() { isFlipped: isFlipped } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -329,7 +426,23 @@ function AisleCreator() { aisleWidth: aisleWidth } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API @@ -366,7 +479,23 @@ function AisleCreator() { isFlipped: isFlipped } }; + addAisle(aisle); + + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Line-Create', + point: { + type: 'Aisle', + lineData: aisle, + timeStamp: new Date().toISOString(), + } + } + ], + }) + if (projectId) { // API diff --git a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx index 6ce66fe..ec90f8d 100644 --- a/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx +++ b/app/src/modules/builder/aisle/aisleCreator/referenceAisle.tsx @@ -3,7 +3,7 @@ import * as THREE from 'three'; import { useFrame, useThree } from '@react-three/fiber'; import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store'; import * as Constants from '../../../../types/world/worldConstants'; -import { Extrude, Html } from '@react-three/drei'; +import { Extrude, Html, Instance, Instances } from '@react-three/drei'; import { useBuilderStore } from '../../../../store/builder/useBuilderStore'; import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping'; import { usePointSnapping } from '../../point/helpers/usePointSnapping'; @@ -289,125 +289,119 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) { } function DashedAisle({ aisle }: { readonly aisle: Aisle }) { - const shapes = useMemo(() => { + + const dashInstances = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return []; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); + const width = aisle.type.aisleWidth || 0.1; const dashLength = aisle.type.dashLength || 0.5; const gapLength = aisle.type.gapLength || 0.3; - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - const perp = new THREE.Vector3(-direction.z, 0, direction.x).normalize(); - - const totalLength = new THREE.Vector3().subVectors(end, start).length(); + const totalVec = new THREE.Vector3().subVectors(end, start); + const totalLength = totalVec.length(); + const direction = totalVec.clone().normalize(); const segmentCount = Math.floor((totalLength + gapLength) / (dashLength + gapLength)); - const shapes = []; - const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); + const instances = []; for (let i = 0; i < segmentCount; i++) { - const segmentStart = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * (dashLength + gapLength)); - const segmentEnd = new THREE.Vector3().copy(segmentStart).addScaledVector(directionNormalized, dashLength); + const center = start.clone().addScaledVector(direction, i * (dashLength + gapLength) + dashLength / 2); - const leftStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, width / 2); - const rightStart = new THREE.Vector3().copy(segmentStart).addScaledVector(perp, -width / 2); - const leftEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, width / 2); - const rightEnd = new THREE.Vector3().copy(segmentEnd).addScaledVector(perp, -width / 2); + const rotationY = Math.atan2(direction.x, direction.z); - const shape = new THREE.Shape(); - shape.moveTo(leftStart.x, leftStart.z); - shape.lineTo(leftEnd.x, leftEnd.z); - shape.lineTo(rightEnd.x, rightEnd.z); - shape.lineTo(rightStart.x, rightStart.z); - shape.closePath(); - - shapes.push(shape); + instances.push({ + position: [center.x, 0, center.z] as [number, number, number], + scale: [width, 0.001, dashLength] as [number, number, number], + rotation: [0, rotationY, 0] as [number, number, number], + }); } - return shapes; + return instances; }, [aisle]); - if (shapes.length === 0) return null; + if (dashInstances.length === 0) return null; return ( - {shapes.map((shape, index) => ( - - + + + {dashInstances.map((inst, i) => ( + - - ))} + ))} + ); } function DottedAisle({ aisle }: { readonly aisle: Aisle }) { - const shapes = useMemo(() => { + + const dotPositions = useMemo(() => { if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return []; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); - const width = aisle.type.dotRadius || 0.1; const dotSpacing = aisle.type.gapLength || 0.5; - const dotRadius = width * 0.6; const totalLength = new THREE.Vector3().subVectors(end, start).length(); const dotCount = Math.floor((totalLength + (dotSpacing / 2)) / dotSpacing); - - const shapes = []; const directionNormalized = new THREE.Vector3().subVectors(end, start).normalize(); - for (let i = 0; i < dotCount; i++) { - const dotCenter = new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2); - - const shape = new THREE.Shape(); - shape.absarc(dotCenter.x, dotCenter.z, dotRadius, 0, Math.PI * 2, false); - - shapes.push(shape); - } - - return shapes; + return Array.from({ length: dotCount }, (_, i) => { + return new THREE.Vector3().copy(start).addScaledVector(directionNormalized, i * dotSpacing + dotSpacing / 2); + }); }, [aisle]); - if (shapes.length === 0) return null; + const { dotRadius, color } = useMemo(() => { + return { + dotRadius: aisle.type.aisleType === 'dotted-aisle' ? ((aisle.type as any).dotRadius || 0.1) * 0.6 : 0.06, + color: aisle.type.aisleColor || '#ffffff' + }; + }, [aisle]); + + if (dotPositions.length === 0) return null; return ( - {shapes.map((shape, index) => ( - - + + + {dotPositions.map((position, index) => ( + - - ))} + ))} + ); } function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { - const arrows = useMemo(() => { - if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return []; + + const { arrowGeometry, arrowInstances } = useMemo(() => { + const result = { + arrowGeometry: null as THREE.ExtrudeGeometry | null, + arrowInstances: [] as { + position: [number, number, number]; + rotation: [number, number, number]; + }[], + }; + + if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrows-aisle') return result; const start = new THREE.Vector3(...aisle.points[0].position); const end = new THREE.Vector3(...aisle.points[1].position); @@ -420,55 +414,53 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) { direction.normalize(); const count = Math.floor((length + spacing) / (arrowLength + spacing)); + const angle = Math.atan2(direction.x, direction.z) + Math.PI; - const arrowShapes: { shape: THREE.Shape; position: THREE.Vector3; rotationY: number }[] = []; + const shape = new THREE.Shape(); + const w = width * 0.8; + const h = arrowLength; + + shape.moveTo(0, 0); + shape.lineTo(w, h * 0.6); + shape.lineTo(w * 0.4, h * 0.6); + shape.lineTo(w * 0.4, h); + shape.lineTo(-w * 0.4, h); + shape.lineTo(-w * 0.4, h * 0.6); + shape.lineTo(-w, h * 0.6); + shape.lineTo(0, 0); + + result.arrowGeometry = new THREE.ExtrudeGeometry(shape, { + depth: 0.01, + bevelEnabled: false, + }); + + result.arrowGeometry.rotateX(Math.PI / 2); for (let i = 0; i < count; i++) { - const initialOffset = arrowLength; - const center = new THREE.Vector3().copy(start).addScaledVector(direction, initialOffset + i * (arrowLength + spacing)); + const offset = arrowLength + i * (arrowLength + spacing); + const center = new THREE.Vector3().copy(start).addScaledVector(direction, offset); - const shape = new THREE.Shape(); - const w = width * 0.8; - const h = arrowLength; - - shape.moveTo(0, 0); - shape.lineTo(w, h * 0.6); - shape.lineTo(w * 0.4, h * 0.6); - shape.lineTo(w * 0.4, h); - shape.lineTo(-w * 0.4, h); - shape.lineTo(-w * 0.4, h * 0.6); - shape.lineTo(-w, h * 0.6); - shape.lineTo(0, 0); - - const angle = Math.atan2(direction.x, direction.z) + Math.PI; - - arrowShapes.push({ shape, position: center, rotationY: angle }); + result.arrowInstances.push({ + position: [center.x, 0, center.z], + rotation: [0, angle, 0], + }); } - return arrowShapes; + return result; }, [aisle]); - if (arrows.length === 0) return null; + if (!arrowGeometry || arrowInstances.length === 0) return null; return ( - {arrows.map(({ shape, position, rotationY }, index) => ( - - - - - - ))} + + + {arrowInstances.map(({ position, rotation }, i) => ( + + ))} + ); } diff --git a/app/src/modules/builder/asset/assetsGroup.tsx b/app/src/modules/builder/asset/assetsGroup.tsx index 89f7471..6c06be2 100644 --- a/app/src/modules/builder/asset/assetsGroup.tsx +++ b/app/src/modules/builder/asset/assetsGroup.tsx @@ -44,6 +44,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { useEffect(() => { if (!projectId || !selectedVersion) return; + clearEvents(); let totalAssets = 0; @@ -304,7 +305,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = -(event.clientY / window.innerHeight) * 2 + 1; - addAssetModel(scene, raycaster, camera, pointer, socket, selectedItem, setSelectedItem, addEvent, addAsset, plane, selectedVersion, projectId, userId); + addAssetModel(scene, raycaster, camera, pointer, socket, selectedItem, setSelectedItem, addEvent, addAsset, plane, loader, selectedVersion, projectId, userId); } }; @@ -345,10 +346,10 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) { canvasElement.removeEventListener("mousemove", onMouseMove); canvasElement.removeEventListener("mouseup", onMouseUp); }; - }, [selectedItem, camera, pointer, activeModule, controls, isRenameMode]); + }, [selectedItem, camera, activeModule, controls, isRenameMode]); return ( - + ) } diff --git a/app/src/modules/builder/asset/functions/addAssetModel.ts b/app/src/modules/builder/asset/functions/addAssetModel.ts index 80d8b0a..bb66c6a 100644 --- a/app/src/modules/builder/asset/functions/addAssetModel.ts +++ b/app/src/modules/builder/asset/functions/addAssetModel.ts @@ -20,6 +20,7 @@ async function addAssetModel( addEvent: (event: EventsSchema) => void, addAsset: (asset: Asset) => void, plane: Types.RefMesh, + loader: GLTFLoader, selectedVersion?: Version | null, projectId?: string, userId?: string @@ -29,12 +30,6 @@ async function addAssetModel( let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; try { - 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); - raycaster.setFromCamera(pointer, camera); const wallFloorsGroup = scene.getObjectByName("Walls-Floors-Group") as Types.Group | null; const floorsGroup = scene.getObjectByName("Floors-Group") as Types.Group | null; diff --git a/app/src/modules/builder/asset/models/model/model.tsx b/app/src/modules/builder/asset/models/model/model.tsx index 8cdf67f..2ce5be6 100644 --- a/app/src/modules/builder/asset/models/model/model.tsx +++ b/app/src/modules/builder/asset/models/model/model.tsx @@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils'; import { RapierRigidBody } from '@react-three/rapier'; import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; -import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; import { ThreeEvent, useThree } from '@react-three/fiber'; import { useActiveTool, useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useSocketStore, useToggleView, useToolMode } from '../../../../../store/builder/store'; import { AssetBoundingBox } from '../../functions/assetBoundingBox'; @@ -25,7 +24,7 @@ import { ModelAnimator } from './animator/modelAnimator'; import ConveyorCollider from '../../../../scene/physics/conveyor/conveyorCollider'; import RibbonCollider from '../../../../scene/physics/conveyor/ribbonCollider'; -function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boolean }) { +function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendered: boolean, loader: GLTFLoader }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const { controls, gl } = useThree(); const savedTheme: string = localStorage.getItem("theme") || "light"; @@ -139,11 +138,6 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole }, [isRendered, selectedFloorItem]) useEffect(() => { - 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); const loadModel = async () => { try { @@ -192,8 +186,7 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error); } }; - loader.load( - modelUrl, + loader.load(modelUrl, handleBackendLoad, undefined, (error) => { @@ -448,26 +441,26 @@ function Model({ asset, isRendered }: { readonly asset: Asset, isRendered: boole castShadow receiveShadow onDoubleClick={(e) => { - e.stopPropagation(); if (!toggleView) { + e.stopPropagation(); handleDblClick(asset); } }} onClick={(e) => { - e.stopPropagation(); if (!toggleView) { + e.stopPropagation(); handleClick(e, asset); } }} onPointerOver={(e) => { - e.stopPropagation(); if (!toggleView) { + e.stopPropagation(); handlePointerOver(asset); } }} onPointerLeave={(e) => { - e.stopPropagation(); if (!toggleView) { + e.stopPropagation(); handlePointerOut(e, asset); } }} diff --git a/app/src/modules/builder/asset/models/models.tsx b/app/src/modules/builder/asset/models/models.tsx index cdfa21b..5944b1c 100644 --- a/app/src/modules/builder/asset/models/models.tsx +++ b/app/src/modules/builder/asset/models/models.tsx @@ -7,10 +7,11 @@ import { useSelectedAsset } from '../../../../store/simulation/useSimulationStor import { useSceneContext } from '../../../scene/sceneContext'; import Model from './model/model'; +import { GLTFLoader } from "three/examples/jsm/Addons"; const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); -function Models() { +function Models({ loader }: { loader: GLTFLoader }) { const { controls, camera } = useThree(); const { assetStore } = useSceneContext(); const { assets } = assetStore(); @@ -57,7 +58,7 @@ function Models() { }} > {assets.map((asset) => ( - + ))} ); diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index ab9434b..d340a35 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -95,7 +95,7 @@ export default function Builder() { - + {/* @@ -103,7 +103,7 @@ export default function Builder() { - + */} @@ -113,9 +113,9 @@ export default function Builder() { - + {/* */} - + {/* */} diff --git a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx index c6c8139..419c1d8 100644 --- a/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx +++ b/app/src/modules/builder/floor/Instances/Instance/floorInstance.tsx @@ -1,108 +1,247 @@ -import { useMemo } from 'react'; -import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace } from 'three'; -import { useLoader } from '@react-three/fiber'; -import { Extrude } from '@react-three/drei'; -import useModuleStore from '../../../../../store/useModuleStore'; -import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; -import { useToggleView } from '../../../../../store/builder/store'; -import * as Constants from '../../../../../types/world/worldConstants'; +import { useMemo } from "react"; +import { + Shape, + Vector2, + DoubleSide, + TextureLoader, + RepeatWrapping, + SRGBColorSpace, + NoColorSpace, +} from "three"; +import { useLoader } from "@react-three/fiber"; +import { Extrude } from "@react-three/drei"; +import useModuleStore from "../../../../../store/useModuleStore"; +import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; +import { useToggleView } from "../../../../../store/builder/store"; +import * as Constants from "../../../../../types/world/worldConstants"; import texturePath from "../../../../../assets/textures/floor/white.png"; import texturePathDark from "../../../../../assets/textures/floor/black.png"; -import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg'; +import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg"; + +// floor-mat1 +import material2Map from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_BaseColor.001.jpg"; +import material2NormalMap from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_Normal.001.jpg"; +import material2MetalicRoughnessMap from "../../../../../assets/textures/floor/tex1/MI_FactoryConcreteFloor01_MetallicRoughness.001.jpg"; + +// floor-mat2 +import material3Map from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_baseColor.png"; +import material3NormalMap from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_Normal.png"; +import material3MetalicRoughnessMap from "../../../../../assets/textures/floor/tex2/MI_FloorMats01_occlusionRoughnessMetallic.png"; + +// floor-mat3 +import material4Map from "../../../../../assets/textures/floor/tex3/metal_plate_diff_1k.jpg"; +import material4RoughnessMap from "../../../../../assets/textures/floor/tex3/metal_plate_rough_1k.png"; +import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal_plate_metal_1k.png"; +import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png"; function FloorInstance({ floor }: { floor: Floor }) { - const { togglView } = useToggleView(); - const { activeModule } = useModuleStore(); - const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore(); - const savedTheme = localStorage.getItem('theme'); + const { togglView } = useToggleView(); + const { activeModule } = useModuleStore(); + const { selectedFloor, setSelectedFloor, setSelectedDecal } = + useBuilderStore(); + const savedTheme = localStorage.getItem("theme"); - const materials: Record = { - "Default Material": savedTheme === "dark" ? texturePathDark : texturePath, - "Material 1": savedTheme === "dark" ? material1 : material1, - }; + const materials: Record< + string, + { + map: string; + roughnessMap?: string; + metalnessMap?: string; + normalMap?: string; + textureTileScale?: [number, number]; + } + > = { + "Default Material": { + map: savedTheme === "dark" ? texturePathDark : texturePath, + }, + "Material 1": { + map: material1, + }, + "Material 2": { + map: material2Map, + roughnessMap: material2MetalicRoughnessMap, + metalnessMap: material2MetalicRoughnessMap, + normalMap: material2NormalMap, + textureTileScale: [0.1, 0.1], + }, + "Material 3": { + map: material3Map, + roughnessMap: material3MetalicRoughnessMap, + metalnessMap: material3MetalicRoughnessMap, + normalMap: material3NormalMap, + textureTileScale: [0.35, 0.5], + }, + "Material 4": { + map: material4Map, + roughnessMap: material4RoughnessMap, + metalnessMap: material4MetalicMap, + normalMap: material4NormalMap, + }, + }; - const shape = useMemo(() => { - const shape = new Shape(); - const points = floor.points.map(p => new Vector2(p.position[0], p.position[2])); - if (points.length < 3) return null; - shape.moveTo(points[0].x, points[0].y); - for (let i = 1; i < points.length; i++) { - shape.lineTo(points[i].x, points[i].y); - } - return shape; - }, [floor]); - - const textureScale = Constants.floorConfig.textureScale; - - const [topTexture, sideTexture] = useLoader( - TextureLoader, - [ - materials[floor.topMaterial] || materials['Default Material'], - materials[floor.sideMaterial] || materials['Default Material'] - ] + const shape = useMemo(() => { + const shape = new Shape(); + const points = floor.points.map( + (p) => new Vector2(p.position[0], p.position[2]) ); + if (points.length < 3) return null; + shape.moveTo(points[0].x, points[0].y); + for (let i = 1; i < points.length; i++) { + shape.lineTo(points[i].x, points[i].y); + } + return shape; + }, [floor]); - if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null; + const textureScale = Constants.floorConfig.textureScale; - [topTexture, sideTexture].forEach(tex => { - tex.wrapS = tex.wrapT = RepeatWrapping; - tex.repeat.set(textureScale, textureScale); - tex.colorSpace = SRGBColorSpace; + // Helper function to handle texture maps and filter out null values + function getMaterialMaps(material: any, defaultMap: any) { + const materialMap = material.map || defaultMap; + const normalMap = material.normalMap || null; + const roughnessMap = material.roughnessMap || null; + const metalnessMap = material.metalnessMap || null; + + return [materialMap, normalMap, roughnessMap, metalnessMap].filter( + (texture): texture is string => texture !== null + ); + } + + // Default material map + const defaultMaterialMap = materials["Default Material"].map; + + // Get top and side material maps + const topMaterial = materials[floor.topMaterial]; + const sideMaterial = materials[floor.sideMaterial]; + + // Get the filtered lists for top and side textures + const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap); + const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap); + + // Use loader to load top and side textures + const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] = + useLoader(TextureLoader, topTexturesList); + + const [ + sideTexture, + sideNormalTexture, + sideRoughnessTexture, + sideMetalicTexture, + ] = useLoader(TextureLoader, sideTexturesList); + + // Early exit if materials are missing + if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) + return null; + + // Combine and pair textures with their corresponding material + const textureMaterialMap = [ + { + textures: [ + topTexture, + topNormalTexture, + topRoughnessTexture, + topMetalicTexture, + ], + materialKey: floor.topMaterial, + }, + { + textures: [ + sideTexture, + sideNormalTexture, + sideRoughnessTexture, + sideMetalicTexture, + ], + materialKey: floor.sideMaterial, + }, + ]; + + // Apply texture settings + textureMaterialMap.forEach(({ textures, materialKey }) => { + const tileScale = materials[materialKey]?.textureTileScale ?? [ + textureScale, + textureScale, + ]; + + textures.forEach((tex, idx) => { + if (!tex) return; + tex.wrapS = tex.wrapT = RepeatWrapping; + tex.repeat.set(tileScale[0], tileScale[1]); + tex.anisotropy = 16; + // First texture is always the color map (use SRGB), others should be linear + tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace; }); + }); - if (!shape) return null; + if (!shape) return null; - return ( - { - if (!togglView && activeModule === 'builder') { - if (e.object.userData.floorUuid) { - e.stopPropagation(); - setSelectedFloor(e.object); - setSelectedDecal(null); - } - } - }} - onPointerMissed={() => { - if (selectedFloor && selectedFloor.userData.floorUuid === floor.floorUuid) { - setSelectedFloor(null); - } - }} - > - - - - - - ); + return ( + { + if (!togglView && activeModule === "builder") { + if (e.object.userData.floorUuid) { + e.stopPropagation(); + setSelectedFloor(e.object); + setSelectedDecal(null); + } + } + }} + onPointerMissed={() => { + if ( + selectedFloor && + selectedFloor.userData.floorUuid === floor.floorUuid + ) { + setSelectedFloor(null); + } + }} + > + + + + + + ); } -export default FloorInstance; \ No newline at end of file +export default FloorInstance; diff --git a/app/src/modules/builder/line/line.tsx b/app/src/modules/builder/line/line.tsx index c9f5e9b..474eb9e 100644 --- a/app/src/modules/builder/line/line.tsx +++ b/app/src/modules/builder/line/line.tsx @@ -43,6 +43,13 @@ function Line({ points }: Readonly) { const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore(); const { selectedPoints } = useSelectedPoints(); + const [initialPositions, setInitialPositions] = useState<{ + aisles?: Aisle[], + walls?: Wall[], + floors?: Floor[], + zones?: Zone[] + }>({}); + const path = useMemo(() => { const [start, end] = points.map(p => new THREE.Vector3(...p.position)); return new THREE.LineCurve3(start, end); @@ -353,6 +360,17 @@ function Line({ points }: Readonly) { const offset = new THREE.Vector3().subVectors(midPoint, hit); setDragOffset(offset); + + if (points[0].pointType === 'Wall') { + const walls = getWallsByPointId(points[0].pointUuid); + setInitialPositions({ walls }); + } else if (points[0].pointType === 'Floor') { + const floors = getFloorsByPointId(points[0].pointUuid); + setInitialPositions({ floors }); + } else if (points[0].pointType === 'Zone') { + const zones = getZonesByPointId(points[0].pointUuid); + setInitialPositions({ zones }); + } } }; @@ -370,9 +388,7 @@ function Line({ points }: Readonly) { // API - // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall).catch((error) => { - // console.error('Error updating wall:', error); - // }); + // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); // SOCKET @@ -386,6 +402,23 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Wall:add', data); }) + + if (initialPositions.walls && initialPositions.walls.length > 0) { + const updatedPoints = initialPositions.walls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + newData: updatedWalls.find(w => w.wallUuid === wall.wallUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } else if (points[0].pointType === 'Floor' && points[1].pointType === 'Floor') { const updatedFloors1 = getFloorsByPointId(points[0].pointUuid); @@ -397,9 +430,7 @@ function Line({ points }: Readonly) { // API - // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor).catch((error) => { - // console.error('Error updating floor:', error); - // }); + // upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor); // SOCKET @@ -413,6 +444,23 @@ function Line({ points }: Readonly) { socket.emit('v1:model-Floor:add', data); }) + + if (initialPositions.floors && initialPositions.floors.length > 0) { + const updatedPoints = initialPositions.floors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + newData: updatedFloors.find(f => f.floorUuid === floor.floorUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } else if (points[0].pointType === 'Zone' && points[1].pointType === 'Zone') { const updatedZones1 = getZonesByPointId(points[0].pointUuid); @@ -424,9 +472,7 @@ function Line({ points }: Readonly) { // API - // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone).catch((error) => { - // console.error('Error updating zone:', error); - // }); + // upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone); // SOCKET @@ -440,6 +486,23 @@ function Line({ points }: Readonly) { socket.emit('v1:zone:add', data); }) + + if (initialPositions.zones && initialPositions.zones.length > 0) { + const updatedPoints = initialPositions.zones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + newData: updatedZones.find(z => z.zoneUuid === zone.zoneUuid), + timeStamp: new Date().toISOString(), + })); + + push2D({ + type: 'Draw', + actions: [{ + actionType: 'Lines-Update', + points: updatedPoints, + }] + }); + } } } } diff --git a/app/src/modules/builder/point/point.tsx b/app/src/modules/builder/point/point.tsx index b203f05..cf44c26 100644 --- a/app/src/modules/builder/point/point.tsx +++ b/app/src/modules/builder/point/point.tsx @@ -631,7 +631,8 @@ function Point({ point }: { readonly point: Point }) { uuid={point.pointUuid} name={`${point.pointType}-Point`} position={[...point.position]} - onClick={() => { + onClick={(e) => { + e.stopPropagation(); handlePointClick(point); }} onPointerOver={(e) => { @@ -707,7 +708,6 @@ function Point({ point }: { readonly point: Point }) { - } ); diff --git a/app/src/modules/builder/wall/Instances/instance/wall.tsx b/app/src/modules/builder/wall/Instances/instance/wall.tsx index 1a73bfe..9023dc6 100644 --- a/app/src/modules/builder/wall/Instances/instance/wall.tsx +++ b/app/src/modules/builder/wall/Instances/instance/wall.tsx @@ -16,8 +16,10 @@ import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png'; import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg'; function Wall({ wall }: { readonly wall: Wall }) { - const { wallStore } = useSceneContext(); + const { wallStore, wallAssetStore } = useSceneContext(); const { walls, addDecal } = wallStore(); + const { wallAssets, getAssetsByWall } = wallAssetStore(); + const assets = getAssetsByWall(wall.wallUuid); const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore(); const { togglView } = useToggleView(); const { activeModule } = useModuleStore(); @@ -105,19 +107,35 @@ function Wall({ wall }: { readonly wall: Wall }) { key={wall.wallUuid} userData={wall} > - - {materials.map((material, index) => ( - - ))} - + {(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ? + + {materials.map((material, index) => ( + + ))} + + : + + {materials.map((material, index) => ( + + ))} + + } { try { // Check Cache @@ -114,11 +115,56 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { useEffect(() => { const canvasElement = gl.domElement; - const onPointerUp = () => { + const onPointerUp = (e: PointerEvent) => { draggingRef.current = false; if (controls) { (controls as any).enabled = true; } + + if (selectedWallAsset) { + pointer.x = (e.clientX / window.innerWidth) * 2 - 1; + pointer.y = -(e.clientY / window.innerHeight) * 2 + 1; + + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(scene.children, true); + const intersect = intersects.find((i: any) => i.object.name.includes('WallReference')); + + if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) { + const newPoint = closestPointOnLineSegment( + new THREE.Vector3(intersect.point.x, 0, intersect.point.z), + new THREE.Vector3(...intersect.object.userData.points[0].position), + new THREE.Vector3(...intersect.object.userData.points[1].position) + ); + + const wallRotation = intersect.object.rotation.clone(); + + const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, { + wallUuid: intersect.object.userData.wallUuid, + position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z], + rotation: [wallRotation.x, wallRotation.y, wallRotation.z], + }); + + if (projectId && updatedWallAsset) { + + // API + + // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset); + + // SOCKET + + const data = { + wallAssetData: updatedWallAsset, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + + socket.emit('v1:wall-asset:add', data); + + } + } + } }; const onPointerMove = (e: any) => { @@ -142,31 +188,11 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const wallRotation = intersect.object.rotation.clone(); - const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, { + updateWallAsset(wallAsset.modelUuid, { wallUuid: intersect.object.userData.wallUuid, position: [newPoint.x, wallAsset.wallAssetType === 'fixed-move' ? 0 : intersect.point.y, newPoint.z], rotation: [wallRotation.x, wallRotation.y, wallRotation.z], }); - - if (projectId && updatedWallAsset) { - - // API - - // upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset); - - // SOCKET - - const data = { - wallAssetData: updatedWallAsset, - projectId: projectId, - versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } - - socket.emit('v1:wall-asset:add', data); - - } } }; @@ -225,7 +251,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { } }, [activeTool, deletableWallAsset]); - if (!gltfScene || !boundingBox || !wall) { return null } + if (!gltfScene || !boundingBox) { return null } const size = new THREE.Vector3(); boundingBox.getSize(size); const center = new THREE.Vector3(); @@ -242,7 +268,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { visible={wallAsset.isVisible} userData={wallAsset} > - + diff --git a/app/src/modules/collaboration/camera/collabCams.tsx b/app/src/modules/collaboration/camera/collabCams.tsx index 2cf09b1..ec313ec 100644 --- a/app/src/modules/collaboration/camera/collabCams.tsx +++ b/app/src/modules/collaboration/camera/collabCams.tsx @@ -17,295 +17,292 @@ import setCameraView from "../functions/setCameraView"; import { getUserData } from "../../../functions/getUserData"; const CamModelsGroup = () => { - const navigate = useNavigate(); - const groupRef = useRef(null); - const { userId, organization, email } = getUserData(); - const { setActiveUsers } = useActiveUsers(); - const { socket } = useSocketStore(); - const { activeModule } = useModuleStore(); - const { selectedUser, setSelectedUser } = useSelectedUserStore(); - const { isPlaying } = usePlayButtonStore(); - // eslint-disable-next-line react-hooks/exhaustive-deps - const loader = new GLTFLoader(); - const dracoLoader = new DRACOLoader(); - dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); - loader.setDRACOLoader(dracoLoader); - - - const { camMode } = useCamMode(); - const { camera, controls } = useThree(); // Access R3F camera and controls - - useEffect(() => { - if (camMode !== "FollowPerson") return; - // If a user is selected, set the camera view to their location - // and update the camera and controls accordingly - if (selectedUser?.location) { - const { position, rotation, target } = selectedUser.location; - if (rotation && target) - setCameraView({ - controls, - camera, - position, - rotation, - target, - username: selectedUser.name, - }); - } - }, [selectedUser, camera, controls, camMode]); - - const [cams, setCams] = useState([]); - const [models, setModels] = useState< - Record< - string, - { - targetPosition: THREE.Vector3; - targetRotation: THREE.Euler; - target: THREE.Vector3; - } - > - >({}); - - const dedupeCams = (cams: any[]) => { - const seen = new Set(); - return cams.filter((cam) => { - if (seen.has(cam.uuid)) return false; - seen.add(cam.uuid); - return true; - }); - }; - - const dedupeUsers = (users: any[]) => { - const seen = new Set(); - return users.filter((user) => { - if (seen.has(user._id)) return false; - seen.add(user._id); - return true; - }); - }; - - useEffect(() => { - if (!email) navigate("/"); - - if (!socket) return; - - socket.on("userConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (data.data.userData.email === email) return; - if (socket.id === data.socketId || organization !== data.organization) - return; - - const model = groupRef.current.getObjectByProperty( - "uuid", - data.data.userData._id - ); - if (model) { - groupRef.current.remove(model); - } - - loader.load(camModel, (gltf) => { - const newModel = gltf.scene.clone(); - newModel.uuid = data.data.userData._id; - newModel.position.set( - data.data.position.x, - data.data.position.y, - data.data.position.z - ); - newModel.rotation.set( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ); - newModel.userData = data.data.userData; - newModel.userData.target = new THREE.Vector3( - data.data.target.x, - data.data.target.y, - data.data.target.z - ); - - setCams((prev) => dedupeCams([...prev, newModel])); - setActiveUsers((prev: any) => - dedupeUsers([...prev, data.data.userData]) - ); - }); - }); - - socket.on("userDisConnectResponse", (data: any) => { - if (!groupRef.current) return; - if (socket.id === data.socketId || organization !== data.organization) - return; - - setCams((prev) => - prev.filter((cam) => cam.uuid !== data.data.userData._id) - ); - setActiveUsers((prev: any) => - prev.filter((user: any) => user._id !== data.data.userData._id) - ); - }); - - socket.on("v1:camera:Response:update", (data: any) => { - // console.log('data: ', data); - if ( - !groupRef.current || - socket.id === data.socketId || - organization !== data.organization - ) - return; - - if (selectedUser && selectedUser?.id === data.data.userId) { - setSelectedUser({ - color: selectedUser.color, - name: selectedUser.name, - id: selectedUser.id, - location: { - position: data.data.position, - rotation: data.data.rotation, - target: data.data.target, - }, - }); - } - setModels((prev) => ({ - ...prev, - [data.data.userId]: { - targetPosition: new THREE.Vector3( - data.data.position.x, - data.data.position.y, - data.data.position.z - ), - targetRotation: new THREE.Euler( - data.data.rotation.x, - data.data.rotation.y, - data.data.rotation.z - ), - target: new THREE.Vector3( - data.data.target.x, - data.data.target.y, - data.data.target.z - ), - }, - })); - }); - - return () => { - socket.off("userConnectResponse"); - socket.off("userDisConnectResponse"); - socket.off("v1:camera:Response:update"); - }; + const navigate = useNavigate(); + const groupRef = useRef(null); + const { organization, email } = getUserData(); + const { setActiveUsers } = useActiveUsers(); + const { socket } = useSocketStore(); + const { activeModule } = useModuleStore(); + const { selectedUser, setSelectedUser } = useSelectedUserStore(); + const { isPlaying } = usePlayButtonStore(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [email, loader, navigate, setActiveUsers, socket]); + const loader = new GLTFLoader(); + const dracoLoader = new DRACOLoader(); + dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/"); + loader.setDRACOLoader(dracoLoader); - useFrame(() => { - if (!groupRef.current) return; - Object.keys(models).forEach((uuid) => { - const model = groupRef.current!.getObjectByProperty("uuid", uuid); - if (!model) return; + const { camMode } = useCamMode(); + const { camera, controls } = useThree(); - const { targetPosition, targetRotation } = models[uuid]; - model.position.lerp(targetPosition, 0.1); - model.rotation.x = THREE.MathUtils.lerp( - model.rotation.x, - targetRotation.x, - 0.1 - ); - model.rotation.y = THREE.MathUtils.lerp( - model.rotation.y, - targetRotation.y, - 0.1 - ); - model.rotation.z = THREE.MathUtils.lerp( - model.rotation.z, - targetRotation.z, - 0.1 - ); - }); - }); + useEffect(() => { + if (camMode !== "FollowPerson") return; + if (selectedUser?.location) { + const { position, rotation, target } = selectedUser.location; + if (rotation && target) + setCameraView({ + controls, + camera, + position, + rotation, + target, + username: selectedUser.name, + }); + } + }, [selectedUser, camera, controls, camMode]); - useEffect(() => { - if (!groupRef.current) return; - - getActiveUsersData(organization).then((data) => { - const filteredData = data.cameraDatas.filter( - (camera: any) => camera.userData.email !== email - ); - - if (filteredData.length > 0) { - loader.load(camModel, (gltf) => { - const newCams = filteredData.map((cam: any) => { - const newModel = gltf.scene.clone(); - newModel.uuid = cam.userData._id; - newModel.position.set( - cam.position.x, - cam.position.y, - cam.position.z - ); - newModel.rotation.set( - cam.rotation.x, - cam.rotation.y, - cam.rotation.z - ); - newModel.userData = cam.userData; - cam.userData.position = newModel.position; - cam.userData.rotation = newModel.rotation; - newModel.userData.target = cam.target; - - return newModel; - }); - - const users = filteredData.map((cam: any) => cam.userData); - setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); - setCams((prev) => dedupeCams([...prev, ...newCams])); - }); - } - }).catch(() => { - console.log('Error fetching active users data') - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - {cams.map((cam, index) => ( - ([]); + const [models, setModels] = useState< + Record< + string, + { + targetPosition: THREE.Vector3; + targetRotation: THREE.Euler; + target: THREE.Vector3; + } > - - - - - ))} - - ); + >({}); + + const dedupeCams = (cams: any[]) => { + const seen = new Set(); + return cams.filter((cam) => { + if (seen.has(cam.uuid)) return false; + seen.add(cam.uuid); + return true; + }); + }; + + const dedupeUsers = (users: any[]) => { + const seen = new Set(); + return users.filter((user) => { + if (seen.has(user._id)) return false; + seen.add(user._id); + return true; + }); + }; + + useEffect(() => { + if (!email) navigate("/"); + + if (!socket) return; + + socket.on("userConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (data.data.userData.email === email) return; + if (socket.id === data.socketId || organization !== data.organization) + return; + + const model = groupRef.current.getObjectByProperty( + "uuid", + data.data.userData._id + ); + if (model) { + groupRef.current.remove(model); + } + + loader.load(camModel, (gltf) => { + const newModel = gltf.scene.clone(); + newModel.uuid = data.data.userData._id; + newModel.position.set( + data.data.position.x, + data.data.position.y, + data.data.position.z + ); + newModel.rotation.set( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ); + newModel.userData = data.data.userData; + newModel.userData.target = new THREE.Vector3( + data.data.target.x, + data.data.target.y, + data.data.target.z + ); + + setCams((prev) => dedupeCams([...prev, newModel])); + setActiveUsers((prev: any) => + dedupeUsers([...prev, data.data.userData]) + ); + }); + }); + + socket.on("userDisConnectResponse", (data: any) => { + if (!groupRef.current) return; + if (socket.id === data.socketId || organization !== data.organization) + return; + + setCams((prev) => + prev.filter((cam) => cam.uuid !== data.data.userData._id) + ); + setActiveUsers((prev: any) => + prev.filter((user: any) => user._id !== data.data.userData._id) + ); + }); + + socket.on("v1:camera:Response:update", (data: any) => { + // console.log('data: ', data); + if ( + !groupRef.current || + socket.id === data.socketId || + organization !== data.organization + ) + return; + + if (selectedUser && selectedUser?.id === data.data.userId) { + setSelectedUser({ + color: selectedUser.color, + name: selectedUser.name, + id: selectedUser.id, + location: { + position: data.data.position, + rotation: data.data.rotation, + target: data.data.target, + }, + }); + } + setModels((prev) => ({ + ...prev, + [data.data.userId]: { + targetPosition: new THREE.Vector3( + data.data.position.x, + data.data.position.y, + data.data.position.z + ), + targetRotation: new THREE.Euler( + data.data.rotation.x, + data.data.rotation.y, + data.data.rotation.z + ), + target: new THREE.Vector3( + data.data.target.x, + data.data.target.y, + data.data.target.z + ), + }, + })); + }); + + return () => { + socket.off("userConnectResponse"); + socket.off("userDisConnectResponse"); + socket.off("v1:camera:Response:update"); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [email, loader, navigate, setActiveUsers, socket]); + + useFrame(() => { + if (!groupRef.current) return; + Object.keys(models).forEach((uuid) => { + const model = groupRef.current!.getObjectByProperty("uuid", uuid); + if (!model) return; + + const { targetPosition, targetRotation } = models[uuid]; + model.position.lerp(targetPosition, 0.1); + model.rotation.x = THREE.MathUtils.lerp( + model.rotation.x, + targetRotation.x, + 0.1 + ); + model.rotation.y = THREE.MathUtils.lerp( + model.rotation.y, + targetRotation.y, + 0.1 + ); + model.rotation.z = THREE.MathUtils.lerp( + model.rotation.z, + targetRotation.z, + 0.1 + ); + }); + }); + + useEffect(() => { + if (!groupRef.current) return; + + getActiveUsersData(organization).then((data) => { + const filteredData = data.cameraDatas.filter( + (camera: any) => camera.userData.email !== email + ); + + if (filteredData.length > 0) { + loader.load(camModel, (gltf) => { + const newCams = filteredData.map((cam: any) => { + const newModel = gltf.scene.clone(); + newModel.uuid = cam.userData._id; + newModel.position.set( + cam.position.x, + cam.position.y, + cam.position.z + ); + newModel.rotation.set( + cam.rotation.x, + cam.rotation.y, + cam.rotation.z + ); + newModel.userData = cam.userData; + cam.userData.position = newModel.position; + cam.userData.rotation = newModel.rotation; + newModel.userData.target = cam.target; + + return newModel; + }); + + const users = filteredData.map((cam: any) => cam.userData); + setActiveUsers((prev: any) => dedupeUsers([...prev, ...users])); + setCams((prev) => dedupeCams([...prev, ...newCams])); + }); + } + }).catch(() => { + console.log('Error fetching active users data') + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + {cams.map((cam, index) => ( + + + + + + ))} + + ); }; export default CamModelsGroup; diff --git a/app/src/modules/scene/camera/camMode.tsx b/app/src/modules/scene/camera/camMode.tsx index 2f03c97..77e2f31 100644 --- a/app/src/modules/scene/camera/camMode.tsx +++ b/app/src/modules/scene/camera/camMode.tsx @@ -9,124 +9,89 @@ import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKe import { firstPersonCamera } from "./firstPersonCamera"; const CamMode: React.FC = () => { - const { camMode, setCamMode } = useCamMode(); - const [, get] = useKeyboardControls(); - const [isTransitioning, setIsTransitioning] = useState(false); - const state: any = useThree(); - const { toggleView } = useToggleView(); - const [isShiftActive, setIsShiftActive] = useState(false); + const { camMode, setCamMode } = useCamMode(); + const [_, get] = useKeyboardControls(); + const [isTransitioning, setIsTransitioning] = useState(false); + const state: any = useThree(); + const { toggleView } = useToggleView(); + const [isShiftActive, setIsShiftActive] = useState(false); - useEffect(() => { - const handlePointerLockChange = async () => { - if (document.pointerLockElement && !toggleView) { - // Pointer is locked - } else if (camMode === "FirstPerson" && !toggleView) { - // Pointer is unlocked - setCamMode("ThirdPerson"); - await switchToThirdPerson(state.controls, state.camera); - } - }; + useEffect(() => { + const handlePointerLockChange = async () => { + if (document.pointerLockElement && !toggleView) { + } else if (camMode === "FirstPerson" && !toggleView) { + setCamMode("ThirdPerson"); + await switchToThirdPerson(state.controls, state.camera); + } + }; - document.addEventListener("pointerlockchange", handlePointerLockChange); + document.addEventListener("pointerlockchange", handlePointerLockChange); - return () => { - document.removeEventListener( - "pointerlockchange", - handlePointerLockChange - ); - }; - }, [camMode, toggleView, setCamMode, state.controls, state.camera]); + return () => { + document.removeEventListener("pointerlockchange", handlePointerLockChange); + }; + }, [camMode, toggleView, setCamMode, state.controls, state.camera]); - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "Shift") { - setIsShiftActive(true); - } - }; + useEffect(() => { + const handleKeyPress = async (event: KeyboardEvent) => { + if (!state.controls) return; - const handleKeyUp = (event: KeyboardEvent) => { - if (event.key === "Shift") { - setIsShiftActive(false); - } - }; + const keyCombination = detectModifierKeys(event); - window.addEventListener("keydown", handleKeyDown); - window.addEventListener("keyup", handleKeyUp); + if (keyCombination === "/" && !isTransitioning && !toggleView) { + firstPersonCamera({ + setIsTransitioning, + state, + camMode, + setCamMode, + switchToFirstPerson, + switchToThirdPerson, + }); + } - return () => { - window.removeEventListener("keydown", handleKeyDown); - window.removeEventListener("keyup", handleKeyUp); - }; - }, []); + if (keyCombination === 'Shift') { + setIsShiftActive(true); + } + }; - useEffect(() => { - const handleKeyPress = async (event: KeyboardEvent) => { - if (!state.controls) return; + const handleKeyUp = (event: KeyboardEvent) => { + if (event.key === "Shift") { + setIsShiftActive(false); + } + }; - const keyCombination = detectModifierKeys(event); + window.addEventListener("keydown", handleKeyPress); + window.addEventListener("keyup", handleKeyUp); - if (keyCombination === "/" && !isTransitioning && !toggleView) { - firstPersonCamera({ - setIsTransitioning, - state, - camMode, - setCamMode, - switchToFirstPerson, - switchToThirdPerson, - }); - } - }; + return () => { + window.removeEventListener("keydown", handleKeyPress); + window.removeEventListener("keyup", handleKeyUp); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [camMode, isTransitioning, toggleView, state.controls, state.camera, setCamMode]); - window.addEventListener("keydown", handleKeyPress); - return () => { - window.removeEventListener("keydown", handleKeyPress); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - camMode, - isTransitioning, - toggleView, - state.controls, - state.camera, - setCamMode, - ]); + useFrame(() => { + const { forward, backward, left, right } = get(); + if (!state.controls) return; + if (camMode === "ThirdPerson" || !document.pointerLockElement) return; - useFrame(() => { - const { forward, backward, left, right } = get(); - if (!state.controls) return; - if (camMode === "ThirdPerson" || !document.pointerLockElement) return; + const speedMultiplier = isShiftActive ? 4 : 1; - const speedMultiplier = isShiftActive ? 4 : 1; + if (forward) { + state.controls.forward(CONSTANTS.firstPersonControls.forwardSpeed * speedMultiplier, true); + } + if (backward) { + state.controls.forward(CONSTANTS.firstPersonControls.backwardSpeed * speedMultiplier, true); + } + if (left) { + state.controls.truck(CONSTANTS.firstPersonControls.leftSpeed * speedMultiplier, 0, true); + } + if (right) { + state.controls.truck(CONSTANTS.firstPersonControls.rightSpeed * speedMultiplier, 0, true); + } + }); - if (forward) { - state.controls.forward( - CONSTANTS.firstPersonControls.forwardSpeed * speedMultiplier, - true - ); - } - if (backward) { - state.controls.forward( - CONSTANTS.firstPersonControls.backwardSpeed * speedMultiplier, - true - ); - } - if (left) { - state.controls.truck( - CONSTANTS.firstPersonControls.leftSpeed * speedMultiplier, - 0, - true - ); - } - if (right) { - state.controls.truck( - CONSTANTS.firstPersonControls.rightSpeed * speedMultiplier, - 0, - true - ); - } - }); - - return null; // This component does not render any UI + return null; }; export default CamMode; diff --git a/app/src/modules/scene/camera/switchView.tsx b/app/src/modules/scene/camera/switchView.tsx index ce64e1f..b9501b3 100644 --- a/app/src/modules/scene/camera/switchView.tsx +++ b/app/src/modules/scene/camera/switchView.tsx @@ -1,73 +1,68 @@ -import * as THREE from "three"; -import { useEffect, useRef } from "react"; -import { useToggleView } from "../../../store/builder/store"; +import { useEffect } from "react"; import { useThree } from "@react-three/fiber"; -import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"; -import * as CONSTANTS from '../../../types/world/worldConstants'; +import * as THREE from 'three'; +import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei'; import { useParams } from "react-router-dom"; +import * as CONSTANTS from '../../../types/world/worldConstants'; +import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi"; import { getUserData } from "../../../functions/getUserData"; -import { CameraControls } from "@react-three/drei"; +import { useToggleView } from "../../../store/builder/store"; export default function SwitchView() { - const { toggleView } = useToggleView(); - const state: any = useThree(); - const { set } = useThree(); - const perspectiveCamera = useRef(null); - const orthoCamera = useRef(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); - const { projectId } = useParams(); - const { organization } = getUserData(); + const { toggleView } = useToggleView(); + const { controls } = useThree(); + const { projectId } = useParams(); + const { organization } = getUserData(); - 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 && controls) { + (controls as any).mouseButtons.left = CONSTANTS.twoDimension.leftMouse; + (controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse; + } else { + try { + getCamera(organization, localStorage.getItem('userId')!, projectId).then((data) => { + if (data && data.position && data.target) { + (controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z); + (controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z); + } else { + (controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition); + (controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget); + } + }); + } catch (error) { + echo.error("Failed to retrieve camera position or target"); + (controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition); + (controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget); + } - useEffect(() => { - if (toggleView && state.controls) { - state.controls.mouseButtons.left = CONSTANTS.twoDimension.leftMouse; - state.controls.mouseButtons.right = CONSTANTS.twoDimension.rightMouse; - } else { - try { - getCamera(organization, localStorage.getItem('userId')!, projectId).then((data) => { - if (data && data.position && data.target) { - // state.controls?.setLookAt(data.position.x, data.position.y, data.position.z, data.target.x, data.target.y, data.target.z, true) - state.controls?.setPosition(data.position.x, data.position.y, data.position.z); - state.controls?.setTarget(data.target.x, data.target.y, data.target.z); - } else { - // state.controls?.setLookAt(...CONSTANTS.threeDimension.defaultPosition, ...CONSTANTS.threeDimension.defaultTarget, true); - state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition); - state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget); - } - }); - } catch (error) { - echo.error("Failed to retrieve camera position or target"); - console.error("Failed to retrieve camera position or target:", error); - // state.controls?.setLookAt(...CONSTANTS.threeDimension.defaultPosition, ...CONSTANTS.threeDimension.defaultTarget, true); - state.controls?.setPosition(...CONSTANTS.threeDimension.defaultPosition); - state.controls?.setTarget(...CONSTANTS.threeDimension.defaultTarget); - } + if (controls) { + (controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse; + (controls as any).mouseButtons.right = CONSTANTS.threeDimension.rightMouse; + } + } + }, [toggleView, controls]); - if (state.controls) { - state.controls.mouseButtons.left = CONSTANTS.threeDimension.leftMouse; - state.controls.mouseButtons.right = CONSTANTS.threeDimension.rightMouse; - } - } - }, [toggleView, state.controls]); - - return ( - <> - ); + return ( + <> + {toggleView ? ( + self.lookAt(new THREE.Vector3(...CONSTANTS.twoDimension.defaultTarget))} + /> + ) : ( + self.lookAt(new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget))} + /> + )} + + ); } \ No newline at end of file diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index 77f234c..79b9c9f 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -13,16 +13,17 @@ import SelectionControls3D from "./selectionControls/selection3D/selectionContro import TransformControl from "./transformControls/transformControls"; import { useParams } from "react-router-dom"; import { getUserData } from "../../../functions/getUserData"; + import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; export default function Controls() { const controlsRef = useRef(null); + const state = useThree(); const { toggleView } = useToggleView(); const { resetCamera, setResetCamera } = useResetCamera(); const { socket } = useSocketStore(); - const state = useThree(); const { projectId } = useParams(); const { userId, organization } = getUserData(); @@ -33,7 +34,6 @@ export default function Controls() { } getCamera(organization, userId, projectId).then((data) => { - // console.log('data: ', 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); @@ -41,8 +41,7 @@ export default function Controls() { controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition); controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget); } - }) - .catch((error) => console.error("Failed to fetch camera data:", error)); + }).catch((error) => console.error("Failed to fetch camera data:", error)); }, []); useEffect(() => { @@ -60,7 +59,6 @@ export default function Controls() { socketId: socket.id, projectId }; - // console.log('CameracamData: ', camData); socket.emit('v1:Camera:set', camData) setResetCamera(false); @@ -128,9 +126,10 @@ export default function Controls() { maxZoom={CONSTANTS.thirdPersonControls.maxZoom} maxPolarAngle={CONSTANTS.thirdPersonControls.maxPolarAngle} camera={state.camera} + dollyToCursor={toggleView} verticalDragToForward={true} boundaryEnclosesCamera={true} - dollyToCursor={toggleView} + dollyDragInverted > diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx index fa12f61..f6d04f0 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx @@ -36,11 +36,12 @@ function MoveControls2D({ const { projectId } = useParams(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); - const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); - const { setPosition: setAislePosition, getAislesByPointId } = aisleStore(); - const { setPosition: setWallPosition, getWallsByPointId } = wallStore(); - const { setPosition: setFloorPosition, getFloorsByPointId } = floorStore(); - const { setPosition: setZonePosition, getZonesByPointId } = zoneStore(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); + const { setPosition: setAislePosition, getAislesByPointId, getAisleById } = aisleStore(); + const { setPosition: setWallPosition, getWallsByPointId, getWallById } = wallStore(); + const { setPosition: setFloorPosition, getFloorsByPointId, getFloorById } = floorStore(); + const { setPosition: setZonePosition, getZonesByPointId, getZoneById } = zoneStore(); const [dragOffset, setDragOffset] = useState(null); const [initialPositions, setInitialPositions] = useState>({}); const [initialStates, setInitialStates] = useState>({}); @@ -223,6 +224,12 @@ function MoveControls2D({ const placeMovedAssets = () => { if (movedObjects.length === 0) return; + const undoPoints: UndoRedo2DDataTypeSchema[] = []; + const processedAisles: UndoRedo2DDataTypeSchema[] = []; + const processedWalls: UndoRedo2DDataTypeSchema[] = []; + const processedFloors: UndoRedo2DDataTypeSchema[] = []; + const processedZones: UndoRedo2DDataTypeSchema[] = []; + movedObjects.forEach((movedObject: THREE.Object3D) => { if (movedObject.userData.pointUuid) { const point: Point = movedObject.userData as Point; @@ -236,45 +243,84 @@ function MoveControls2D({ // upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || ''); - // SOCKET + // SOCKET socket.emit('v1:model-aisle:add', { - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization, + userId, + organization, aisleUuid: updatedAisle.aisleUuid, points: updatedAisle.points, type: updatedAisle.type - }) - }) + }); + + const old = initialStates[movedObject.uuid]; + if (old) { + processedAisles.push({ + type: 'Aisle', + lineData: { + ...updatedAisle, + points: [ + updatedAisle.points[0].pointUuid === point.pointUuid + ? { ...updatedAisle.points[0], position: [old.position.x, old.position.y, old.position.z] } + : updatedAisle.points[0], + updatedAisle.points[1].pointUuid === point.pointUuid + ? { ...updatedAisle.points[1], position: [old.position.x, old.position.y, old.position.z] } + : updatedAisle.points[1] + ] as [Point, Point], + }, + newData: updatedAisle, + timeStamp: new Date().toISOString(), + }); + } + }); } } else if (point.pointType === 'Wall') { const updatedWalls = getWallsByPointId(point.pointUuid); - if (updatedWalls && updatedWalls.length > 0 && projectId) { - updatedWalls.forEach((updatedWall) => { + if (updatedWalls?.length && projectId) { + updatedWalls.forEach(updatedWall => { // API // upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall); - // SOCKET + // SOCKET - const data = { + socket.emit('v1:model-Wall:add', { wallData: updatedWall, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:model-Wall:add', data); + const old = initialStates[movedObject.uuid]; + if (old) { + processedWalls.push({ + type: 'Wall', + lineData: { + ...updatedWall, + points: [ + updatedWall.points[0].pointUuid === point.pointUuid + ? { ...updatedWall.points[0], position: [old.position.x, old.position.y, old.position.z] } + : updatedWall.points[0], + updatedWall.points[1].pointUuid === point.pointUuid + ? { ...updatedWall.points[1], position: [old.position.x, old.position.y, old.position.z] } + : updatedWall.points[1] + ] as [Point, Point], + }, + newData: updatedWall, + timeStamp: new Date().toISOString(), + }); + } }); } } else if (point.pointType === 'Floor') { + const Floors = getFloorsByPointId(point.pointUuid); const updatedFloors = getFloorsByPointId(point.pointUuid); - if (updatedFloors && updatedFloors.length > 0 && projectId) { - updatedFloors.forEach((updatedFloor) => { + if (updatedFloors?.length && projectId) { + updatedFloors.forEach(updatedFloor => { // API @@ -282,21 +328,38 @@ function MoveControls2D({ // SOCKET - const data = { + socket.emit('v1:model-Floor:add', { floorData: updatedFloor, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:model-Floor:add', data); + const updatedFloorsData = updatedFloors.map((floor) => { + const originalFloor = Floors.find(f => f.floorUuid === floor.floorUuid) || floor; + + const updatedPoints = originalFloor.points.map((pt: Point) => { + const init = initialStates[pt.pointUuid]; + return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt; + }) as [Point, Point]; + + return { + type: "Floor" as const, + lineData: { ...originalFloor, points: updatedPoints }, + newData: floor, + timeStamp: new Date().toISOString(), + }; + }); + + processedFloors.push(...updatedFloorsData); }); } } else if (point.pointType === 'Zone') { + const Zones = getZonesByPointId(point.pointUuid); const updatedZones = getZonesByPointId(point.pointUuid); - if (updatedZones && updatedZones.length > 0 && projectId) { - updatedZones.forEach((updatedZone) => { + if (updatedZones?.length && projectId) { + updatedZones.forEach(updatedZone => { // API @@ -304,23 +367,160 @@ function MoveControls2D({ // SOCKET - const data = { + socket.emit('v1:zone:add', { zoneData: updatedZone, - projectId: projectId, + projectId, versionId: selectedVersion?.versionId || '', - userId: userId, - organization: organization - } + userId, + organization + }); - socket.emit('v1:zone:add', data); + const updatedZonesData = updatedZones.map((zone) => { + const originalZone = Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone; + + const updatedPoints = originalZone.points.map((pt: Point) => { + const init = initialStates[pt.pointUuid]; + return init ? { ...pt, position: [init.position.x, init.position.y, init.position.z] } : pt; + }) as [Point, Point]; + + return { + type: "Zone" as const, + lineData: { ...originalZone, points: updatedPoints }, + newData: zone, + timeStamp: new Date().toISOString(), + }; + }); + + processedZones.push(...updatedZonesData); }); } } } - }) + }); + + setTimeout(() => { + if (processedWalls.length > 0) { + const wallMap = new Map(); + + for (const wall of processedWalls) { + if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue; + const uuid = wall.lineData.wallUuid; + if (!wallMap.has(uuid)) wallMap.set(uuid, []); + wallMap.get(uuid)!.push(wall); + } + + wallMap.forEach((actions, uuid) => { + const hasUpdate = actions.some(action => 'newData' in action); + if (hasUpdate) { + const wallData = getWallById(uuid); + if (wallData) { + undoPoints.push({ + type: 'Wall', + lineData: actions[0].lineData as Wall, + newData: wallData as Wall, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (processedAisles.length > 0) { + const aisleMap = new Map(); + + for (const aisle of processedAisles) { + if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue; + const uuid = aisle.lineData.aisleUuid; + if (!aisleMap.has(uuid)) aisleMap.set(uuid, []); + aisleMap.get(uuid)!.push(aisle); + } + + aisleMap.forEach((actions, uuid) => { + const hasUpdate = actions.some(action => 'newData' in action); + if (hasUpdate) { + const aisleData = getAisleById(uuid); + if (aisleData) { + undoPoints.push({ + type: 'Aisle', + lineData: actions[0].lineData as Aisle, + newData: aisleData as Aisle, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (processedFloors.length > 0) { + const floorMap = new Map(); + + for (const floor of processedFloors) { + if (floor.type !== 'Floor' || !floor.lineData.floorUuid) continue; + const uuid = floor.lineData.floorUuid; + if (!floorMap.has(uuid)) { + floorMap.set(uuid, []); + } + floorMap.get(uuid)!.push(floor); + } + + floorMap.forEach((actions, uuid) => { + const hasUpdate = actions.some(action => 'newData' in action); + if (hasUpdate) { + const floorData = getFloorById(uuid); + if (floorData) { + undoPoints.push({ + type: 'Floor', + lineData: actions[0].lineData as Floor, + newData: floorData as Floor, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (processedZones.length > 0) { + const zoneMap = new Map(); + + for (const zone of processedZones) { + if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) continue; + const uuid = zone.lineData.zoneUuid; + if (!zoneMap.has(uuid)) { + zoneMap.set(uuid, []); + } + zoneMap.get(uuid)!.push(zone); + } + + zoneMap.forEach((actions, uuid) => { + const hasUpdate = actions.some(action => 'newData' in action); + if (hasUpdate) { + const zoneData = getZoneById(uuid); + if (zoneData) { + undoPoints.push({ + type: 'Zone', + lineData: actions[0].lineData as Zone, + newData: zoneData as Zone, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (undoPoints.length > 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: undoPoints + } + ] + }); + } + }, 0); echo.success("Object moved!"); - clearSelection(); }; diff --git a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx index 9d90d3c..3ca6f99 100644 --- a/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection2D/selectionControls2D.tsx @@ -22,7 +22,7 @@ import MoveControls2D from "./moveControls2D"; // import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi"; const SelectionControls2D: React.FC = () => { - const { camera, controls, gl, scene, raycaster, pointer } = useThree(); + const { camera, controls, gl, scene, pointer } = useThree(); const { toggleView } = useToggleView(); const { selectedPoints, setSelectedPoints, clearSelectedPoints } = useSelectedPoints(); const [movedObjects, setMovedObjects] = useState([]); @@ -38,11 +38,12 @@ const SelectionControls2D: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { hoveredLine, hoveredPoint } = useBuilderStore(); - const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext(); + const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore } = useSceneContext(); + const { push2D } = undoRedo2DStore(); const { removePoint: removeAislePoint } = aisleStore(); const { removePoint: removeWallPoint } = wallStore(); - const { removePoint: removeFloorPoint } = floorStore(); - const { removePoint: removeZonePoint } = zoneStore(); + const { removePoint: removeFloorPoint, getFloorsByPointId, getFloorById } = floorStore(); + const { removePoint: removeZonePoint, getZonesByPointId, getZoneById } = zoneStore(); const isDragging = useRef(false); const isLeftMouseDown = useRef(false); @@ -223,6 +224,13 @@ const SelectionControls2D: React.FC = () => { const deleteSelection = () => { if (selectedPoints.length > 0 && duplicatedObjects.length === 0) { + const deletedPoints: UndoRedo2DDataTypeSchema[] = []; + const updatedPoints: UndoRedo2DDataTypeSchema[] = []; + const processedAisles: UndoRedo2DDataTypeSchema[] = []; + const processedWalls: UndoRedo2DDataTypeSchema[] = []; + const processedFloors: UndoRedo2DDataTypeSchema[] = []; + const processedZones: UndoRedo2DDataTypeSchema[] = []; + selectedPoints.forEach((selectedPoint) => { if (selectedPoint.userData.pointUuid) { const point: Point = selectedPoint.userData as Point; @@ -249,6 +257,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-aisle:delete', data); } }); + + const removedAislesData = removedAisles.map((aisle) => ({ + type: "Aisle" as const, + lineData: aisle, + timeStamp: new Date().toISOString(), + })); + + processedAisles.push(...removedAislesData); } } if (point.pointType === 'Wall') { @@ -274,9 +290,18 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Wall:delete', data); } }); + + const removedWallsData = removedWalls.map((wall) => ({ + type: "Wall" as const, + lineData: wall, + timeStamp: new Date().toISOString(), + })); + + processedWalls.push(...removedWallsData); } } if (point.pointType === 'Floor') { + const Floors = getFloorsByPointId(point.pointUuid); const { removedFloors, updatedFloors } = removeFloorPoint(point.pointUuid); if (removedFloors.length > 0) { removedFloors.forEach(floor => { @@ -299,6 +324,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Floor:delete', data); } }); + + const removedFloorsData = removedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: floor, + timeStamp: new Date().toISOString(), + })); + + processedFloors.push(...removedFloorsData); } if (updatedFloors.length > 0) { updatedFloors.forEach(floor => { @@ -321,9 +354,19 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:model-Floor:add', data); } }); + + const updatedFloorsData = updatedFloors.map((floor) => ({ + type: "Floor" as const, + lineData: Floors.find(f => f.floorUuid === floor.floorUuid) || floor, + newData: floor, + timeStamp: new Date().toISOString(), + })); + + processedFloors.push(...updatedFloorsData); } } if (point.pointType === 'Zone') { + const Zones = getZonesByPointId(point.pointUuid); const { removedZones, updatedZones } = removeZonePoint(point.pointUuid); if (removedZones.length > 0) { removedZones.forEach(zone => { @@ -346,6 +389,14 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:zone:delete', data); } }); + + const removedZonesData = removedZones.map((zone) => ({ + type: "Zone" as const, + lineData: zone, + timeStamp: new Date().toISOString(), + })); + + processedZones.push(...removedZonesData); } if (updatedZones.length > 0) { updatedZones.forEach(zone => { @@ -368,11 +419,173 @@ const SelectionControls2D: React.FC = () => { socket.emit('v1:zone:add', data); } }); + + const updatedZonesData = updatedZones.map((zone) => ({ + type: "Zone" as const, + lineData: Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone, + newData: zone, + timeStamp: new Date().toISOString(), + })); + + processedZones.push(...updatedZonesData); } } } }) + setTimeout(() => { + if (processedWalls.length > 0) { + const wallMap = new Map(); + + for (const wall of processedWalls) { + if (wall.type !== 'Wall' || !wall.lineData.wallUuid) continue; + const uuid = wall.lineData.wallUuid; + if (!wallMap.has(uuid)) wallMap.set(uuid, []); + wallMap.get(uuid)!.push(wall); + } + + wallMap.forEach((actions) => { + const hasDelete = actions.some(action => !('newData' in action)); + if (hasDelete) { + deletedPoints.push({ + type: 'Wall', + lineData: actions[0].lineData as Wall, + timeStamp: new Date().toISOString() + }); + } + }); + } + + if (processedAisles.length > 0) { + const aisleMap = new Map(); + + for (const aisle of processedAisles) { + if (aisle.type !== 'Aisle' || !aisle.lineData.aisleUuid) continue; + const uuid = aisle.lineData.aisleUuid; + if (!aisleMap.has(uuid)) aisleMap.set(uuid, []); + aisleMap.get(uuid)!.push(aisle); + } + + aisleMap.forEach((actions) => { + const hasDelete = actions.some(action => !('newData' in action)); + if (hasDelete) { + deletedPoints.push({ + type: 'Aisle', + lineData: actions[0].lineData as Aisle, + timeStamp: new Date().toISOString() + }); + } + }); + } + + if (processedFloors.length > 0) { + const floorMap = new Map(); + + for (const floor of processedFloors) { + if (floor.type !== 'Floor' || !floor.lineData.floorUuid) return; + const uuid = floor.lineData.floorUuid; + if (!floorMap.has(uuid)) { + floorMap.set(uuid, []); + } + floorMap.get(uuid)!.push(floor); + } + + floorMap.forEach((actions, uuid) => { + const hasDelete = actions.some(action => !('newData' in action)); + const hasUpdate = actions.some(action => 'newData' in action); + + if (hasDelete) { + deletedPoints.push({ + type: 'Floor', + lineData: actions[0].lineData as Floor, + timeStamp: new Date().toISOString() + }); + } else if (!hasDelete && hasUpdate) { + const floorData = getFloorById(uuid); + if (floorData) { + updatedPoints.push({ + type: 'Floor', + lineData: actions[0].lineData as Floor, + newData: floorData as Floor, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (processedZones.length > 0) { + const zoneMap = new Map(); + + for (const zone of processedZones) { + if (zone.type !== 'Zone' || !zone.lineData.zoneUuid) return; + const uuid = zone.lineData.zoneUuid; + if (!zoneMap.has(uuid)) { + zoneMap.set(uuid, []); + } + zoneMap.get(uuid)!.push(zone); + } + + zoneMap.forEach((actions, uuid) => { + const hasDelete = actions.some(action => !('newData' in action)); + const hasUpdate = actions.some(action => 'newData' in action); + + if (hasDelete) { + deletedPoints.push({ + type: 'Zone', + lineData: actions[0].lineData as Zone, + timeStamp: new Date().toISOString() + }); + } else if (!hasDelete && hasUpdate) { + const zoneData = getZoneById(uuid); + if (zoneData) { + updatedPoints.push({ + type: 'Zone', + lineData: actions[0].lineData as Zone, + newData: zoneData as Zone, + timeStamp: new Date().toISOString() + }); + } + } + }); + } + + if (deletedPoints.length > 0 && updatedPoints.length > 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: deletedPoints + }, + { + actionType: 'Lines-Update', + points: updatedPoints + } + ] + }); + } else if (deletedPoints.length > 0 && updatedPoints.length === 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Delete', + points: deletedPoints + } + ] + }); + } else if (updatedPoints.length > 0 && deletedPoints.length === 0) { + push2D({ + type: 'Draw', + actions: [ + { + actionType: 'Lines-Update', + points: updatedPoints + } + ] + }); + } + }, 0); } echo.success("Selected points removed!"); clearSelection(); @@ -380,6 +593,7 @@ const SelectionControls2D: React.FC = () => { return ( <> + diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index eb8bdae..c72da8b 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -333,6 +333,7 @@ const SelectionControls3D: React.FC = () => { return ( <> + @@ -340,6 +341,7 @@ const SelectionControls3D: React.FC = () => { + ); }; diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts index e5f31a5..99902c1 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useRedoHandler.ts @@ -296,7 +296,7 @@ function useRedoHandler() { // SOCKET const data = { - aisleData, + ...aisleData, projectId, versionId: selectedVersion?.versionId || '', userId, @@ -338,7 +338,7 @@ function useRedoHandler() { // SOCKET const data = { - aisleData: updatedData, + ...updatedData, projectId, versionId: selectedVersion?.versionId || '', userId, diff --git a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts index 357f36e..c822ef9 100644 --- a/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts +++ b/app/src/modules/scene/controls/undoRedoControls/handlers/useUndoHandler.ts @@ -297,7 +297,7 @@ function useUndoHandler() { // SOCKET const data = { - aisleData, + ...aisleData, projectId, versionId: selectedVersion?.versionId || '', userId, @@ -339,7 +339,7 @@ function useUndoHandler() { // SOCKET const data = { - aisleData: updatedData, + ...updatedData, projectId, versionId: selectedVersion?.versionId || '', userId, diff --git a/app/src/modules/scene/environment/sky.tsx b/app/src/modules/scene/environment/sky.tsx index 4a584ee..ee7f2d9 100644 --- a/app/src/modules/scene/environment/sky.tsx +++ b/app/src/modules/scene/environment/sky.tsx @@ -6,9 +6,9 @@ import * as CONSTANTS from '../../../types/world/worldConstants'; export default function Sun() { const savedTheme: string | null = localStorage.getItem("theme"); - const { elevation, setElevation } = useElevation(); - const { sunPosition, setSunPosition } = useSunPosition(); - const { azimuth, setAzimuth } = useAzimuth(); + const { elevation } = useElevation(); + const { setSunPosition } = useSunPosition(); + const { azimuth } = useAzimuth(); const [turbidity, setTurbidity] = useState(CONSTANTS.skyConfig.defaultTurbidity); const sunPositionRef = useRef(new THREE.Vector3(0, 0, 0)); const [_, forceUpdate] = useState(0); diff --git a/app/src/modules/scene/postProcessing/postProcessing.tsx b/app/src/modules/scene/postProcessing/postProcessing.tsx index c70f733..8aacd96 100644 --- a/app/src/modules/scene/postProcessing/postProcessing.tsx +++ b/app/src/modules/scene/postProcessing/postProcessing.tsx @@ -1,4 +1,5 @@ -import { EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; +import { DepthOfField, Bloom, EffectComposer, N8AO, Outline } from "@react-three/postprocessing"; +import { useThree } from "@react-three/fiber"; import { BlendFunction } from "postprocessing"; import { useDeletableFloorItem, @@ -11,6 +12,7 @@ import { useEffect } from "react"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; export default function PostProcessing() { + const { scene } = useThree(); const { selectedPoints } = useSelectedPoints(); const { deletableFloorItem } = useDeletableFloorItem(); const { selectedWallItem } = useSelectedWallItem(); @@ -77,6 +79,17 @@ export default function PostProcessing() { denoiseRadius={6} denoiseSamples={16} /> + {/* */} + {/* */} {selectedWallAsset && ( [ @@ -54,6 +54,7 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co }).catch((err) => { console.error(err); }); + // eslint-disable-next-line }, [activeModule, assets, loadingProgress]) return ( @@ -66,10 +67,11 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co onContextMenu={(e) => { e.preventDefault(); }} + performance={{ min: 0.9, max: 1.0 }} onCreated={(e) => { e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d); }} - gl={{ powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} + gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} > @@ -81,7 +83,8 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co + ); -} \ No newline at end of file +} diff --git a/app/src/modules/scene/setup/setup.tsx b/app/src/modules/scene/setup/setup.tsx index 9e06d3f..d51d8af 100644 --- a/app/src/modules/scene/setup/setup.tsx +++ b/app/src/modules/scene/setup/setup.tsx @@ -2,7 +2,7 @@ import Sun from '../environment/sky' import Shadows from '../environment/shadow' import PostProcessing from '../postProcessing/postProcessing' import Controls from '../controls/controls'; -import { Environment } from '@react-three/drei' +import { AdaptiveDpr, AdaptiveEvents, Environment } from '@react-three/drei' import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr"; @@ -24,8 +24,12 @@ function Setup() { {/* */} + + + + ) } -export default Setup \ No newline at end of file +export default Setup; diff --git a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx index 1f4dee8..1afc206 100644 --- a/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx +++ b/app/src/modules/simulation/spatialUI/arm/armBotUI.tsx @@ -55,7 +55,6 @@ const ArmBotUI = () => { }) } - // Fetch and setup selected ArmBot data useEffect(() => { if (selectedEventSphere) { const selectedArmBot = getEventByModelUuid(selectedProduct.productUuid, selectedEventSphere.userData.modelUuid); @@ -86,13 +85,11 @@ const ArmBotUI = () => { const modelData = getEventByModelUuid(selectedProduct.productUuid, modelUuid); if (modelData?.type === "roboticArm") { - const baseX = modelData.point.position?.[0] || 0; - const baseY = modelData.point.position?.[1] || 0;; - const baseZ = modelData.point.position?.[2] || 0; + const baseY = modelData.point.position?.[1] || 0; return { - pick: [baseX, baseY, baseZ + 0.5], - drop: [baseX, baseY, baseZ - 0.5], - default: [baseX, baseY, baseZ], + pick: [0, baseY, 0 + 0.5], + drop: [0, baseY, 0 - 0.5], + default: [0, baseY, 0], }; } @@ -225,7 +222,7 @@ const ArmBotUI = () => { ); } else { - return null; // important! must return something + return null; } })} diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index 19e9482..57f8ab2 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -40,7 +40,6 @@ export default function PolygonGenerator({ aisle.type.aisleType === "arc-aisle" ) - arcAndCircleResult.forEach((arc) => { const arcGroup = scene.getObjectByProperty("uuid", arc.aisleUuid); if (!arcGroup) return; @@ -60,7 +59,6 @@ export default function PolygonGenerator({ }); }); - const wallPoints: THREE.Vector3[][] = walls .map((wall) => wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2])) diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 1ab5888..4db9e8b 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -74,10 +74,10 @@ border-radius: #{$border-radius-large}; outline: 1px solid var(--border-color); } - .upload-btn{ + .upload-btn { padding: 4px 16px !important; } - .generate-walls-btn{ + .generate-walls-btn { padding: 4px 16px; @include flex-center; gap: 4px; @@ -117,9 +117,20 @@ } } } - - } -.label-toogler.bottom{ +.label-toogler.bottom { bottom: 32%; } + +.scene-performance-stats { + bottom: 36px !important; + left: 12px !important; + border-radius: 6px; + backdrop-filter: blur(4px); + svg { + display: none; + } + .c-jiwtRJ{ + align-items: center; + } +} diff --git a/app/src/types/world/worldConstants.ts b/app/src/types/world/worldConstants.ts index 71d9caf..04214c1 100644 --- a/app/src/types/world/worldConstants.ts +++ b/app/src/types/world/worldConstants.ts @@ -206,7 +206,7 @@ export const thirdPersonControls: ThirdPersonControls = { polarRotateSpeed: 1, // Speed of rotation around the polar axis truckSpeed: 2, // Speed of truck movement maxDistance: 100, // Maximum distance from the target - maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle + maxPolarAngle: Math.PI / 2, // Maximum polar angle minZoom: 6, // Minimum zoom level maxZoom: 100, // Maximum zoom level targetOffset: 20, // Offset of the target from the camera diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 0764fd2..ffd9ddf 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -245,8 +245,12 @@ const KeyPressListener: React.FC = () => { if (keyCombination === "Ctrl+Shift+?") { setShowShortcuts(!showShortcuts); } + + // if (keyCombination === "Ctrl+Shift+P") { + // pref + // } + if (keyCombination === "U") { - // console.log("viewSceneLabels: ", viewSceneLabels); setViewSceneLabels((prev) => !prev); }