- Implemented `useAssemblyHandler` to manage assembly actions for humans. - Enhanced `useHumanActions` to include assembly action handling. - Updated `HumanInstance` to support assembly processes and animations. - Modified `HumanUi` to allow for assembly point configuration and rotation. - Created `AssemblyAction` component for setting process time and material swap options. - Updated simulation types to include assembly action properties. - Adjusted existing action handlers to accommodate assembly actions alongside worker actions. - Refactored `MaterialAnimator` and `VehicleAnimator` to manage attachment states and visibility based on load. - Updated product store types to include human point actions.
383 lines
15 KiB
TypeScript
383 lines
15 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
import * as Types from "../../../../types/world/worldTypes";
|
|
import { useGLTF } from "@react-three/drei";
|
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
import { useSelectedEventSphere, useIsDragging, useIsRotating, } from "../../../../store/simulation/useSimulationStore";
|
|
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
|
import { DoubleSide, Group, Plane, Vector3 } from "three";
|
|
|
|
import startPoint from "../../../../assets/gltf-glb/ui/arrow_green.glb";
|
|
import startEnd from "../../../../assets/gltf-glb/ui/arrow_red.glb";
|
|
import { useSceneContext } from "../../../scene/sceneContext";
|
|
import { useProductContext } from "../../products/productContext";
|
|
import { useParams } from "react-router-dom";
|
|
import { useVersionContext } from "../../../builder/version/versionContext";
|
|
|
|
const VehicleUI = () => {
|
|
const { scene: startScene } = useGLTF(startPoint) as any;
|
|
const { scene: endScene } = useGLTF(startEnd) as any;
|
|
const startMarker = useRef<Group>(null);
|
|
const endMarker = useRef<Group>(null);
|
|
const prevMousePos = useRef({ x: 0, y: 0 });
|
|
const { selectedEventSphere } = useSelectedEventSphere();
|
|
const { selectedProductStore } = useProductContext();
|
|
const { vehicleStore, productStore } = useSceneContext();
|
|
const { selectedProduct } = selectedProductStore();
|
|
const { vehicles, getVehicleById } = vehicleStore();
|
|
const { updateEvent } = productStore();
|
|
const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 1, 0,]);
|
|
|
|
const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 1, 0,]);
|
|
const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0,]);
|
|
|
|
const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0,]);
|
|
const [steeringRotation, setSteeringRotation] = useState<[number, number, number]>([0, 0, 0]);
|
|
|
|
const { isDragging, setIsDragging } = useIsDragging();
|
|
const { isRotating, setIsRotating } = useIsRotating();
|
|
const { raycaster } = useThree();
|
|
const [point, setPoint] = useState<[number, number, number]>([0, 0, 0]);
|
|
const plane = useRef(new Plane(new Vector3(0, 1, 0), 0));
|
|
const [tubeRotation, setTubeRotation] = useState<boolean>(false);
|
|
const tubeRef = useRef<Group>(null);
|
|
const outerGroup = useRef<Group>(null);
|
|
const state: Types.ThreeState = useThree();
|
|
const controls: any = state.controls;
|
|
const [selectedVehicleData, setSelectedVehicleData] = useState<{ position: [number, number, number]; rotation: [number, number, number]; }>({ position: [0, 0, 0], rotation: [0, 0, 0] });
|
|
const CIRCLE_RADIUS = 0.8;
|
|
const { selectedVersionStore } = useVersionContext();
|
|
const { selectedVersion } = selectedVersionStore();
|
|
const { projectId } = useParams();
|
|
|
|
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 selectedVehicle = getVehicleById(
|
|
selectedEventSphere.userData.modelUuid
|
|
);
|
|
|
|
if (selectedVehicle) {
|
|
setSelectedVehicleData({
|
|
position: selectedVehicle.position,
|
|
rotation: selectedVehicle.rotation,
|
|
});
|
|
setPoint(selectedVehicle.point.position);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (selectedVehicle?.point?.action) {
|
|
const { pickUpPoint, unLoadPoint, steeringAngle } = selectedVehicle.point.action;
|
|
|
|
if (pickUpPoint && outerGroup.current) {
|
|
const worldPos = new Vector3(
|
|
pickUpPoint.position.x,
|
|
pickUpPoint.position.y,
|
|
pickUpPoint.position.z
|
|
);
|
|
const localPosition = outerGroup.current.worldToLocal(worldPos.clone());
|
|
|
|
setStartPosition([
|
|
localPosition.x,
|
|
selectedVehicle.point.position[1],
|
|
localPosition.z,
|
|
]);
|
|
setStartRotation([
|
|
pickUpPoint.rotation.x,
|
|
pickUpPoint.rotation.y,
|
|
pickUpPoint.rotation.z,
|
|
]);
|
|
} else {
|
|
setStartPosition([0, selectedVehicle.point.position[1] + 0.1, 1.5]);
|
|
setStartRotation([0, 0, 0]);
|
|
}
|
|
// end point
|
|
if (unLoadPoint && outerGroup.current) {
|
|
const worldPos = new Vector3(
|
|
unLoadPoint.position.x,
|
|
unLoadPoint.position.y,
|
|
unLoadPoint.position.z
|
|
);
|
|
const localPosition = outerGroup.current.worldToLocal(worldPos);
|
|
|
|
setEndPosition([
|
|
localPosition.x,
|
|
selectedVehicle.point.position[1],
|
|
localPosition.z,
|
|
]);
|
|
setEndRotation([
|
|
unLoadPoint.rotation.x,
|
|
unLoadPoint.rotation.y,
|
|
unLoadPoint.rotation.z,
|
|
]);
|
|
} else {
|
|
setEndPosition([0, selectedVehicle.point.position[1] + 0.1, -1.5]);
|
|
setEndRotation([0, 0, 0]);
|
|
}
|
|
setSteeringRotation([0, steeringAngle, 0]);
|
|
}
|
|
}, 10);
|
|
}, [selectedEventSphere, outerGroup.current, vehicles]);
|
|
|
|
const handlePointerDown = (
|
|
e: any,
|
|
state: "start" | "end",
|
|
rotation: "start" | "end"
|
|
) => {
|
|
if (e.object.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);
|
|
if (controls) controls.enabled = false;
|
|
setIsDragging(null);
|
|
} else {
|
|
setIsDragging(state);
|
|
setIsRotating(null);
|
|
if (controls) controls.enabled = false;
|
|
}
|
|
};
|
|
|
|
const handlePointerUp = () => {
|
|
controls.enabled = true;
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
|
|
if (selectedEventSphere?.userData.modelUuid) {
|
|
const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid);
|
|
|
|
let globalStartPosition = null;
|
|
let globalEndPosition = null;
|
|
|
|
if (outerGroup.current && startMarker.current && endMarker.current) {
|
|
const worldPosStart = new Vector3(...startPosition);
|
|
globalStartPosition = outerGroup.current.localToWorld(
|
|
worldPosStart.clone()
|
|
);
|
|
const worldPosEnd = new Vector3(...endPosition);
|
|
globalEndPosition = outerGroup.current.localToWorld(
|
|
worldPosEnd.clone()
|
|
);
|
|
}
|
|
if (updatedVehicle && globalEndPosition && globalStartPosition) {
|
|
const event = updateEvent(
|
|
selectedProduct.productUuid,
|
|
selectedEventSphere.userData.modelUuid,
|
|
{
|
|
point: {
|
|
...updatedVehicle.point,
|
|
action: {
|
|
...updatedVehicle.point?.action,
|
|
pickUpPoint: {
|
|
position: {
|
|
x: globalStartPosition.x,
|
|
y: 0,
|
|
z: globalStartPosition.z,
|
|
},
|
|
rotation: { x: 0, y: startRotation[1], z: 0 },
|
|
},
|
|
unLoadPoint: {
|
|
position: {
|
|
x: globalEndPosition.x,
|
|
y: 0,
|
|
z: globalEndPosition.z,
|
|
},
|
|
rotation: { x: 0, y: endRotation[1], z: 0 },
|
|
},
|
|
steeringAngle: steeringRotation[1],
|
|
},
|
|
},
|
|
}
|
|
);
|
|
|
|
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());
|
|
if (isDragging === "start") {
|
|
setStartPosition([localPoint.x, point[1], localPoint.z]);
|
|
} else if (isDragging === "end") {
|
|
setEndPosition([localPoint.x, point[1], localPoint.z]);
|
|
}
|
|
});
|
|
|
|
useEffect(() => {
|
|
const handleGlobalPointerUp = () => {
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
setTubeRotation(false);
|
|
if (controls) controls.enabled = true;
|
|
handlePointerUp();
|
|
};
|
|
|
|
if (isDragging || isRotating || tubeRotation) {
|
|
window.addEventListener("pointerup", handleGlobalPointerUp);
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener("pointerup", handleGlobalPointerUp);
|
|
};
|
|
}, [isDragging, isRotating, startPosition, startRotation, endPosition, endRotation, tubeRotation, steeringRotation, outerGroup.current, tubeRef.current,]);
|
|
|
|
const prevSteeringY = useRef(0);
|
|
|
|
useFrame((state) => {
|
|
if (tubeRotation) {
|
|
const currentPointerX = state.pointer.x;
|
|
const deltaX = currentPointerX - prevMousePos.current.x;
|
|
prevMousePos.current.x = currentPointerX;
|
|
|
|
const marker = tubeRef.current;
|
|
if (marker) {
|
|
const rotationSpeed = 2;
|
|
marker.rotation.y += deltaX * rotationSpeed;
|
|
setSteeringRotation([
|
|
marker.rotation.x,
|
|
marker.rotation.y,
|
|
marker.rotation.z,
|
|
]);
|
|
}
|
|
} else {
|
|
prevSteeringY.current = 0;
|
|
}
|
|
});
|
|
|
|
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 : endMarker.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 {
|
|
setEndRotation([
|
|
marker.rotation.x,
|
|
marker.rotation.y,
|
|
marker.rotation.z,
|
|
]);
|
|
}
|
|
}
|
|
});
|
|
|
|
return selectedVehicleData ? (
|
|
<group
|
|
position={selectedVehicleData.position}
|
|
ref={outerGroup}
|
|
>
|
|
<group
|
|
position={[0, 0, 0]}
|
|
ref={tubeRef}
|
|
rotation={steeringRotation}
|
|
onPointerDown={(e) => {
|
|
e.stopPropagation();
|
|
setTubeRotation(true);
|
|
prevMousePos.current.x = e.pointer.x;
|
|
controls.enabled = false;
|
|
}}
|
|
onPointerMissed={() => {
|
|
controls.enabled = true;
|
|
setTubeRotation(false);
|
|
}}
|
|
onPointerUp={() => {
|
|
controls.enabled = true;
|
|
setTubeRotation(false);
|
|
}}
|
|
>
|
|
(
|
|
<mesh
|
|
position={[0, point[1], 0]}
|
|
rotation={[-Math.PI / 2, 0, 0]}
|
|
name="steering"
|
|
>
|
|
<ringGeometry args={[CIRCLE_RADIUS, CIRCLE_RADIUS + 0.2, 36]} />
|
|
<meshBasicMaterial color="yellow" side={DoubleSide} />
|
|
</mesh>
|
|
<mesh
|
|
position={[0, point[1], CIRCLE_RADIUS + 0.24]}
|
|
rotation={[Math.PI / 2, 0, 0]}
|
|
>
|
|
<coneGeometry args={[0.1, 0.3, 12]} />
|
|
<meshBasicMaterial color="yellow" side={DoubleSide} />
|
|
</mesh>
|
|
)
|
|
</group>
|
|
|
|
{/* Start Marker */}
|
|
<primitive
|
|
name="startMarker"
|
|
object={startScene}
|
|
ref={startMarker}
|
|
position={startPosition}
|
|
rotation={startRotation}
|
|
onPointerDown={(e: any) => {
|
|
e.stopPropagation();
|
|
handlePointerDown(e, "start", "start");
|
|
}}
|
|
onPointerMissed={() => {
|
|
controls.enabled = true;
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
}}
|
|
/>
|
|
|
|
{/* End Marker */}
|
|
<primitive
|
|
name="endMarker"
|
|
object={endScene}
|
|
ref={endMarker}
|
|
position={endPosition}
|
|
rotation={endRotation}
|
|
onPointerDown={(e: any) => {
|
|
e.stopPropagation();
|
|
handlePointerDown(e, "end", "end");
|
|
}}
|
|
onPointerMissed={() => {
|
|
controls.enabled = true;
|
|
setIsDragging(null);
|
|
setIsRotating(null);
|
|
}}
|
|
/>
|
|
</group>
|
|
) : null;
|
|
};
|
|
export default VehicleUI;
|