Files
Dwinzo_Demo/app/src/modules/simulation/spatialUI/vehicle/vehicleUI.tsx
Jerald-Golden-B 74094aee9f feat: Add assembly action handling and UI components
- 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.
2025-07-07 15:00:16 +05:30

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;