feat: Enhance wall asset interaction and management; implement position updates and add closest point calculation utility

This commit is contained in:
2025-06-30 17:48:29 +05:30
parent 1a9aef323a
commit 364b643c72
6 changed files with 135 additions and 14 deletions

View File

@@ -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));
}

View File

@@ -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<THREE.Box3 | null>(null);
const groupRef = useRef<THREE.Group>(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 }) {
</Subtraction>
{gltfScene && (
<mesh
onPointerDown={(e) => {
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) {

View File

@@ -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])

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -6,6 +6,7 @@ interface WallAssetStore {
setWallAssets: (assets: WallAsset[]) => void;
addWallAsset: (asset: WallAsset) => void;
updateWallAsset: (uuid: string, updated: Partial<WallAsset>) => 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);
}),