From 364b643c724c4d28562e40617035d7108b4a73e0 Mon Sep 17 00:00:00 2001 From: Jerald-Golden-B Date: Mon, 30 Jun 2025 17:48:29 +0530 Subject: [PATCH] feat: Enhance wall asset interaction and management; implement position updates and add closest point calculation utility --- .../helpers/getClosestPointOnLineSegment.ts | 12 ++++ .../Instances/Instances/wallAssetInstance.tsx | 68 ++++++++++++++++++- .../Instances/wallAssetInstances.tsx | 47 ++++++++++++- .../builder/wallAsset/wallAssetCreator.tsx | 12 +--- .../selectionControls/selectionControls.tsx | 2 +- app/src/store/builder/useWallAssetStore.ts | 8 +++ 6 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 app/src/modules/builder/line/helpers/getClosestPointOnLineSegment.ts diff --git a/app/src/modules/builder/line/helpers/getClosestPointOnLineSegment.ts b/app/src/modules/builder/line/helpers/getClosestPointOnLineSegment.ts new file mode 100644 index 0000000..8b65282 --- /dev/null +++ b/app/src/modules/builder/line/helpers/getClosestPointOnLineSegment.ts @@ -0,0 +1,12 @@ +import { Vector3 } from "three"; + +export default function closestPointOnLineSegment(p: Vector3, a: Vector3, b: Vector3) { + const ab = new Vector3().subVectors(b, a); + const ap = new Vector3().subVectors(p, a); + + const abLengthSq = ab.lengthSq(); + const dot = ap.dot(ab); + const t = Math.max(0, Math.min(1, dot / abLengthSq)); + + return new Vector3().copy(a).add(ab.multiplyScalar(t)); +} \ No newline at end of file diff --git a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx index e8255e1..90b93ad 100644 --- a/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx +++ b/app/src/modules/builder/wallAsset/Instances/Instances/wallAssetInstance.tsx @@ -8,11 +8,15 @@ import useModuleStore from '../../../../../store/useModuleStore'; import { useSceneContext } from '../../../../scene/sceneContext'; import { useBuilderStore } from '../../../../../store/builder/useBuilderStore'; import { useToggleView } from '../../../../../store/builder/store'; +import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment'; +import { useThree } from '@react-three/fiber'; function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; - const { wallStore } = useSceneContext(); + const { raycaster, pointer, camera, scene, controls, gl } = useThree(); + const { wallStore, wallAssetStore } = useSceneContext(); const { walls, getWallById } = wallStore(); + const { updateWallAsset } = wallAssetStore(); const { togglView } = useToggleView(); const { activeModule } = useModuleStore(); const { selectedWallAsset, setSelectedWallAsset } = useBuilderStore(); @@ -20,6 +24,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { const [boundingBox, setBoundingBox] = useState(null); const groupRef = useRef(null); const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]); + const draggingRef = useRef(false); useEffect(() => { const loader = new GLTFLoader(); @@ -94,6 +99,57 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { }, []); + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerUp = () => { + draggingRef.current = false; + if (controls) { + (controls as any).enabled = true; + } + }; + + const onPointerMove = (e: any) => { + if (!draggingRef.current || !wall || !selectedWallAsset) return; + if (controls) { + (controls as any).enabled = false; + } + 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) { + 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(); + + 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 (selectedWallAsset && !togglView && activeModule === 'builder') { + canvasElement.addEventListener('mousemove', onPointerMove); + canvasElement.addEventListener('pointerup', onPointerUp); + } + + return () => { + canvasElement.removeEventListener('mousemove', onPointerMove); + canvasElement.removeEventListener('pointerup', onPointerUp); + }; + + }, [gl, camera, togglView, activeModule, selectedWallAsset]) + if (!gltfScene || !boundingBox || !wall) { return null } const size = new THREE.Vector3(); boundingBox.getSize(size); @@ -118,6 +174,16 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) { {gltfScene && ( { + if (!togglView && activeModule === 'builder' && selectedWallAsset && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) { + draggingRef.current = true; + e.stopPropagation(); + setSelectedWallAsset(groupRef.current); + if (controls) { + (controls as any).enabled = false; + } + } + }} onClick={(e) => { if (!togglView && activeModule === 'builder') { if (e.object) { diff --git a/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx b/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx index 581dfe7..3c5fbdd 100644 --- a/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx +++ b/app/src/modules/builder/wallAsset/Instances/wallAssetInstances.tsx @@ -5,9 +5,54 @@ import WallAssetInstance from './Instances/wallAssetInstance'; function WallAssetInstances() { const { wallAssetStore } = useSceneContext(); - const { wallAssets } = wallAssetStore(); + const { wallAssets, setWallAssets } = wallAssetStore(); const { toggleView } = useToggleView(); + useEffect(() => { + setWallAssets([ + { + "modelName": "shutter_open", + "modelUuid": "23bf68d5-10c9-4cd0-807f-80e5016707b5", + "wallUuid": "7fc7984d-6d62-4cec-afaa-289673dcce43", + "wallAssetType": "fixed-move", + "assetId": "274f6c32aa861255c2947bea", + "position": [ + -13.748701534598418, + 0, + 20.112805679001355 + ], + "rotation": [ + 0, + -3.141592653589793, + 0 + ], + "isLocked": false, + "isVisible": true, + "opacity": 1 + }, + { + "modelName": "window", + "modelUuid": "5d73f98e-6f17-42b0-a53c-9a2c4472f2be", + "wallUuid": "7fc7984d-6d62-4cec-afaa-289673dcce43", + "wallAssetType": "free-move", + "assetId": "e44a85ff2021392f4c4a03f4", + "position": [ + -4.522713460474861, + 4.189202463272018, + 20.112805679001355 + ], + "rotation": [ + 0, + -3.141592653589793, + 0 + ], + "isLocked": false, + "isVisible": true, + "opacity": 1 + } + ]) + }, []) + useEffect(() => { // console.log('wallAssets: ', wallAssets); }, [wallAssets]) diff --git a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx index a64d957..aacca0e 100644 --- a/app/src/modules/builder/wallAsset/wallAssetCreator.tsx +++ b/app/src/modules/builder/wallAsset/wallAssetCreator.tsx @@ -4,6 +4,7 @@ import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/b import useModuleStore from '../../../store/useModuleStore'; import { MathUtils, Vector3 } from 'three'; import { useSceneContext } from '../../scene/sceneContext'; +import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegment'; function WallAssetCreator() { const { socket } = useSocketStore(); @@ -14,17 +15,6 @@ function WallAssetCreator() { const { addWallAsset } = wallAssetStore(); const { selectedItem, setSelectedItem } = useSelectedItem(); - function closestPointOnLineSegment(p: Vector3, a: Vector3, b: Vector3) { - const ab = new Vector3().subVectors(b, a); - const ap = new Vector3().subVectors(p, a); - - const abLengthSq = ab.lengthSq(); - const dot = ap.dot(ab); - const t = Math.max(0, Math.min(1, dot / abLengthSq)); - - return new Vector3().copy(a).add(ab.multiplyScalar(t)); - } - useEffect(() => { const canvasElement = gl.domElement; diff --git a/app/src/modules/scene/controls/selectionControls/selectionControls.tsx b/app/src/modules/scene/controls/selectionControls/selectionControls.tsx index fdc6d9a..645ed1c 100644 --- a/app/src/modules/scene/controls/selectionControls/selectionControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/selectionControls.tsx @@ -225,7 +225,7 @@ const SelectionControls: React.FC = () => { selectedObjects.forEach((object) => { let currentObject: THREE.Object3D | null = object; while (currentObject) { - if (currentObject.userData.modelUuid) { + if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) { Objects.add(currentObject); break; } diff --git a/app/src/store/builder/useWallAssetStore.ts b/app/src/store/builder/useWallAssetStore.ts index 408aade..3d4e89d 100644 --- a/app/src/store/builder/useWallAssetStore.ts +++ b/app/src/store/builder/useWallAssetStore.ts @@ -6,6 +6,7 @@ interface WallAssetStore { setWallAssets: (assets: WallAsset[]) => void; addWallAsset: (asset: WallAsset) => void; updateWallAsset: (uuid: string, updated: Partial) => void; + setWallAssetPosition: (uuid: string, position: [number, number, number]) => void; removeWallAsset: (uuid: string) => void; clearWallAssets: () => void; @@ -37,6 +38,13 @@ export const createWallAssetStore = () => { } }), + setWallAssetPosition: (uuid, position) => set(state => { + const asset = state.wallAssets.find(a => a.modelUuid === uuid); + if (asset) { + asset.position = position; + } + }), + removeWallAsset: (uuid) => set(state => { state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid); }),