added temp vehicle schema that holds the path details and vehicle details
This commit is contained in:
@@ -180,11 +180,11 @@ function PointsCreator() {
|
||||
drag = false;
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
const onMouseUp = (e : MouseEvent) => {
|
||||
if (selectedEventSphere && !drag) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === "Event-Sphere");
|
||||
if (intersects.length === 0) {
|
||||
if (intersects.length === 0 && e.button === 0) {
|
||||
clearSelectedEventSphere();
|
||||
setTransformMode(null);
|
||||
}
|
||||
|
||||
@@ -199,6 +199,7 @@ const VehicleUI = () => {
|
||||
steeringAngle: steeringRotation[1],
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,365 +1,409 @@
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useFrame, useThree, ThreeEvent } from '@react-three/fiber';
|
||||
import * as THREE from 'three';
|
||||
import { Line } from '@react-three/drei';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useActiveTool, useSelectedPath } from '../../../../../store/builder/store';
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useFrame, useThree, ThreeEvent } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { Line } from "@react-three/drei";
|
||||
import {
|
||||
useAnimationPlaySpeed,
|
||||
usePauseButtonStore,
|
||||
usePlayButtonStore,
|
||||
useResetButtonStore,
|
||||
} from "../../../../../store/usePlayButtonStore";
|
||||
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||
import {
|
||||
useActiveTool,
|
||||
useSelectedPath,
|
||||
} from "../../../../../store/builder/store";
|
||||
|
||||
interface VehicleAnimatorProps {
|
||||
path: [number, number, number][];
|
||||
handleCallBack: () => void;
|
||||
reset: () => void;
|
||||
startUnloadingProcess: () => void;
|
||||
currentPhase: string;
|
||||
agvUuid: string;
|
||||
agvDetail: VehicleStatus;
|
||||
path: [number, number, number][];
|
||||
handleCallBack: () => void;
|
||||
reset: () => void;
|
||||
startUnloadingProcess: () => void;
|
||||
currentPhase: string;
|
||||
agvUuid: string;
|
||||
agvDetail: VehicleStatus;
|
||||
}
|
||||
|
||||
function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset, startUnloadingProcess }: Readonly<VehicleAnimatorProps>) {
|
||||
const { vehicleStore } = useSceneContext();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isReset, setReset } = useResetButtonStore();
|
||||
const progressRef = useRef<number>(0);
|
||||
const movingForward = useRef<boolean>(true);
|
||||
const completedRef = useRef<boolean>(false);
|
||||
const [objectRotation, setObjectRotation] = useState<{ x: number; y: number; z: number } | undefined>(agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 })
|
||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||
const { scene, controls } = useThree();
|
||||
const { selectedPath } = useSelectedPath();
|
||||
const [isAnyDragging, setIsAnyDragging] = useState<string>("");
|
||||
function VehicleAnimator({
|
||||
path,
|
||||
handleCallBack,
|
||||
currentPhase,
|
||||
agvUuid,
|
||||
agvDetail,
|
||||
reset,
|
||||
startUnloadingProcess,
|
||||
}: Readonly<VehicleAnimatorProps>) {
|
||||
const { vehicleStore } = useSceneContext();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isReset, setReset } = useResetButtonStore();
|
||||
const progressRef = useRef<number>(0);
|
||||
const movingForward = useRef<boolean>(true);
|
||||
const completedRef = useRef<boolean>(false);
|
||||
const [objectRotation, setObjectRotation] = useState<
|
||||
{ x: number; y: number; z: number } | undefined
|
||||
>(agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 });
|
||||
const [restRotation, setRestingRotation] = useState<boolean>(true);
|
||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>(
|
||||
[]
|
||||
);
|
||||
const { scene, controls } = useThree();
|
||||
const { selectedPath } = useSelectedPath();
|
||||
const [isAnyDragging, setIsAnyDragging] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPhase === "stationed-pickup" && path.length > 0) {
|
||||
setCurrentPath(path);
|
||||
setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation);
|
||||
} else if (currentPhase === "pickup-drop" && path.length > 0) {
|
||||
setObjectRotation(agvDetail.point.action?.unLoadPoint?.rotation);
|
||||
setCurrentPath(path);
|
||||
} else if (currentPhase === "drop-pickup" && path.length > 0) {
|
||||
setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation);
|
||||
setCurrentPath(path);
|
||||
}
|
||||
}, [currentPhase, path, objectRotation, selectedPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentPhase === 'stationed-pickup' && path.length > 0 && selectedPath === "auto") {
|
||||
setCurrentPath(path);
|
||||
setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation)
|
||||
} else if (currentPhase === 'pickup-drop' && path.length > 0) {
|
||||
setObjectRotation(agvDetail.point.action?.unLoadPoint?.rotation)
|
||||
setCurrentPath(path);
|
||||
} else if (currentPhase === 'drop-pickup' && path.length > 0) {
|
||||
setObjectRotation(agvDetail.point.action?.pickUpPoint?.rotation)
|
||||
setCurrentPath(path);
|
||||
useEffect(() => {
|
||||
completedRef.current = false;
|
||||
}, [currentPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
reset();
|
||||
setCurrentPath([]);
|
||||
completedRef.current = false;
|
||||
movingForward.current = true;
|
||||
progressRef.current = 0;
|
||||
setReset(false);
|
||||
setRestingRotation(true);
|
||||
const object = scene.getObjectByProperty("uuid", agvUuid);
|
||||
const vehicle = getVehicleById(agvDetail.modelUuid);
|
||||
if (object && vehicle) {
|
||||
object.position.set(
|
||||
vehicle.position[0],
|
||||
vehicle.position[1],
|
||||
vehicle.position[2]
|
||||
);
|
||||
object.rotation.set(
|
||||
vehicle.rotation[0],
|
||||
vehicle.rotation[1],
|
||||
vehicle.rotation[2]
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [isReset, isPlaying]);
|
||||
|
||||
const lastTimeRef = useRef(performance.now());
|
||||
|
||||
useFrame(() => {
|
||||
if (!isPlaying) return;
|
||||
const now = performance.now();
|
||||
const delta = (now - lastTimeRef.current) / 1000;
|
||||
lastTimeRef.current = now;
|
||||
|
||||
const object = scene.getObjectByProperty("uuid", agvUuid);
|
||||
if (!object || currentPath.length < 2) return;
|
||||
if (isPaused) return;
|
||||
|
||||
let totalDistance = 0;
|
||||
const distances = [];
|
||||
let accumulatedDistance = 0;
|
||||
let index = 0;
|
||||
const rotationSpeed = 0.75;
|
||||
|
||||
for (let i = 0; i < currentPath.length - 1; i++) {
|
||||
const start = new THREE.Vector3(...currentPath[i]);
|
||||
const end = new THREE.Vector3(...currentPath[i + 1]);
|
||||
const segmentDistance = start.distanceTo(end);
|
||||
distances.push(segmentDistance);
|
||||
totalDistance += segmentDistance;
|
||||
}
|
||||
|
||||
while (
|
||||
index < distances.length &&
|
||||
progressRef.current > accumulatedDistance + distances[index]
|
||||
) {
|
||||
accumulatedDistance += distances[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < distances.length) {
|
||||
const start = new THREE.Vector3(...currentPath[index]);
|
||||
const end = new THREE.Vector3(...currentPath[index + 1]);
|
||||
const segmentDistance = distances[index];
|
||||
|
||||
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(
|
||||
new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0))
|
||||
);
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
Math.PI
|
||||
);
|
||||
targetQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
|
||||
if (angle < step) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
}, [currentPhase, path, objectRotation, selectedPath]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
completedRef.current = false;
|
||||
}, [currentPath]);
|
||||
const isAligned = angle < 0.01;
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset || !isPlaying) {
|
||||
reset();
|
||||
setCurrentPath([]);
|
||||
completedRef.current = false;
|
||||
movingForward.current = true;
|
||||
progressRef.current = 0;
|
||||
setReset(false);
|
||||
setRestingRotation(true);
|
||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
||||
const vehicle = getVehicleById(agvDetail.modelUuid);
|
||||
if (object && vehicle) {
|
||||
object.position.set(vehicle.position[0], vehicle.position[1], vehicle.position[2]);
|
||||
object.rotation.set(vehicle.rotation[0], vehicle.rotation[1], vehicle.rotation[2]);
|
||||
if (isAligned) {
|
||||
progressRef.current += delta * (speed * agvDetail.speed);
|
||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||
const position = start.clone().lerp(end, t);
|
||||
object.position.copy(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
if (restRotation && objectRotation) {
|
||||
const targetEuler = new THREE.Euler(
|
||||
0,
|
||||
objectRotation.y - agvDetail.point.action.steeringAngle,
|
||||
0
|
||||
);
|
||||
|
||||
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(
|
||||
new THREE.Vector3(0, 1, 0),
|
||||
Math.PI
|
||||
);
|
||||
const targetQuaternion = baseQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
setRestingRotation(false);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
|
||||
if (angle < step) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
setRestingRotation(true);
|
||||
progressRef.current = 0;
|
||||
movingForward.current = !movingForward.current;
|
||||
setCurrentPath([]);
|
||||
handleCallBack();
|
||||
if (currentPhase === "pickup-drop") {
|
||||
requestAnimationFrame(startUnloadingProcess);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatePoint = (index: number, pos: THREE.Vector3) => {
|
||||
const updated = [...currentPath];
|
||||
updated[index] = pos.toArray() as [number, number, number];
|
||||
setCurrentPath(updated);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedPath === "auto" && (
|
||||
<group visible={false}>
|
||||
{currentPath.map((pos, i) => {
|
||||
if (i < currentPath.length - 1) {
|
||||
return (
|
||||
<DraggableLineSegment
|
||||
key={i}
|
||||
index={i}
|
||||
start={new THREE.Vector3(...currentPath[i])}
|
||||
end={new THREE.Vector3(...currentPath[i + 1])}
|
||||
updatePoints={(i0, p0, i1, p1) => {
|
||||
const updated = [...currentPath];
|
||||
updated[i0] = p0.toArray() as [number, number, number];
|
||||
updated[i1] = p1.toArray() as [number, number, number];
|
||||
setCurrentPath(updated);
|
||||
}}
|
||||
isAnyDragging={isAnyDragging}
|
||||
setIsAnyDragging={setIsAnyDragging}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [isReset, isPlaying])
|
||||
|
||||
const lastTimeRef = useRef(performance.now());
|
||||
|
||||
useFrame(() => {
|
||||
if (!isPlaying) return
|
||||
const now = performance.now();
|
||||
const delta = (now - lastTimeRef.current) / 1000;
|
||||
lastTimeRef.current = now;
|
||||
|
||||
const object = scene.getObjectByProperty('uuid', agvUuid);
|
||||
if (!object || currentPath.length < 2) return;
|
||||
if (isPaused) return;
|
||||
|
||||
let totalDistance = 0;
|
||||
const distances = [];
|
||||
let accumulatedDistance = 0;
|
||||
let index = 0;
|
||||
const rotationSpeed = 0.75;
|
||||
|
||||
for (let i = 0; i < currentPath.length - 1; i++) {
|
||||
const start = new THREE.Vector3(...currentPath[i]);
|
||||
const end = new THREE.Vector3(...currentPath[i + 1]);
|
||||
const segmentDistance = start.distanceTo(end);
|
||||
distances.push(segmentDistance);
|
||||
totalDistance += segmentDistance;
|
||||
}
|
||||
|
||||
while (index < distances.length && progressRef.current > accumulatedDistance + distances[index]) {
|
||||
accumulatedDistance += distances[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < distances.length) {
|
||||
const start = new THREE.Vector3(...currentPath[index]);
|
||||
const end = new THREE.Vector3(...currentPath[index + 1]);
|
||||
const segmentDistance = distances[index];
|
||||
|
||||
const targetQuaternion = new THREE.Quaternion().setFromRotationMatrix(new THREE.Matrix4().lookAt(start, end, new THREE.Vector3(0, 1, 0)));
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||
targetQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
|
||||
if (angle < step) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
}
|
||||
|
||||
const isAligned = angle < 0.01;
|
||||
|
||||
if (isAligned) {
|
||||
progressRef.current += delta * (speed * agvDetail.speed);
|
||||
const t = (progressRef.current - accumulatedDistance) / segmentDistance;
|
||||
const position = start.clone().lerp(end, t);
|
||||
object.position.copy(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
if (restRotation && objectRotation) {
|
||||
const targetEuler = new THREE.Euler(0, objectRotation.y - agvDetail.point.action.steeringAngle, 0);
|
||||
|
||||
const baseQuaternion = new THREE.Quaternion().setFromEuler(targetEuler);
|
||||
const y180 = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI);
|
||||
const targetQuaternion = baseQuaternion.multiply(y180);
|
||||
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
if (angle < 0.01) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
setRestingRotation(false);
|
||||
} else {
|
||||
const step = rotationSpeed * delta * speed * agvDetail.speed;
|
||||
const angle = object.quaternion.angleTo(targetQuaternion);
|
||||
|
||||
if (angle < step) {
|
||||
object.quaternion.copy(targetQuaternion);
|
||||
} else {
|
||||
object.quaternion.rotateTowards(targetQuaternion, step);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (progressRef.current >= totalDistance) {
|
||||
setRestingRotation(true);
|
||||
progressRef.current = 0;
|
||||
movingForward.current = !movingForward.current;
|
||||
setCurrentPath([]);
|
||||
handleCallBack();
|
||||
if (currentPhase === 'pickup-drop') {
|
||||
requestAnimationFrame(startUnloadingProcess);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const updatePoint = (index: number, pos: THREE.Vector3) => {
|
||||
const updated = [...currentPath];
|
||||
updated[index] = pos.toArray() as [number, number, number];
|
||||
setCurrentPath(updated);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedPath === "auto" &&
|
||||
<group visible={false}>
|
||||
{currentPath.map((pos, i) => {
|
||||
if (i < currentPath.length - 1) {
|
||||
return (
|
||||
<DraggableLineSegment
|
||||
key={i}
|
||||
index={i}
|
||||
start={new THREE.Vector3(...currentPath[i])}
|
||||
end={new THREE.Vector3(...currentPath[i + 1])}
|
||||
updatePoints={(i0, p0, i1, p1) => {
|
||||
const updated = [...currentPath];
|
||||
updated[i0] = p0.toArray() as [number, number, number];
|
||||
updated[i1] = p1.toArray() as [number, number, number];
|
||||
setCurrentPath(updated);
|
||||
}}
|
||||
isAnyDragging={isAnyDragging}
|
||||
setIsAnyDragging={setIsAnyDragging}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
{currentPath.length > 0 && (
|
||||
<group onPointerMissed={() => { if (controls) (controls as any).enabled = true; }}>
|
||||
{currentPath.map((pos, i) =>
|
||||
(
|
||||
<DraggableSphere
|
||||
key={i}
|
||||
index={i}
|
||||
position={new THREE.Vector3(...pos)}
|
||||
onMove={updatePoint}
|
||||
isAnyDragging={isAnyDragging}
|
||||
setIsAnyDragging={setIsAnyDragging}
|
||||
/>)
|
||||
)}
|
||||
</group >
|
||||
)}
|
||||
</group >
|
||||
}
|
||||
</>
|
||||
);
|
||||
return null;
|
||||
})}
|
||||
{currentPath.length > 0 && (
|
||||
<group
|
||||
onPointerMissed={() => {
|
||||
if (controls) (controls as any).enabled = true;
|
||||
}}
|
||||
>
|
||||
{currentPath.map((pos, i) => (
|
||||
<DraggableSphere
|
||||
key={i}
|
||||
index={i}
|
||||
position={new THREE.Vector3(...pos)}
|
||||
onMove={updatePoint}
|
||||
isAnyDragging={isAnyDragging}
|
||||
setIsAnyDragging={setIsAnyDragging}
|
||||
/>
|
||||
))}
|
||||
</group>
|
||||
)}
|
||||
</group>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default VehicleAnimator;
|
||||
|
||||
|
||||
|
||||
|
||||
function DraggableSphere({
|
||||
index,
|
||||
position,
|
||||
onMove,
|
||||
isAnyDragging,
|
||||
setIsAnyDragging,
|
||||
index,
|
||||
position,
|
||||
onMove,
|
||||
isAnyDragging,
|
||||
setIsAnyDragging,
|
||||
}: {
|
||||
index: number;
|
||||
position: THREE.Vector3;
|
||||
onMove: (index: number, pos: THREE.Vector3) => void;
|
||||
isAnyDragging: string;
|
||||
setIsAnyDragging: (val: string) => void;
|
||||
index: number;
|
||||
position: THREE.Vector3;
|
||||
onMove: (index: number, pos: THREE.Vector3) => void;
|
||||
isAnyDragging: string;
|
||||
setIsAnyDragging: (val: string) => void;
|
||||
}) {
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const { gl, controls, raycaster } = useThree();
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const { activeTool } = useActiveTool();
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const { gl, controls, raycaster } = useThree();
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const { activeTool } = useActiveTool();
|
||||
|
||||
const onPointerDown = (e: ThreeEvent<PointerEvent>) => {
|
||||
e.stopPropagation()
|
||||
if (activeTool !== 'pen') return;
|
||||
setIsAnyDragging("point");
|
||||
gl.domElement.style.cursor = 'grabbing';
|
||||
if (controls) (controls as any).enabled = false;
|
||||
const onPointerDown = (e: ThreeEvent<PointerEvent>) => {
|
||||
e.stopPropagation();
|
||||
if (activeTool !== "pen") return;
|
||||
setIsAnyDragging("point");
|
||||
gl.domElement.style.cursor = "grabbing";
|
||||
if (controls) (controls as any).enabled = false;
|
||||
};
|
||||
|
||||
const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
|
||||
if (isAnyDragging !== "point" || activeTool !== "pen") return;
|
||||
|
||||
const intersect = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
meshRef.current!.position.copy(intersect);
|
||||
onMove(index, intersect);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (activeTool !== "pen") return;
|
||||
setIsAnyDragging("");
|
||||
gl.domElement.style.cursor = "default";
|
||||
if (controls) (controls as any).enabled = true;
|
||||
};
|
||||
useEffect(() => {
|
||||
gl.domElement.addEventListener("pointerup", onPointerUp);
|
||||
return () => {
|
||||
gl.domElement.removeEventListener("pointerup", onPointerUp);
|
||||
};
|
||||
}, [activeTool]);
|
||||
|
||||
const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
|
||||
if (isAnyDragging !== "point" || activeTool !== 'pen') return;
|
||||
|
||||
const intersect = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
meshRef.current!.position.copy(intersect);
|
||||
onMove(index, intersect);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (activeTool !== 'pen') return;
|
||||
setIsAnyDragging("");
|
||||
gl.domElement.style.cursor = 'default';
|
||||
if (controls) (controls as any).enabled = true;
|
||||
};
|
||||
useEffect(() => {
|
||||
gl.domElement.addEventListener("pointerup", onPointerUp);
|
||||
return (() => {
|
||||
gl.domElement.removeEventListener("pointerup", onPointerUp);
|
||||
})
|
||||
}, [activeTool])
|
||||
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
position={position}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerMissed={onPointerUp}
|
||||
>
|
||||
<sphereGeometry args={[0.2, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
);
|
||||
return (
|
||||
<mesh
|
||||
ref={meshRef}
|
||||
position={position}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerMissed={onPointerUp}
|
||||
>
|
||||
<sphereGeometry args={[0.2, 16, 16]} />
|
||||
<meshStandardMaterial color="red" />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
function DraggableLineSegment({
|
||||
index,
|
||||
start,
|
||||
end,
|
||||
updatePoints,
|
||||
isAnyDragging,
|
||||
setIsAnyDragging,
|
||||
index,
|
||||
start,
|
||||
end,
|
||||
updatePoints,
|
||||
isAnyDragging,
|
||||
setIsAnyDragging,
|
||||
}: {
|
||||
index: number;
|
||||
start: THREE.Vector3;
|
||||
end: THREE.Vector3;
|
||||
updatePoints: (i0: number, p0: THREE.Vector3, i1: number, p1: THREE.Vector3) => void;
|
||||
isAnyDragging: string;
|
||||
setIsAnyDragging: (val: string) => void;
|
||||
index: number;
|
||||
start: THREE.Vector3;
|
||||
end: THREE.Vector3;
|
||||
updatePoints: (
|
||||
i0: number,
|
||||
p0: THREE.Vector3,
|
||||
i1: number,
|
||||
p1: THREE.Vector3
|
||||
) => void;
|
||||
isAnyDragging: string;
|
||||
setIsAnyDragging: (val: string) => void;
|
||||
}) {
|
||||
const { gl, raycaster, controls } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const dragStart = useRef<THREE.Vector3 | null>(null);
|
||||
const { gl, raycaster, controls } = useThree();
|
||||
const { activeTool } = useActiveTool();
|
||||
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);
|
||||
const dragStart = useRef<THREE.Vector3 | null>(null);
|
||||
|
||||
const onPointerDown = () => {
|
||||
if (activeTool !== 'pen' || isAnyDragging) return;
|
||||
setIsAnyDragging("line");
|
||||
gl.domElement.style.cursor = 'grabbing';
|
||||
if (controls) (controls as any).enabled = false;
|
||||
const onPointerDown = () => {
|
||||
if (activeTool !== "pen" || isAnyDragging) return;
|
||||
setIsAnyDragging("line");
|
||||
gl.domElement.style.cursor = "grabbing";
|
||||
if (controls) (controls as any).enabled = false;
|
||||
};
|
||||
|
||||
const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
|
||||
if (isAnyDragging !== "line" || activeTool !== "pen") return;
|
||||
|
||||
const intersect = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
if (!dragStart.current) dragStart.current = intersect.clone();
|
||||
const offset = new THREE.Vector3().subVectors(
|
||||
intersect,
|
||||
dragStart.current
|
||||
);
|
||||
const newStart = start.clone().add(offset);
|
||||
const newEnd = end.clone().add(offset);
|
||||
updatePoints(index, newStart, index + 1, newEnd);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (activeTool !== "pen") return;
|
||||
setIsAnyDragging("");
|
||||
dragStart.current = null;
|
||||
gl.domElement.style.cursor = "default";
|
||||
if (controls) (controls as any).enabled = true;
|
||||
};
|
||||
useEffect(() => {
|
||||
gl.domElement.addEventListener("pointerup", onPointerUp);
|
||||
return () => {
|
||||
gl.domElement.removeEventListener("pointerup", onPointerUp);
|
||||
};
|
||||
|
||||
const onPointerMove = (e: ThreeEvent<PointerEvent>) => {
|
||||
if (isAnyDragging !== "line" || activeTool !== 'pen') return;
|
||||
|
||||
const intersect = new THREE.Vector3();
|
||||
if (raycaster.ray.intersectPlane(plane, intersect)) {
|
||||
if (!dragStart.current) dragStart.current = intersect.clone();
|
||||
const offset = new THREE.Vector3().subVectors(intersect, dragStart.current);
|
||||
const newStart = start.clone().add(offset);
|
||||
const newEnd = end.clone().add(offset);
|
||||
updatePoints(index, newStart, index + 1, newEnd);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
if (activeTool !== 'pen') return;
|
||||
setIsAnyDragging("");
|
||||
dragStart.current = null;
|
||||
gl.domElement.style.cursor = 'default';
|
||||
if (controls) (controls as any).enabled = true;
|
||||
};
|
||||
useEffect(() => {
|
||||
gl.domElement.addEventListener("pointerup", onPointerUp);
|
||||
return (() => {
|
||||
gl.domElement.removeEventListener("pointerup", onPointerUp);
|
||||
})
|
||||
}, [activeTool])
|
||||
return (
|
||||
<Line
|
||||
points={[start, end]}
|
||||
color="blue"
|
||||
lineWidth={5}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerMissed={onPointerUp}
|
||||
/>
|
||||
);
|
||||
}, [activeTool]);
|
||||
return (
|
||||
<Line
|
||||
points={[start, end]}
|
||||
color="blue"
|
||||
lineWidth={5}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerMissed={onPointerUp}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useCreatedPaths } from "../../../../../store/builder/store";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { Vector3 } from "three";
|
||||
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||
import { useProductContext } from "../../../products/productContext";
|
||||
import { useSelectedEventSphere } from "../../../../../store/simulation/useSimulationStore";
|
||||
function dist(a: PointData, b: PointData): number {
|
||||
return Math.sqrt(
|
||||
(a.position[0] - b.position[0]) ** 2 +
|
||||
(a.position[1] - b.position[1]) ** 2 +
|
||||
(a.position[2] - b.position[2]) ** 2
|
||||
);
|
||||
}
|
||||
type SegmentPoint = {
|
||||
position: Vector3;
|
||||
originalPoint?: PointData;
|
||||
pathId?: string;
|
||||
startId?: string;
|
||||
endId?: string;
|
||||
};
|
||||
|
||||
/** --- A* Algorithm --- */
|
||||
type AStarResult = {
|
||||
pointIds: string[];
|
||||
distance: number;
|
||||
};
|
||||
|
||||
function aStarShortestPath(
|
||||
startId: string,
|
||||
goalId: string,
|
||||
points: PointData[],
|
||||
paths: PathData
|
||||
): AStarResult | null {
|
||||
const pointById = new Map(points.map((p) => [p.pointId, p]));
|
||||
const start = pointById.get(startId);
|
||||
const goal = pointById.get(goalId);
|
||||
if (!start || !goal) return null;
|
||||
|
||||
const openSet = new Set<string>([startId]);
|
||||
const cameFrom: Record<string, string | null> = {};
|
||||
const gScore: Record<string, number> = {};
|
||||
const fScore: Record<string, number> = {};
|
||||
|
||||
for (const p of points) {
|
||||
cameFrom[p.pointId] = null;
|
||||
gScore[p.pointId] = Infinity;
|
||||
fScore[p.pointId] = Infinity;
|
||||
}
|
||||
|
||||
gScore[startId] = 0;
|
||||
fScore[startId] = dist(start, goal);
|
||||
|
||||
const neighborsOf = (id: string): { id: string; cost: number }[] => {
|
||||
const me = pointById.get(id)!;
|
||||
const out: { id: string; cost: number }[] = [];
|
||||
for (const edge of paths) {
|
||||
const [a, b] = edge.pathPoints;
|
||||
if (a.pointId === id) out.push({ id: b.pointId, cost: dist(me, b) });
|
||||
else if (b.pointId === id) out.push({ id: a.pointId, cost: dist(me, a) });
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
while (openSet.size > 0) {
|
||||
let current: string = [...openSet].reduce((a, b) =>
|
||||
fScore[a] < fScore[b] ? a : b
|
||||
);
|
||||
|
||||
if (current === goalId) {
|
||||
const ids: string[] = [];
|
||||
let node: string | null = current;
|
||||
while (node) {
|
||||
ids.unshift(node);
|
||||
node = cameFrom[node];
|
||||
}
|
||||
return { pointIds: ids, distance: gScore[goalId] };
|
||||
}
|
||||
|
||||
openSet.delete(current);
|
||||
|
||||
for (const nb of neighborsOf(current)) {
|
||||
const tentativeG = gScore[current] + nb.cost;
|
||||
if (tentativeG < gScore[nb.id]) {
|
||||
cameFrom[nb.id] = current;
|
||||
gScore[nb.id] = tentativeG;
|
||||
fScore[nb.id] = tentativeG + dist(pointById.get(nb.id)!, goal);
|
||||
openSet.add(nb.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** --- Convert node path to edges --- */
|
||||
function nodePathToEdges(
|
||||
pointIds: string[],
|
||||
points: PointData[],
|
||||
paths: PathData
|
||||
): PathData {
|
||||
const byId = new Map(points.map((p) => [p.pointId, p]));
|
||||
const edges: PathData = [];
|
||||
|
||||
for (let i = 0; i < pointIds.length - 1; i++) {
|
||||
const a = pointIds[i];
|
||||
const b = pointIds[i + 1];
|
||||
|
||||
const edge = paths.find(
|
||||
(p) =>
|
||||
(p.pathPoints[0].pointId === a && p.pathPoints[1].pointId === b) ||
|
||||
(p.pathPoints[0].pointId === b && p.pathPoints[1].pointId === a)
|
||||
);
|
||||
|
||||
if (edge) {
|
||||
const [p1, p2] = edge.pathPoints;
|
||||
edges.push({
|
||||
pathId: edge.pathId,
|
||||
pathPoints:
|
||||
p1.pointId === a
|
||||
? ([p1, p2] as [PointData, PointData])
|
||||
: ([p2, p1] as [PointData, PointData]),
|
||||
});
|
||||
} else {
|
||||
const pa = byId.get(a)!;
|
||||
const pb = byId.get(b)!;
|
||||
edges.push({
|
||||
pathId: `synthetic-${a}-${b}`,
|
||||
pathPoints: [pa, pb],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
interface VehicleInstanceProps {
|
||||
vehicleData: VehicleStructure;
|
||||
vehiclesData: VehicleStructure[];
|
||||
setVehiclesData: React.Dispatch<React.SetStateAction<VehicleStructure[]>>;
|
||||
}
|
||||
export default function VehicleInstance2({
|
||||
vehicleData,
|
||||
vehiclesData,
|
||||
setVehiclesData,
|
||||
}: VehicleInstanceProps) {
|
||||
const { paths, setPaths } = useCreatedPaths();
|
||||
const { vehicleStore, productStore } = useSceneContext();
|
||||
const { vehicles, getVehicleById } = vehicleStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { updateEvent, updateAction } = productStore();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
|
||||
const { scene, gl, raycaster } = useThree();
|
||||
const [selected, setSelected] = useState<any>([]);
|
||||
const allPoints = useMemo(() => {
|
||||
const points: PointData[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
paths?.forEach((path: PathDataInterface) => {
|
||||
path.pathPoints.forEach((p) => {
|
||||
if (!seen.has(p.pointId)) {
|
||||
seen.add(p.pointId);
|
||||
points.push(p);
|
||||
}
|
||||
});
|
||||
});
|
||||
return points;
|
||||
}, [paths]);
|
||||
const vehiclesDataRef = useRef(vehiclesData);
|
||||
const selectedEventSphereRef = useRef(selectedEventSphere);
|
||||
|
||||
useEffect(() => {
|
||||
vehiclesDataRef.current = vehiclesData;
|
||||
}, [vehiclesData]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedEventSphereRef.current = selectedEventSphere;
|
||||
}, [selectedEventSphere]);
|
||||
|
||||
const handleContextMenu = (e: any) => {
|
||||
const intersectObject = raycaster.intersectObjects(scene.children);
|
||||
if (intersectObject.length > 0) {
|
||||
const pathPoint = intersectObject[0].object;
|
||||
if (pathPoint.name === "Path-Point") {
|
||||
const point: any = pathPoint.userData;
|
||||
|
||||
const pointIndex = allPoints.findIndex(
|
||||
(p) => p.pointId === point.pointId
|
||||
);
|
||||
if (pointIndex === -1) return;
|
||||
|
||||
setSelected((prev: any) => {
|
||||
if (prev.length === 0) {
|
||||
return [pointIndex];
|
||||
}
|
||||
|
||||
if (prev.length === 1) {
|
||||
const prevPoint = allPoints[prev[0]];
|
||||
console.log("prevPoint: ", prevPoint);
|
||||
const newPoint = allPoints[pointIndex];
|
||||
console.log("newPoint: ", newPoint);
|
||||
|
||||
if (prevPoint.pointId === newPoint.pointId) return prev;
|
||||
|
||||
const result = aStarShortestPath(
|
||||
prevPoint.pointId,
|
||||
newPoint.pointId,
|
||||
allPoints,
|
||||
paths
|
||||
);
|
||||
|
||||
if (result) {
|
||||
const edges = nodePathToEdges(result.pointIds, allPoints, paths);
|
||||
|
||||
setTimeout(() => {
|
||||
const modelUuid = selectedEventSphere?.userData?.modelUuid;
|
||||
const index = vehiclesData.findIndex(
|
||||
(v) => v.vehicleId === modelUuid
|
||||
);
|
||||
|
||||
if (index !== -1) {
|
||||
const updatedVehicles = [...vehiclesData];
|
||||
updatedVehicles[index] = {
|
||||
...updatedVehicles[index],
|
||||
startPoint: prevPoint.position,
|
||||
endPoint: newPoint.position,
|
||||
route: edges,
|
||||
};
|
||||
setVehiclesData(updatedVehicles);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return [prev[0], pointIndex];
|
||||
}
|
||||
|
||||
return [pointIndex];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const canvasElement = gl.domElement;
|
||||
canvasElement.addEventListener("contextmenu", handleContextMenu);
|
||||
return () => {
|
||||
canvasElement.removeEventListener("contextmenu", handleContextMenu);
|
||||
};
|
||||
}, [raycaster]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,24 +1,49 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import VehicleInstance from "./instance/vehicleInstance";
|
||||
import VehicleContentUi from "../../ui3d/VehicleContentUi";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import { useViewSceneStore } from "../../../../store/builder/store";
|
||||
import PathCreator from "../pathCreator/pathCreator";
|
||||
import VehicleInstance2 from "./instance/vehicleInstance2";
|
||||
|
||||
function VehicleInstances() {
|
||||
const { vehicleStore } = useSceneContext();
|
||||
const { vehicles } = vehicleStore();
|
||||
const { viewSceneLabels } = useViewSceneStore();
|
||||
const { vehicleStore } = useSceneContext();
|
||||
const { vehicles } = vehicleStore();
|
||||
const [vehiclesData, setVehiclesData] = useState<VehicleStructure[]>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{vehicles.map((vehicle: VehicleStatus) => (
|
||||
<React.Fragment key={vehicle.modelUuid}>
|
||||
<VehicleInstance agvDetail={vehicle} />
|
||||
{viewSceneLabels && <VehicleContentUi vehicle={vehicle} />}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
useEffect(() => {
|
||||
const updatedVehicles = vehicles.map((val) => ({
|
||||
vehicleId: val.modelUuid,
|
||||
position: val.position,
|
||||
rotation: val.rotation,
|
||||
startPoint: null,
|
||||
endPoint: null,
|
||||
selectedPointId: val.point.uuid,
|
||||
}));
|
||||
|
||||
console.log("updatedVehicles: ", updatedVehicles);
|
||||
setVehiclesData(updatedVehicles);
|
||||
}, [vehicles]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* {vehicles.map((vehicle: VehicleStatus) => (
|
||||
<React.Fragment key={vehicle.modelUuid}>
|
||||
<VehicleInstance agvDetail={vehicle} />
|
||||
{viewSceneLabels && <VehicleContentUi vehicle={vehicle} />}
|
||||
</React.Fragment>
|
||||
))} */}
|
||||
{vehiclesData.map((vehicle: VehicleStructure) => (
|
||||
<React.Fragment key={vehicle.vehicleId}>
|
||||
<VehicleInstance2
|
||||
vehicleData={vehicle}
|
||||
vehiclesData={vehiclesData}
|
||||
setVehiclesData={setVehiclesData}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default VehicleInstances;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useCreatedPaths } from "../../../../../store/builder/store";
|
||||
|
||||
export const usePathManager = (pathId?: string, vehicleId?: string | null) => {
|
||||
const { paths, allPaths, setAllPaths } = useCreatedPaths();
|
||||
console.log("allPaths: ", allPaths);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!paths || paths.length === 0) return;
|
||||
|
||||
@@ -35,11 +35,6 @@ interface PathDataInterface {
|
||||
type PathData = PathDataInterface[];
|
||||
|
||||
export default function PathCreator() {
|
||||
// const [paths, setPaths] = useState<PathData>([]);
|
||||
// const [paths, setPaths] = useState<PathData>(() => {
|
||||
// const stored = localStorage.getItem("paths");
|
||||
// return stored ? JSON.parse(stored) : [];
|
||||
// });
|
||||
const { paths, setPaths } = useCreatedPaths();
|
||||
const { activeTool } = useActiveTool();
|
||||
const { toolMode } = useToolMode();
|
||||
@@ -62,9 +57,9 @@ export default function PathCreator() {
|
||||
[]
|
||||
);
|
||||
const plane = useMemo(() => new Plane(new Vector3(0, 1, 0), 0), []);
|
||||
|
||||
|
||||
const { scene, raycaster, gl } = useThree();
|
||||
|
||||
|
||||
const POINT_SNAP_THRESHOLD = 0.5;
|
||||
const CAN_POINT_SNAP = true;
|
||||
|
||||
@@ -80,8 +75,10 @@ export default function PathCreator() {
|
||||
) ?? []
|
||||
);
|
||||
}, [paths, draftPoints]);
|
||||
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem("paths");
|
||||
|
||||
setPaths(stored ? JSON.parse(stored) : []);
|
||||
}, []);
|
||||
|
||||
@@ -344,7 +341,7 @@ export default function PathCreator() {
|
||||
const allPoints = useMemo(() => {
|
||||
const points: PointData[] = [];
|
||||
const seen = new Set<string>();
|
||||
console.log("paths: ", paths);
|
||||
|
||||
paths?.forEach((path: PathDataInterface) => {
|
||||
path.pathPoints.forEach((p) => {
|
||||
if (!seen.has(p.pointId)) {
|
||||
@@ -355,9 +352,9 @@ export default function PathCreator() {
|
||||
});
|
||||
return points;
|
||||
}, [paths]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
console.log("paths ", paths);
|
||||
localStorage.setItem("paths", JSON.stringify(paths));
|
||||
}, [paths]);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
} from "../../../../store/usePlayButtonStore";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import { usePathManager } from "./function/usePathManager";
|
||||
import { useProductContext } from "../../products/productContext";
|
||||
import { useSelectedEventSphere } from "../../../../store/simulation/useSimulationStore";
|
||||
type PointData = {
|
||||
pointId: string;
|
||||
position: [number, number, number];
|
||||
@@ -202,6 +204,12 @@ export default function PointHandler({
|
||||
const [multiPaths, setMultiPaths] = useState<
|
||||
{ id: number; path: PathData }[]
|
||||
>([]);
|
||||
const { vehicleStore, productStore } = useSceneContext();
|
||||
const { vehicles, getVehicleById } = vehicleStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { updateEvent, updateAction } = productStore();
|
||||
const { selectedEventSphere } = useSelectedEventSphere();
|
||||
const pathIdRef = useRef(1); // To ensure unique incremental IDs
|
||||
const { toolMode } = useToolMode();
|
||||
const { activeTool } = useActiveTool();
|
||||
@@ -230,19 +238,7 @@ export default function PointHandler({
|
||||
const [vehicleData, setVehicleData] = useState<VehicleDetails[]>([]);
|
||||
const { paths, setPaths } = useCreatedPaths();
|
||||
const [managerData, setManagerData] = useState<Manager>();
|
||||
// useEffect(() => {
|
||||
// const findVehicle = assets
|
||||
// .filter((val) => val.eventData?.type === "Vehicle")
|
||||
// ?.map((val) => val.modelUuid);
|
||||
|
||||
//
|
||||
// setVehicleUuids(findVehicle);
|
||||
|
||||
// vehicleMovementState.current = {};
|
||||
// findVehicle.forEach((uuid) => {
|
||||
// vehicleMovementState.current[uuid] = { index: 0, progress: 0 };
|
||||
// });
|
||||
// }, [assets]);
|
||||
useEffect(() => {
|
||||
const findVehicle = assets
|
||||
.filter((val) => val.eventData?.type === "Vehicle")
|
||||
@@ -417,87 +413,174 @@ export default function PointHandler({
|
||||
removePathByPoint(point.pointId);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let selectedVehiclePaths: Array<
|
||||
Array<{
|
||||
vehicleId: string;
|
||||
pathId: string;
|
||||
isActive?: boolean;
|
||||
isCurved?: boolean;
|
||||
pathPoints: [PointData, PointData];
|
||||
}>
|
||||
> = [];
|
||||
|
||||
if (e.shiftKey) {
|
||||
const pointIndex = points.findIndex((p) => p.pointId === point.pointId);
|
||||
if (pointIndex === -1) {
|
||||
return;
|
||||
function assignPathToSelectedVehicle(
|
||||
selectedVehicleId: string,
|
||||
currentPath: PathData
|
||||
) {
|
||||
const vehiclePathSegments = currentPath.map((path) => ({
|
||||
vehicleId: selectedVehicleId,
|
||||
...path,
|
||||
}));
|
||||
|
||||
return selectedVehiclePaths.push(vehiclePathSegments);
|
||||
}
|
||||
|
||||
const handleContextMenu = (e: any, point: PointData) => {
|
||||
// if (e.shiftKey && e.button === 2) {
|
||||
const pointIndex = points.findIndex((p) => p.pointId === point.pointId);
|
||||
if (pointIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelected((prev) => {
|
||||
if (prev.length === 0) {
|
||||
return [pointIndex];
|
||||
}
|
||||
// if (prev.length === 1) {
|
||||
// setTimeout(() => {
|
||||
//
|
||||
// const prevPoint = points[prev[0]];
|
||||
//
|
||||
// const newPoint = points[pointIndex];
|
||||
//
|
||||
// const result = aStarShortestPath(
|
||||
// prevPoint.pointId,
|
||||
// newPoint.pointId,
|
||||
// points,
|
||||
// paths
|
||||
// );
|
||||
|
||||
setSelected((prev) => {
|
||||
if (prev.length === 0) {
|
||||
return [pointIndex];
|
||||
}
|
||||
// if (prev.length === 1) {
|
||||
// setTimeout(() => {
|
||||
//
|
||||
// const prevPoint = points[prev[0]];
|
||||
//
|
||||
// const newPoint = points[pointIndex];
|
||||
//
|
||||
// const result = aStarShortestPath(
|
||||
// prevPoint.pointId,
|
||||
// newPoint.pointId,
|
||||
// points,
|
||||
// paths
|
||||
// );
|
||||
// if (result) {
|
||||
// const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
//
|
||||
// setShortestPaths(edges);
|
||||
// setShortestEdges(edges);
|
||||
// } else {
|
||||
// setShortestPaths([]);
|
||||
// setShortestEdges([]);
|
||||
// }
|
||||
// if (prevPoint.pointId === newPoint.pointId) {
|
||||
// return prev;
|
||||
// }
|
||||
// }, 0);
|
||||
|
||||
// if (result) {
|
||||
// const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
//
|
||||
// setShortestPaths(edges);
|
||||
// setShortestEdges(edges);
|
||||
// } else {
|
||||
// setShortestPaths([]);
|
||||
// setShortestEdges([]);
|
||||
// }
|
||||
// if (prevPoint.pointId === newPoint.pointId) {
|
||||
// return prev;
|
||||
// }
|
||||
// }, 0);
|
||||
// return [prev[0], pointIndex];
|
||||
// }
|
||||
|
||||
// return [prev[0], pointIndex];
|
||||
// }
|
||||
|
||||
// More than two points — reset
|
||||
if (prev.length === 1) {
|
||||
setTimeout(() => {
|
||||
const prevPoint = points[prev[0]];
|
||||
const newPoint = points[pointIndex];
|
||||
|
||||
if (prevPoint.pointId === newPoint.pointId) return;
|
||||
|
||||
const result = aStarShortestPath(
|
||||
prevPoint.pointId,
|
||||
newPoint.pointId,
|
||||
points,
|
||||
paths
|
||||
// More than two points — reset
|
||||
if (prev.length === 1) {
|
||||
setTimeout(() => {
|
||||
const prevPoint = points[prev[0]];
|
||||
const newPoint = points[pointIndex];
|
||||
console.log(
|
||||
"selectedEventSphere?.userData.modelUuid: ",
|
||||
selectedEventSphere?.userData.modelUuid
|
||||
);
|
||||
if (selectedEventSphere?.userData.modelUuid) {
|
||||
const updatedVehicle = getVehicleById(
|
||||
selectedEventSphere.userData.modelUuid
|
||||
);
|
||||
|
||||
if (result) {
|
||||
const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
const startPoint = new Vector3(...prevPoint.position);
|
||||
const endPoint = new Vector3(...newPoint.position);
|
||||
if (updatedVehicle && startPoint && endPoint) {
|
||||
if (updatedVehicle.type === "vehicle") {
|
||||
const event = updateAction(
|
||||
selectedProduct.productUuid,
|
||||
updatedVehicle.point?.action.actionUuid,
|
||||
{
|
||||
pickUpPoint: {
|
||||
position: {
|
||||
x: startPoint.x,
|
||||
y: 0,
|
||||
z: startPoint.z,
|
||||
},
|
||||
rotation: {
|
||||
x:
|
||||
updatedVehicle.point.action.pickUpPoint?.rotation.x ??
|
||||
0,
|
||||
y:
|
||||
updatedVehicle.point.action.pickUpPoint?.rotation.y ??
|
||||
0,
|
||||
z:
|
||||
updatedVehicle.point.action.pickUpPoint?.rotation.z ??
|
||||
0,
|
||||
},
|
||||
},
|
||||
unLoadPoint: {
|
||||
position: {
|
||||
x: endPoint.x,
|
||||
y: endPoint.y,
|
||||
z: endPoint.z,
|
||||
},
|
||||
rotation: {
|
||||
x:
|
||||
updatedVehicle.point.action.unLoadPoint?.rotation.x ??
|
||||
0,
|
||||
y:
|
||||
updatedVehicle.point.action.unLoadPoint?.rotation.y ??
|
||||
0,
|
||||
z:
|
||||
updatedVehicle.point.action.unLoadPoint?.rotation.z ??
|
||||
0,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Create a new path object
|
||||
const newPathObj = {
|
||||
id: pathIdRef.current++,
|
||||
path: edges,
|
||||
};
|
||||
setShortestPaths(edges);
|
||||
setShortestEdges(edges);
|
||||
// Append it to the list of paths
|
||||
setMultiPaths((prevPaths) => [...prevPaths, newPathObj]);
|
||||
if (prevPoint.pointId === newPoint.pointId) return;
|
||||
|
||||
const result = aStarShortestPath(
|
||||
prevPoint.pointId,
|
||||
newPoint.pointId,
|
||||
points,
|
||||
paths
|
||||
);
|
||||
|
||||
if (result) {
|
||||
const edges = nodePathToEdges(result.pointIds, points, paths);
|
||||
|
||||
// Create a new path object/
|
||||
const newPathObj = {
|
||||
id: pathIdRef.current++,
|
||||
path: edges,
|
||||
};
|
||||
const shortPath = assignPathToSelectedVehicle(
|
||||
updatedVehicle?.modelUuid,
|
||||
edges
|
||||
);
|
||||
console.log("shortPath: ", shortPath);
|
||||
|
||||
setShortestPaths(edges);
|
||||
setShortestEdges(edges);
|
||||
// Append it to the list of paths
|
||||
setMultiPaths((prevPaths) => [...prevPaths, newPathObj]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset selection to allow new pair selection
|
||||
}, 0);
|
||||
// Reset selection to allow new pair selection
|
||||
}, 0);
|
||||
|
||||
return [prev[0], pointIndex];
|
||||
}
|
||||
return [prev[0], pointIndex];
|
||||
}
|
||||
|
||||
setShortestPaths([]);
|
||||
return [pointIndex];
|
||||
});
|
||||
}
|
||||
setShortestPaths([]);
|
||||
return [pointIndex];
|
||||
});
|
||||
// }
|
||||
};
|
||||
|
||||
const handleDragStart = (point: PointData) => {
|
||||
@@ -630,10 +713,8 @@ export default function PointHandler({
|
||||
}
|
||||
return null; // not found
|
||||
}
|
||||
|
||||
//
|
||||
//works
|
||||
useFrame((_, delta) => {
|
||||
//
|
||||
if (!isPlaying || pathSegments.length < 2 || vehicleData.length === 0)
|
||||
return;
|
||||
|
||||
@@ -651,7 +732,6 @@ export default function PointHandler({
|
||||
|
||||
const pathStart = pathSegments[0].position;
|
||||
|
||||
// Step 1: Move vehicle to the start of the path
|
||||
if (!state.hasStarted) {
|
||||
const distanceToStart = object.position.distanceTo(pathStart);
|
||||
const step = speed * delta;
|
||||
@@ -659,11 +739,10 @@ export default function PointHandler({
|
||||
if (distanceToStart <= step) {
|
||||
object.position.copy(pathStart);
|
||||
object.quaternion.identity();
|
||||
state.hasStarted = true; // start path animation next frame
|
||||
state.hasStarted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Move toward path start
|
||||
const direction = pathStart.clone().sub(object.position);
|
||||
const distance = direction.length();
|
||||
|
||||
@@ -671,21 +750,19 @@ export default function PointHandler({
|
||||
direction.normalize();
|
||||
object.position.add(direction.clone().multiplyScalar(step));
|
||||
|
||||
// ROTATE toward direction of movement
|
||||
const forward = new Vector3(0, 0, 1); // <-- adjust this based on your model
|
||||
const forward = new Vector3(0, 0, 1);
|
||||
const targetQuat = new Quaternion().setFromUnitVectors(
|
||||
forward,
|
||||
direction
|
||||
);
|
||||
object.quaternion.slerp(targetQuat, 0.1); // smooth rotation
|
||||
object.quaternion.slerp(targetQuat, 0.1);
|
||||
} else {
|
||||
// Snap to start segment when close enough
|
||||
object.position.copy(pathStart);
|
||||
object.quaternion.identity(); // reset orientation or leave as-is
|
||||
object.quaternion.identity();
|
||||
vehicleMovementState.current[uuid] = { index: 0, progress: 0 };
|
||||
}
|
||||
|
||||
return; // still approaching start
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2: Follow the path
|
||||
@@ -707,14 +784,12 @@ export default function PointHandler({
|
||||
state.index++;
|
||||
|
||||
if (state.index >= pathSegments.length - 1) {
|
||||
// Path complete
|
||||
state.index = 0;
|
||||
state.progress = 0;
|
||||
state.hasStarted = false; // reset for next loop
|
||||
object.position.copy(vehicleStartPos); // go back to start
|
||||
state.hasStarted = false;
|
||||
object.position.copy(vehicleStartPos);
|
||||
object.quaternion.identity();
|
||||
|
||||
// Move to next vehicle
|
||||
setActiveVehicleIndex((prevIndex) =>
|
||||
prevIndex + 1 >= vehicleData.length ? 0 : prevIndex + 1
|
||||
);
|
||||
@@ -737,6 +812,101 @@ export default function PointHandler({
|
||||
object.quaternion.slerp(targetQuat, 0.1);
|
||||
});
|
||||
|
||||
// useFrame((_, delta) => {
|
||||
// if (!isPlaying || pathSegments.length < 2 || vehicleData.length === 0)
|
||||
// return;
|
||||
|
||||
// const vehicleStartPos = new Vector3(...agvDetail.position);
|
||||
|
||||
// const object = scene.getObjectByProperty("uuid", agvDetail.modelUuid);
|
||||
// if (!object) return;
|
||||
|
||||
// const state = vehicleMovementState.current[agvDetail.modelUuid];
|
||||
// if (!state) return;
|
||||
|
||||
// const moveSpeed = speed * delta;
|
||||
|
||||
// const pathStart = pathSegments[0].position;
|
||||
|
||||
// // ➤ Step 1: Move the object to the starting point of the path
|
||||
// if (!state.hasStarted) {
|
||||
// const distanceToStart = object.position.distanceTo(pathStart);
|
||||
|
||||
// if (distanceToStart <= moveSpeed) {
|
||||
// object.position.copy(pathStart);
|
||||
// object.quaternion.identity();
|
||||
// state.hasStarted = true;
|
||||
// state.index = 0;
|
||||
// state.progress = 0;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const direction = pathStart.clone().sub(object.position).normalize();
|
||||
// object.position.add(direction.clone().multiplyScalar(moveSpeed));
|
||||
|
||||
// const forward = new Vector3(0, 0, 1);
|
||||
// const targetQuat = new Quaternion().setFromUnitVectors(
|
||||
// forward,
|
||||
// direction
|
||||
// );
|
||||
// object.quaternion.slerp(targetQuat, 0.1);
|
||||
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // ➤ Step 2: Traverse path segments
|
||||
// const startSeg = pathSegments[state.index];
|
||||
// const endSeg = pathSegments[state.index + 1];
|
||||
// if (!startSeg || !endSeg) return;
|
||||
|
||||
// const currentPathId = getPathIdByPoints(
|
||||
// startSeg.startId || startSeg.endId,
|
||||
// endSeg.endId || endSeg.startId,
|
||||
// shortestPaths
|
||||
// );
|
||||
|
||||
// setManagerData({ pathId: currentPathId, vehicleId: agvDetail.modelUuid });
|
||||
|
||||
// const segmentDistance = startSeg.position.distanceTo(endSeg.position);
|
||||
// state.progress += moveSpeed / segmentDistance;
|
||||
|
||||
// // ➤ Step 3: If we've finished this segment, move to the next
|
||||
// while (state.progress >= 1) {
|
||||
// state.progress -= 1;
|
||||
// state.index++;
|
||||
|
||||
// if (state.index >= pathSegments.length - 1) {
|
||||
// // Path completed
|
||||
// state.index = 0;
|
||||
// state.progress = 0;
|
||||
// state.hasStarted = false;
|
||||
|
||||
// object.position.copy(vehicleStartPos);
|
||||
// object.quaternion.identity();
|
||||
|
||||
// setActiveVehicleIndex((prevIndex) =>
|
||||
// prevIndex + 1 >= vehicleData.length ? 0 : prevIndex + 1
|
||||
// );
|
||||
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // ➤ Step 4: Interpolate position and rotate
|
||||
// const newPos = startSeg.position
|
||||
// .clone()
|
||||
// .lerp(endSeg.position, state.progress);
|
||||
// object.position.copy(newPos);
|
||||
|
||||
// const direction = endSeg.position
|
||||
// .clone()
|
||||
// .sub(startSeg.position)
|
||||
// .normalize();
|
||||
// const forward = new Vector3(0, 0, 1);
|
||||
// const targetQuat = new Quaternion().setFromUnitVectors(forward, direction);
|
||||
// object.quaternion.slerp(targetQuat, 0.1);
|
||||
// });
|
||||
|
||||
// useFrame((_, delta) => {
|
||||
// if (!isPlaying || pathSegments.length < 2 || vehicleData.length === 0)
|
||||
// return;
|
||||
@@ -792,12 +962,12 @@ export default function PointHandler({
|
||||
|
||||
// object.quaternion.slerp(targetQuat, 0.1);
|
||||
// });
|
||||
useEffect(() => {}, [multiPaths]);
|
||||
|
||||
const manager = usePathManager(managerData?.pathId, managerData?.vehicleId);
|
||||
|
||||
useEffect(() => {
|
||||
// if (managerData) {
|
||||
// console.log("manager: ", manager);
|
||||
//
|
||||
// }
|
||||
}, [managerData]);
|
||||
return (
|
||||
@@ -817,6 +987,7 @@ export default function PointHandler({
|
||||
onClick={(e) => {
|
||||
handlePointClick(e, point);
|
||||
}}
|
||||
// onContextMenu={(e) => handleContextMenu(e, point)}
|
||||
onPointerOver={(e) => {
|
||||
if (!hoveredPoint && e.buttons === 0 && !e.ctrlKey) {
|
||||
setHoveredPoint(point);
|
||||
|
||||
@@ -664,8 +664,71 @@ interface allPaths {
|
||||
|
||||
type PathData = PathDataInterface[];
|
||||
export const useCreatedPaths = create<any>((set: any) => ({
|
||||
paths: [],
|
||||
paths: [
|
||||
{
|
||||
pathId: "276724c5-05a3-4b5e-a127-a60b3533ccce",
|
||||
pathPoints: [
|
||||
{
|
||||
pointId: "19c3f429-f214-4f87-8906-7eaaedd925da",
|
||||
position: [2.33155763270131, 0, -20.538859668988927],
|
||||
},
|
||||
{
|
||||
pointId: "ea73c7c8-0e26-4aae-9ed8-2349ff2d6718",
|
||||
position: [17.13371069714903, 0, -22.156135485080462],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pathId: "2736b20b-a433-443c-a5c9-5ba4348ac682",
|
||||
pathPoints: [
|
||||
{
|
||||
pointId: "ea73c7c8-0e26-4aae-9ed8-2349ff2d6718",
|
||||
position: [17.13371069714903, 0, -22.156135485080462],
|
||||
},
|
||||
{
|
||||
pointId: "2212bb52-c63c-4289-8b50-5ffd229d13e5",
|
||||
position: [16.29236816120279, 0, -10.819973445497789],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pathId: "3144a2df-7aad-483d-bbc7-de7f7b5b3bfc",
|
||||
pathPoints: [
|
||||
{
|
||||
pointId: "2212bb52-c63c-4289-8b50-5ffd229d13e5",
|
||||
position: [16.29236816120279, 0, -10.819973445497789],
|
||||
},
|
||||
{
|
||||
pointId: "adfd05a7-4e16-403f-81d0-ce99f2e34f5f",
|
||||
position: [4.677047323894161, 0, -8.279486846767094],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
pathId: "e0a1b5da-27c2-44a0-81db-759b5a5eb416",
|
||||
pathPoints: [
|
||||
{
|
||||
pointId: "adfd05a7-4e16-403f-81d0-ce99f2e34f5f",
|
||||
position: [4.677047323894161, 0, -8.279486846767094],
|
||||
},
|
||||
{
|
||||
pointId: "19c3f429-f214-4f87-8906-7eaaedd925da",
|
||||
position: [2.33155763270131, 0, -20.538859668988927],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
setPaths: (x: PathData) => set({ paths: x }),
|
||||
allPaths: [],
|
||||
setAllPaths: (x: allPaths) => set({ allPaths: x }),
|
||||
}));
|
||||
|
||||
// route?: {
|
||||
// pathId: string;
|
||||
// pathPoints: {
|
||||
// pointId: string;
|
||||
// position: [number, number, number];
|
||||
// isCurved?: boolean;
|
||||
// handleA?: [number, number, number] | null;
|
||||
// handleB: [number, number, number] | null;
|
||||
// }[];
|
||||
Reference in New Issue
Block a user