feat: Enhance wall asset interaction and management; implement position updates and add closest point calculation utility
This commit is contained in:
@@ -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));
|
||||||
|
}
|
||||||
@@ -8,11 +8,15 @@ import useModuleStore from '../../../../../store/useModuleStore';
|
|||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
|
||||||
import { useToggleView } from '../../../../../store/builder/store';
|
import { useToggleView } from '../../../../../store/builder/store';
|
||||||
|
import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment';
|
||||||
|
import { useThree } from '@react-three/fiber';
|
||||||
|
|
||||||
function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
||||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
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 { walls, getWallById } = wallStore();
|
||||||
|
const { updateWallAsset } = wallAssetStore();
|
||||||
const { togglView } = useToggleView();
|
const { togglView } = useToggleView();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { selectedWallAsset, setSelectedWallAsset } = useBuilderStore();
|
const { selectedWallAsset, setSelectedWallAsset } = useBuilderStore();
|
||||||
@@ -20,6 +24,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
|||||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]);
|
const wall = useMemo(() => getWallById(wallAsset.wallUuid), [getWallById, wallAsset.wallUuid, walls]);
|
||||||
|
const draggingRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loader = new GLTFLoader();
|
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 }
|
if (!gltfScene || !boundingBox || !wall) { return null }
|
||||||
const size = new THREE.Vector3();
|
const size = new THREE.Vector3();
|
||||||
boundingBox.getSize(size);
|
boundingBox.getSize(size);
|
||||||
@@ -118,6 +174,16 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
|
|||||||
</Subtraction>
|
</Subtraction>
|
||||||
{gltfScene && (
|
{gltfScene && (
|
||||||
<mesh
|
<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) => {
|
onClick={(e) => {
|
||||||
if (!togglView && activeModule === 'builder') {
|
if (!togglView && activeModule === 'builder') {
|
||||||
if (e.object) {
|
if (e.object) {
|
||||||
|
|||||||
@@ -5,9 +5,54 @@ import WallAssetInstance from './Instances/wallAssetInstance';
|
|||||||
|
|
||||||
function WallAssetInstances() {
|
function WallAssetInstances() {
|
||||||
const { wallAssetStore } = useSceneContext();
|
const { wallAssetStore } = useSceneContext();
|
||||||
const { wallAssets } = wallAssetStore();
|
const { wallAssets, setWallAssets } = wallAssetStore();
|
||||||
const { toggleView } = useToggleView();
|
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(() => {
|
useEffect(() => {
|
||||||
// console.log('wallAssets: ', wallAssets);
|
// console.log('wallAssets: ', wallAssets);
|
||||||
}, [wallAssets])
|
}, [wallAssets])
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useSelectedItem, useSocketStore, useToggleView } from '../../../store/b
|
|||||||
import useModuleStore from '../../../store/useModuleStore';
|
import useModuleStore from '../../../store/useModuleStore';
|
||||||
import { MathUtils, Vector3 } from 'three';
|
import { MathUtils, Vector3 } from 'three';
|
||||||
import { useSceneContext } from '../../scene/sceneContext';
|
import { useSceneContext } from '../../scene/sceneContext';
|
||||||
|
import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegment';
|
||||||
|
|
||||||
function WallAssetCreator() {
|
function WallAssetCreator() {
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
@@ -14,17 +15,6 @@ function WallAssetCreator() {
|
|||||||
const { addWallAsset } = wallAssetStore();
|
const { addWallAsset } = wallAssetStore();
|
||||||
const { selectedItem, setSelectedItem } = useSelectedItem();
|
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(() => {
|
useEffect(() => {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
|
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ const SelectionControls: React.FC = () => {
|
|||||||
selectedObjects.forEach((object) => {
|
selectedObjects.forEach((object) => {
|
||||||
let currentObject: THREE.Object3D | null = object;
|
let currentObject: THREE.Object3D | null = object;
|
||||||
while (currentObject) {
|
while (currentObject) {
|
||||||
if (currentObject.userData.modelUuid) {
|
if (currentObject.userData.modelUuid && !currentObject.userData.wallAssetType) {
|
||||||
Objects.add(currentObject);
|
Objects.add(currentObject);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ interface WallAssetStore {
|
|||||||
setWallAssets: (assets: WallAsset[]) => void;
|
setWallAssets: (assets: WallAsset[]) => void;
|
||||||
addWallAsset: (asset: WallAsset) => void;
|
addWallAsset: (asset: WallAsset) => void;
|
||||||
updateWallAsset: (uuid: string, updated: Partial<WallAsset>) => void;
|
updateWallAsset: (uuid: string, updated: Partial<WallAsset>) => void;
|
||||||
|
setWallAssetPosition: (uuid: string, position: [number, number, number]) => void;
|
||||||
removeWallAsset: (uuid: string) => void;
|
removeWallAsset: (uuid: string) => void;
|
||||||
clearWallAssets: () => 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 => {
|
removeWallAsset: (uuid) => set(state => {
|
||||||
state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid);
|
state.wallAssets = state.wallAssets.filter(a => a.modelUuid !== uuid);
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user