Files
Dwinzo_Demo/app/src/modules/scene/controls/selectionControls/selection3D/duplicationControls3D.tsx

519 lines
25 KiB
TypeScript

import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
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 { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
const DuplicationControls3D = ({
duplicatedObjects,
setDuplicatedObjects,
setpastedObjects,
movedObjects,
setMovedObjects,
rotatedObjects,
setRotatedObjects,
}: any) => {
const { camera, controls, gl, scene, pointer, raycaster } = useThree();
const { toggleView } = useToggleView();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
const { socket } = useSocketStore();
const { assetStore, eventStore } = useSceneContext();
const { addEvent } = eventStore();
const { projectId } = useParams();
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { userId, organization } = getUserData();
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isDuplicating, setIsDuplicating] = useState(false);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;
canvasElement.tabIndex = 0;
let isPointerMoving = false;
const onPointerDown = (event: PointerEvent) => {
isPointerMoving = false;
if (event.button === 0) mouseButtonsDown.current.left = true;
if (event.button === 2) mouseButtonsDown.current.right = true;
};
const onPointerMove = () => {
isPointerMoving = true;
};
const onPointerUp = (event: PointerEvent) => {
if (event.button === 0) mouseButtonsDown.current.left = false;
if (event.button === 2) mouseButtonsDown.current.right = false;
if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 0) {
event.preventDefault();
addDuplicatedAssets();
}
if (!isPointerMoving && duplicatedObjects.length > 0 && event.button === 2) {
event.preventDefault();
clearSelection();
duplicatedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "Ctrl+D" && movedObjects.length === 0 && rotatedObjects.length === 0) {
duplicateSelection();
}
if (keyCombination === "ESCAPE" && duplicatedObjects.length > 0) {
event.preventDefault();
clearSelection();
duplicatedObjects.forEach((obj: THREE.Object3D) => {
removeAsset(obj.userData.modelUuid);
});
}
};
if (!toggleView) {
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);
};
}, [assets, camera, controls, scene, toggleView, selectedAssets, duplicatedObjects, movedObjects, socket, rotatedObjects]);
useFrame(() => {
if (!isDuplicating || duplicatedObjects.length === 0) return;
const intersectionPoint = new THREE.Vector3();
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
if (mouseButtonsDown.current.left || mouseButtonsDown.current.right) {
const model = scene.getObjectByProperty("uuid", duplicatedObjects[0].userData.modelUuid);
if (model) {
const newOffset = calculateDragOffset(model, intersectionPoint);
setDragOffset(newOffset);
}
return;
}
if (dragOffset) {
const adjustedHit = new THREE.Vector3().addVectors(intersectionPoint, dragOffset);
duplicatedObjects.forEach((duplicatedObject: THREE.Object3D) => {
if (duplicatedObject.userData.modelUuid) {
const initialPosition = initialPositions[duplicatedObject.userData.modelUuid];
if (initialPosition) {
const relativeOffset = new THREE.Vector3().subVectors(
initialPosition,
initialPositions[duplicatedObjects[0].userData.modelUuid]
);
const model = scene.getObjectByProperty("uuid", duplicatedObject.userData.modelUuid);
const newPosition = new THREE.Vector3().addVectors(adjustedHit, relativeOffset);
const positionArray: [number, number, number] = [newPosition.x, newPosition.y, newPosition.z];
if (model) {
model.position.set(...positionArray);
}
}
}
});
}
}
});
const duplicateSelection = useCallback(() => {
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
const positions: Record<string, THREE.Vector3> = {};
const newDuplicatedObjects = selectedAssets.map((obj: any) => {
const clone = SkeletonUtils.clone(obj);
clone.userData.modelUuid = THREE.MathUtils.generateUUID();
positions[clone.userData.modelUuid] = new THREE.Vector3().copy(obj.position);
return clone;
});
setDuplicatedObjects(newDuplicatedObjects);
setInitialPositions(positions);
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
if (hit) {
const offset = calculateDragOffset(selectedAssets[0], hit);
setDragOffset(offset);
}
setIsDuplicating(true);
newDuplicatedObjects.forEach((obj: THREE.Object3D) => {
const asset: Asset = {
modelUuid: obj.userData.modelUuid,
modelName: obj.userData.modelName,
assetId: obj.userData.assetId,
position: [obj.position.x, 0, obj.position.z],
rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
isLocked: false,
isVisible: true,
isCollidable: false,
opacity: 0.5,
};
addAsset(asset);
});
}
}, [selectedAssets, duplicatedObjects]);
const addDuplicatedAssets = () => {
if (duplicatedObjects.length === 0) return;
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
if (duplicatedAsset) {
const assetUuid = duplicatedAsset.userData.modelUuid;
const asset = getAssetById(assetUuid);
const model = scene.getObjectByProperty("uuid", duplicatedAsset.userData.modelUuid);
if (!asset || !model) return;
const position = new THREE.Vector3().copy(model.position);
const newFloorItem: Types.FloorItemType = {
modelUuid: duplicatedAsset.userData.modelUuid,
modelName: duplicatedAsset.userData.modelName,
assetId: duplicatedAsset.userData.assetId,
position: [position.x, position.y, position.z],
rotation: { x: asset.rotation[0], y: asset.rotation[1], z: asset.rotation[2] },
isLocked: false,
isVisible: true,
};
let updatedEventData = null;
if (duplicatedAsset.userData.eventData) {
updatedEventData = JSON.parse(JSON.stringify(duplicatedAsset.userData.eventData));
updatedEventData.modelUuid = newFloorItem.modelUuid;
const eventData: any = {
type: duplicatedAsset.userData.eventData.type,
};
if (duplicatedAsset.userData.eventData.type === "Conveyor") {
const ConveyorEvent: ConveyorEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: 'transfer',
speed: 1,
points: updatedEventData.points.map((point: any, index: number) => ({
uuid: THREE.MathUtils.generateUUID(),
position: [point.position[0], point.position[1], point.position[2]],
rotation: [point.rotation[0], point.rotation[1], point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: `Action 1`,
actionType: 'default',
material: 'Default Material',
delay: 0,
spawnInterval: 5,
spawnCount: 1,
triggers: []
}
}))
};
addEvent(ConveyorEvent);
eventData.points = ConveyorEvent.points.map(point => ({
uuid: point.uuid,
position: point.position,
rotation: point.rotation
}));
} else if (duplicatedAsset.userData.eventData.type === "Vehicle") {
const vehicleEvent: VehicleEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "vehicle",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "travel",
unLoadDuration: 5,
loadCapacity: 1,
steeringAngle: 0,
pickUpPoint: null,
unLoadPoint: null,
paths: {
initPickup: [],
pickupDrop: [],
dropPickup: [],
},
triggers: []
}
}
};
addEvent(vehicleEvent);
eventData.point = {
uuid: vehicleEvent.point.uuid,
position: vehicleEvent.point.position,
rotation: vehicleEvent.point.rotation
};
} else if (duplicatedAsset.userData.eventData.type === "ArmBot") {
const roboticArmEvent: RoboticArmEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "roboticArm",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "pickAndPlace",
process: {
startPoint: null,
endPoint: null
},
triggers: []
}
]
}
};
addEvent(roboticArmEvent);
eventData.point = {
uuid: roboticArmEvent.point.uuid,
position: roboticArmEvent.point.position,
rotation: roboticArmEvent.point.rotation
};
} else if (duplicatedAsset.userData.eventData.type === "StaticMachine") {
const machineEvent: MachineEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "machine",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "process",
processTime: 10,
swapMaterial: "Default Material",
triggers: []
}
}
};
addEvent(machineEvent);
eventData.point = {
uuid: machineEvent.point.uuid,
position: machineEvent.point.position,
rotation: machineEvent.point.rotation
};
} else if (duplicatedAsset.userData.eventData.type === "Storage") {
const storageEvent: StorageEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "storageUnit",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
action: {
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "store",
storageCapacity: 10,
triggers: []
}
}
}
addEvent(storageEvent);
eventData.point = {
uuid: storageEvent.point.uuid,
position: storageEvent.point.position,
rotation: storageEvent.point.rotation
};
} else if (duplicatedAsset.userData.eventData.type === "Human") {
const humanEvent: HumanEventSchema = {
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
rotation: [newFloorItem.rotation.x, newFloorItem.rotation.y, newFloorItem.rotation.z],
state: "idle",
type: "human",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [updatedEventData.point.position[0], updatedEventData.point.position[1], updatedEventData.point.position[2]],
rotation: [updatedEventData.point.rotation[0], updatedEventData.point.rotation[1], updatedEventData.point.rotation[2]],
actions: [
{
actionUuid: THREE.MathUtils.generateUUID(),
actionName: "Action 1",
actionType: "worker",
loadCapacity: 1,
assemblyCount: 1,
loadCount: 1,
processTime: 10,
triggers: []
}
]
}
}
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation
};
}
newFloorItem.eventData = eventData;
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: [position.x, position.y, position.z],
rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
eventData: eventData,
versionId: selectedVersion?.versionId || '',
userId,
projectId
};
//REST
// await setAssetsApi(data);
//SOCKET
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
eventData: data.eventData
};
updateAsset(asset.modelUuid, asset);
} else {
const data = {
organization,
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: [position.x, position.y, position.z],
rotation: { x: duplicatedAsset.rotation.x, y: duplicatedAsset.rotation.y, z: duplicatedAsset.rotation.z },
isLocked: false,
isVisible: true,
socketId: socket.id,
versionId: selectedVersion?.versionId || '',
projectId,
userId
};
socket.emit("v1:model-asset:add", data);
const asset: Asset = {
modelUuid: data.modelUuid,
modelName: data.modelName,
assetId: data.assetId,
position: [position.x, position.y, position.z],
rotation: [data.rotation.x, data.rotation.y, data.rotation.z],
isLocked: data.isLocked,
isCollidable: false,
isVisible: data.isVisible,
opacity: 1,
};
updateAsset(asset.modelUuid, asset);
}
}
});
echo.success("Object duplicated!");
clearSelection();
};
const clearSelection = () => {
setMovedObjects([]);
setpastedObjects([]);
setDuplicatedObjects([]);
setRotatedObjects([]);
setSelectedAssets([]);
setIsDuplicating(false);
setDragOffset(null);
};
return null;
};
export default DuplicationControls3D;