Files
Dwinzo_Demo/app/src/modules/scene/controls/selectionControls/selection2D/moveControls2D.tsx

621 lines
28 KiB
TypeScript

import * as THREE from "three";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useSelectedPoints } from "../../../../../store/simulation/useSimulationStore";
import useModuleStore from "../../../../../store/useModuleStore";
import { calculateAssetTransformationOnWall } from "../../../../builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall";
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
function MoveControls2D({
movedObjects,
setMovedObjects,
pastedObjects,
setPastedObjects,
duplicatedObjects,
setDuplicatedObjects,
rotatedObjects,
setRotatedObjects,
}: any) {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { selectedPoints, clearSelectedPoints } = useSelectedPoints();
const { socket } = useSocketStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { aisleStore, wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore } = useSceneContext();
const { getWallAssetsByWall, updateWallAsset } = wallAssetStore();
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<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
const [initial, setInitial] = useState<{
aisles?: Aisle[],
walls?: Wall[],
floors?: Floor[],
zones?: Zone[]
}>({});
const [isMoving, setIsMoving] = useState(false);
useEffect(() => {
if (!camera || !scene || !toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isMoving = false;
const onPointerDown = () => {
isMoving = false;
};
const onPointerMove = () => {
isMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (!isMoving && movedObjects.length > 0 && event.button === 0) {
event.preventDefault();
placeMovedPoints();
}
if (!isMoving && movedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
setMovedObjects([]);
resetToInitialPositions();
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (pastedObjects.length > 0 || duplicatedObjects.length > 0 || rotatedObjects.length > 0) return;
if (keyCombination === "G") {
if (selectedPoints.length > 0) {
movePoints();
}
}
if (keyCombination === "ESCAPE") {
event.preventDefault();
clearSelection();
setMovedObjects([]);
resetToInitialPositions();
}
};
if (toggleView && selectedPoints.length > 0) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
}, [camera, controls, scene, toggleView, selectedPoints, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, initial]);
useEffect(() => {
if (toolMode !== 'move' || !toggleView) {
if (movedObjects.length > 0) {
resetToInitialPositions();
}
clearSelection();
}
}, [activeModule, toolMode, toggleView, movedObjects]);
useFrame(() => {
if (!isMoving || movedObjects.length === 0 || !dragOffset) return;
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
const baseNewPosition = new THREE.Vector3().addVectors(hit, dragOffset);
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid) {
const point: Point = movedPoint.userData as Point;
const initialPosition = initialPositions[movedPoint.uuid];
if (initialPosition) {
const relativeOffset = new THREE.Vector3().subVectors(
initialPosition,
initialPositions[movedObjects[0].uuid]
);
const newPosition = new THREE.Vector3().addVectors(baseNewPosition, relativeOffset);
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
if (point.pointType === 'Aisle') {
setAislePosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Wall') {
setWallPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Floor') {
setFloorPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Zone') {
setZonePosition(point.pointUuid, positionArray);
}
}
}
});
}
});
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
const movePoints = (() => {
if (selectedPoints.length === 0) return;
const states: Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }> = {};
const initials: {
aisles?: Aisle[] | undefined;
walls?: Wall[];
floors?: Floor[];
zones?: Zone[];
} = {}
selectedPoints.forEach((point: THREE.Object3D) => {
states[point.uuid] = {
position: new THREE.Vector3().copy(point.position),
rotation: point.rotation ? new THREE.Euler().copy(point.rotation) : undefined
};
if (point.userData.pointType === "Aisle") {
const aisles = getAislesByPointId(point.userData.pointUuid);
initials.aisles = [...(initials.aisles ?? []), ...aisles,].filter((aisle, index, self) => index === self.findIndex((a) => a.aisleUuid === aisle.aisleUuid));
} else if (point.userData.pointType === "Wall") {
const walls = getWallsByPointId(point.userData.pointUuid);
initials.walls = [...(initials.walls ?? []), ...walls,].filter((wall, index, self) => index === self.findIndex((w) => w.wallUuid === wall.wallUuid));
} else if (point.userData.pointType === "Floor") {
const floors = getFloorsByPointId(point.userData.pointUuid);
initials.floors = [...(initials.floors ?? []), ...floors,].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));
} else if (point.userData.pointType === "Zone") {
const zones = getZonesByPointId(point.userData.pointUuid);
initials.zones = [...(initials.zones ?? []), ...zones,].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));
}
});
setInitial(initials)
setInitialStates(states);
const positions: Record<string, THREE.Vector3> = {};
selectedPoints.forEach((point: THREE.Object3D) => { positions[point.uuid] = new THREE.Vector3().copy(point.position); });
setInitialPositions(positions);
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit && selectedPoints[0]) {
const offset = calculateDragOffset(selectedPoints[0], hit);
setDragOffset(offset);
}
setMovedObjects(selectedPoints);
setIsMoving(true);
});
const resetToInitialPositions = () => {
setTimeout(() => {
movedObjects.forEach((movedPoint: THREE.Object3D) => {
if (movedPoint.userData.pointUuid && initialStates[movedPoint.uuid]) {
const point: Point = movedPoint.userData as Point;
const initialState = initialStates[movedPoint.uuid];
const positionArray: [number, number, number] = [
initialState.position.x,
initialState.position.y,
initialState.position.z
];
if (point.pointType === 'Aisle') {
setAislePosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Wall') {
setWallPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Floor') {
setFloorPosition(point.pointUuid, positionArray);
} else if (point.pointType === 'Zone') {
setZonePosition(point.pointUuid, positionArray);
}
}
});
}, 0)
};
const placeMovedPoints = () => {
if (movedObjects.length === 0) return;
const undoPoints: UndoRedo2DDataTypeSchema[] = [];
const processedAisles: UndoRedo2DDataTypeSchema[] = [];
const processedWalls: UndoRedo2DDataTypeSchema[] = [];
const processedFloors: UndoRedo2DDataTypeSchema[] = [];
const processedZones: UndoRedo2DDataTypeSchema[] = [];
const wallAssetUpdates: WallAsset[] = [];
movedObjects.forEach((movedObject: THREE.Object3D) => {
if (movedObject.userData.pointUuid) {
const point: Point = movedObject.userData as Point;
if (point.pointType === 'Aisle') {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId,
versionId: selectedVersion?.versionId || '',
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: [
initialStates[updatedAisle.points[0].pointUuid] ?
{ ...updatedAisle.points[0], position: initialStates[updatedAisle.points[0].pointUuid].position }
: updatedAisle.points[0],
initialStates[updatedAisle.points[1].pointUuid] ?
{ ...updatedAisle.points[1], position: initialStates[updatedAisle.points[1].pointUuid].position }
: updatedAisle.points[1]
] as [Point, Point],
},
newData: updatedAisle,
timeStamp: new Date().toISOString(),
});
}
});
}
} else if (point.pointType === 'Wall') {
const updatedWalls = getWallsByPointId(point.pointUuid);
if (updatedWalls?.length && projectId) {
updatedWalls.forEach(updatedWall => {
const initialWall = initial.walls?.find(w => w.wallUuid === updatedWall.wallUuid);
if (initialWall) {
const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid);
assetsOnWall.forEach((asset) => {
const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall);
const updatedWallAsset: WallAsset = {
...asset,
position: [position[0], asset.position[1], position[2]],
rotation,
};
wallAssetUpdates.push(updatedWallAsset);
});
}
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
socket.emit('v1:model-Wall:add', {
wallData: updatedWall,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
const old = initialStates[movedObject.uuid];
if (old) {
processedWalls.push({
type: 'Wall',
lineData: {
...updatedWall,
points: [
initialStates[updatedWall.points[0].pointUuid] ?
{ ...updatedWall.points[0], position: initialStates[updatedWall.points[0].pointUuid].position }
: updatedWall.points[0],
initialStates[updatedWall.points[1].pointUuid] ?
{ ...updatedWall.points[1], position: initialStates[updatedWall.points[1].pointUuid].position }
: 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?.length && projectId) {
updatedFloors.forEach(updatedFloor => {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
// SOCKET
socket.emit('v1:model-Floor:add', {
floorData: updatedFloor,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
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?.length && projectId) {
updatedZones.forEach(updatedZone => {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
// SOCKET
socket.emit('v1:zone:add', {
zoneData: updatedZone,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
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 (wallAssetUpdates.length > 0) {
wallAssetUpdates.filter((wallAssets, index, self) => index === self.findIndex((w) => w.modelUuid === wallAssets.modelUuid));
wallAssetUpdates.forEach((updatedWallAsset) => {
if (projectId && updatedWallAsset) {
updateWallAsset(updatedWallAsset.modelUuid, {
position: updatedWallAsset.position,
rotation: updatedWallAsset.rotation
});
// 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);
}
});
}
if (processedWalls.length > 0) {
const wallMap = new Map<string, UndoRedo2DDataTypeSchema[]>();
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<string, UndoRedo2DDataTypeSchema[]>();
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<string, UndoRedo2DDataTypeSchema[]>();
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<string, UndoRedo2DDataTypeSchema[]>();
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();
};
const clearSelection = () => {
setPastedObjects([]);
setDuplicatedObjects([]);
setMovedObjects([]);
setRotatedObjects([]);
clearSelectedPoints();
};
return (
<>
</>
);
}
export default MoveControls2D;