381 lines
16 KiB
TypeScript
381 lines
16 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
import { useGLTF } from '@react-three/drei';
|
|
import { useFrame, useThree } from '@react-three/fiber';
|
|
import { useIsDragging, useIsRotating, useSelectedAction, useSelectedEventSphere } from '../../../../../store/simulation/useSimulationStore';
|
|
import { useProductContext } from '../../../products/productContext';
|
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
|
import { Group, Plane, Vector2, Vector3 } from 'three';
|
|
import { useVersionContext } from '../../../../builder/version/versionContext';
|
|
import { useParams } from 'react-router-dom';
|
|
import startPoint from "../../../../../assets/gltf-glb/ui/human-ui-green.glb";
|
|
import startEnd from "../../../../../assets/gltf-glb/ui/human-ui-orange.glb";
|
|
import assembly from "../../../../../assets/gltf-glb/ui/human-ui-assembly.glb";
|
|
import { upsertProductOrEventApi } from '../../../../../services/simulation/products/UpsertProductOrEventApi';
|
|
|
|
function HumanUi() {
|
|
const { scene: startScene } = useGLTF(startPoint) as any;
|
|
const { scene: endScene } = useGLTF(startEnd) as any;
|
|
const { scene: assemblyScene } = useGLTF(assembly) as any;
|
|
const startMarker = useRef<Group>(null);
|
|
const endMarker = useRef<Group>(null);
|
|
const assemblyMarker = useRef<Group>(null);
|
|
const outerGroup = useRef<Group>(null);
|
|
const prevMousePos = useRef({ x: 0, y: 0 });
|
|
const { controls, raycaster, camera } = useThree();
|
|
const { selectedEventSphere } = useSelectedEventSphere();
|
|
const { selectedProductStore } = useProductContext();
|
|
const { humanStore, productStore } = useSceneContext();
|
|
const { selectedProduct } = selectedProductStore();
|
|
const { humans, getHumanById } = humanStore();
|
|
const { updateEvent } = productStore();
|
|
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0]);
|
|
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0]);
|
|
const [assemblyPosition, setAssemblyPosition] = useState<[number, number, number]>([0, 1, 0]);
|
|
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, Math.PI, 0]);
|
|
const [assemblyRotation, setAssemblyRotation] = useState<[number, number, number]>([0, 0, 0]);
|
|
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]);
|
|
const { isDragging, setIsDragging } = useIsDragging();
|
|
const { isRotating, setIsRotating } = useIsRotating();
|
|
const plane = useRef(new Plane(new Vector3(0, 1, 0), 0));
|
|
const dragOffset = useRef(new Vector3());
|
|
|
|
const [selectedHumanData, setSelectedHumanData] = useState<{
|
|
position: [number, number, number];
|
|
rotation: [number, number, number];
|
|
}>({ position: [0, 0, 0], rotation: [0, 0, 0] });
|
|
|
|
const { selectedAction } = useSelectedAction();
|
|
const { selectedVersionStore } = useVersionContext();
|
|
const { selectedVersion } = selectedVersionStore();
|
|
const { projectId } = useParams();
|
|
|
|
const selectedHuman = selectedEventSphere ? getHumanById(selectedEventSphere.userData.modelUuid) : null;
|
|
const actionType = selectedHuman?.point?.action?.actionType || null;
|
|
const isAssembly = actionType === 'assembly';
|
|
|
|
const updateBackend = (
|
|
productName: string,
|
|
productUuid: string,
|
|
projectId: string,
|
|
eventData: EventsSchema
|
|
) => {
|
|
upsertProductOrEventApi({
|
|
productName: productName,
|
|
productUuid: productUuid,
|
|
projectId: projectId,
|
|
eventDatas: eventData,
|
|
versionId: selectedVersion?.versionId || '',
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!selectedEventSphere) return;
|
|
|
|
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
|
|
if (!selectedHuman || !selectedHuman.point?.action) return;
|
|
|
|
setSelectedHumanData({
|
|
position: selectedHuman.position,
|
|
rotation: selectedHuman.rotation,
|
|
});
|
|
|
|
if (outerGroup.current) {
|
|
outerGroup.current.position.set(
|
|
selectedHuman.position[0],
|
|
selectedHuman.position[1],
|
|
selectedHuman.position[2]
|
|
);
|
|
}
|
|
|
|
const action = selectedHuman.point.action;
|
|
|
|
if (isAssembly) {
|
|
if (action.assemblyPoint?.position && outerGroup.current) {
|
|
const worldPos = new Vector3(...action.assemblyPoint.position);
|
|
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
|
|
setAssemblyPosition([localPosition.x, 1, localPosition.z]);
|
|
setAssemblyRotation(action.assemblyPoint.rotation || [0, 0, 0]);
|
|
} else {
|
|
setAssemblyPosition([0, 1, 0]);
|
|
setAssemblyRotation([0, 0, 0]);
|
|
}
|
|
} else {
|
|
if (action.pickUpPoint?.position && outerGroup.current) {
|
|
const worldPos = new Vector3(...action.pickUpPoint.position);
|
|
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
|
|
setStartPosition([localPosition.x, 1, localPosition.z]);
|
|
setStartRotation(action.pickUpPoint.rotation || [0, 0, 0]);
|
|
} else {
|
|
setStartPosition([0, 1, 0]);
|
|
setStartRotation([0, Math.PI, 0]);
|
|
}
|
|
|
|
if (action.dropPoint?.position && outerGroup.current) {
|
|
const worldPos = new Vector3(...action.dropPoint.position);
|
|
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
|
|
setEndPosition([localPosition.x, 1, localPosition.z]);
|
|
setEndRotation(action.dropPoint.rotation || [0, Math.PI, 0]);
|
|
} else {
|
|
setEndPosition([0, 1, 0]);
|
|
setEndRotation([0, 0, 0]);
|
|
}
|
|
}
|
|
}, [selectedEventSphere, outerGroup.current, selectedAction, humans]);
|
|
|
|
const handlePointerDown = (
|
|
e: any,
|
|
state: "start" | "end" | "assembly",
|
|
rotation: "start" | "end" | "assembly"
|
|
) => {
|
|
e.stopPropagation();
|
|
const intersection = new Vector3();
|
|
const pointer = new Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
|
|
raycaster.setFromCamera(pointer, camera);
|
|
const intersects = raycaster.ray.intersectPlane(plane.current, intersection);
|
|
|
|
if (e.object.parent.name === "handle") {
|
|
const normalizedX = (e.clientX / window.innerWidth) * 2 - 1;
|
|
const normalizedY = -(e.clientY / window.innerHeight) * 2 + 1;
|
|
prevMousePos.current = { x: normalizedX, y: normalizedY };
|
|
setIsRotating(rotation);
|
|
setIsDragging(null);
|
|
} else {
|
|
setIsDragging(state);
|
|
setIsRotating(null);
|
|
}
|
|
|
|
if (intersects) {
|
|
let localPoint: Vector3 | null = null;
|
|
if (outerGroup.current) {
|
|
localPoint = outerGroup.current.worldToLocal(intersection.clone());
|
|
}
|
|
const marker =
|
|
state === "start" ? startMarker.current :
|
|
state === "end" ? endMarker.current :
|
|
assemblyMarker.current;
|
|
if (marker && localPoint) {
|
|
const markerPos = new Vector3().copy(marker.position);
|
|
dragOffset.current.copy(markerPos.sub(localPoint));
|
|
}
|
|
}
|
|
|
|
if (controls) (controls as any).enabled = false;
|
|
};
|
|
|
|
const handlePointerUp = () => {
|
|
(controls as any).enabled = true;
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
|
|
if (!selectedEventSphere?.userData.modelUuid || !selectedAction?.actionId) return;
|
|
|
|
const selectedHuman = getHumanById(selectedEventSphere.userData.modelUuid);
|
|
if (!selectedHuman || !outerGroup.current) return;
|
|
|
|
let updatedAction;
|
|
|
|
if (isAssembly) {
|
|
if (!assemblyMarker.current) return;
|
|
|
|
const worldPosAssembly = new Vector3(...assemblyPosition);
|
|
const globalAssemblyPosition = outerGroup.current.localToWorld(worldPosAssembly.clone());
|
|
|
|
updatedAction = {
|
|
...selectedHuman.point.action,
|
|
assemblyPoint: {
|
|
position: [globalAssemblyPosition.x, globalAssemblyPosition.y, globalAssemblyPosition.z] as [number, number, number],
|
|
rotation: assemblyRotation
|
|
},
|
|
};
|
|
} else {
|
|
if (!startMarker.current || !endMarker.current) return;
|
|
|
|
const worldPosStart = new Vector3(...startPosition);
|
|
const globalStartPosition = outerGroup.current.localToWorld(worldPosStart.clone());
|
|
|
|
const worldPosEnd = new Vector3(...endPosition);
|
|
const globalEndPosition = outerGroup.current.localToWorld(worldPosEnd.clone());
|
|
|
|
updatedAction = {
|
|
...selectedHuman.point.action,
|
|
pickUpPoint: {
|
|
position: [globalStartPosition.x, globalStartPosition.y, globalStartPosition.z] as [number, number, number],
|
|
rotation: startRotation,
|
|
},
|
|
dropPoint: {
|
|
position: [globalEndPosition.x, globalEndPosition.y, globalEndPosition.z] as [number, number, number],
|
|
rotation: endRotation,
|
|
},
|
|
};
|
|
}
|
|
|
|
const event = updateEvent(
|
|
selectedProduct.productUuid,
|
|
selectedEventSphere.userData.modelUuid,
|
|
{
|
|
...selectedHuman,
|
|
point: {
|
|
...selectedHuman.point,
|
|
action: updatedAction,
|
|
},
|
|
}
|
|
);
|
|
|
|
if (event) {
|
|
updateBackend(
|
|
selectedProduct.productName,
|
|
selectedProduct.productUuid,
|
|
projectId || '',
|
|
event
|
|
);
|
|
}
|
|
};
|
|
|
|
useFrame(() => {
|
|
if (!isDragging || !plane.current || !raycaster || !outerGroup.current) return;
|
|
const intersectPoint = new Vector3();
|
|
const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint);
|
|
if (!intersects) return;
|
|
|
|
const localPoint = outerGroup.current.worldToLocal(intersectPoint.clone()).add(dragOffset.current);
|
|
|
|
if (isDragging === "start") {
|
|
setStartPosition([localPoint.x, 1, localPoint.z]);
|
|
} else if (isDragging === "end") {
|
|
setEndPosition([localPoint.x, 1, localPoint.z]);
|
|
} else if (isDragging === "assembly") {
|
|
setAssemblyPosition([localPoint.x, 1, localPoint.z]);
|
|
}
|
|
});
|
|
|
|
useFrame((state) => {
|
|
if (!isRotating) return;
|
|
const currentPointerX = state.pointer.x;
|
|
const deltaX = currentPointerX - prevMousePos.current.x;
|
|
prevMousePos.current.x = currentPointerX;
|
|
|
|
const marker =
|
|
isRotating === "start" ? startMarker.current :
|
|
isRotating === "end" ? endMarker.current :
|
|
assemblyMarker.current;
|
|
|
|
if (marker) {
|
|
const rotationSpeed = 10;
|
|
marker.rotation.y += deltaX * rotationSpeed;
|
|
|
|
if (isRotating === "start") {
|
|
setStartRotation([
|
|
marker.rotation.x,
|
|
marker.rotation.y,
|
|
marker.rotation.z,
|
|
]);
|
|
} else if (isRotating === "end") {
|
|
setEndRotation([
|
|
marker.rotation.x,
|
|
marker.rotation.y,
|
|
marker.rotation.z,
|
|
]);
|
|
} else {
|
|
setAssemblyRotation([
|
|
marker.rotation.x,
|
|
marker.rotation.y,
|
|
marker.rotation.z,
|
|
]);
|
|
}
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
const handleGlobalPointerUp = () => {
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
if (controls) (controls as any).enabled = true;
|
|
handlePointerUp();
|
|
};
|
|
|
|
if (isDragging || isRotating) {
|
|
window.addEventListener("pointerup", handleGlobalPointerUp);
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener("pointerup", handleGlobalPointerUp);
|
|
};
|
|
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, assemblyPosition, assemblyRotation]);
|
|
|
|
return (
|
|
<>
|
|
{selectedHumanData && (
|
|
<group
|
|
position={selectedHumanData.position}
|
|
ref={outerGroup}
|
|
rotation={[0, Math.PI, 0]}
|
|
>
|
|
{isAssembly ? (
|
|
<primitive
|
|
ref={assemblyMarker}
|
|
object={assemblyScene}
|
|
position={assemblyPosition}
|
|
rotation={assemblyRotation}
|
|
onPointerDown={(e: any) => {
|
|
if (e.object.parent.name === "handle") {
|
|
handlePointerDown(e, "assembly", "assembly");
|
|
} else {
|
|
handlePointerDown(e, "assembly", "assembly");
|
|
}
|
|
}}
|
|
onPointerMissed={() => {
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
if (controls) (controls as any).enabled = true;
|
|
}}
|
|
/>
|
|
) : (
|
|
<>
|
|
<primitive
|
|
name="startMarker"
|
|
object={startScene}
|
|
ref={startMarker}
|
|
position={startPosition}
|
|
rotation={startRotation}
|
|
onPointerDown={(e: any) => {
|
|
if (e.object.parent.name === "handle") {
|
|
handlePointerDown(e, "start", "start");
|
|
} else {
|
|
handlePointerDown(e, "start", "start");
|
|
}
|
|
}}
|
|
onPointerMissed={() => {
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
if (controls) (controls as any).enabled = true;
|
|
}}
|
|
/>
|
|
|
|
<primitive
|
|
name="endMarker"
|
|
object={endScene}
|
|
ref={endMarker}
|
|
position={endPosition}
|
|
rotation={endRotation}
|
|
onPointerDown={(e: any) => {
|
|
if (e.object.parent.name === "handle") {
|
|
handlePointerDown(e, "end", "end");
|
|
} else {
|
|
handlePointerDown(e, "end", "end");
|
|
}
|
|
}}
|
|
onPointerMissed={() => {
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
if (controls) (controls as any).enabled = true;
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</group>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default HumanUi; |