feat: Refactor vehicle management and update storage unit load handling; remove unused vehicle component and enhance vehicle status types
This commit is contained in:
parent
83ee14e9c7
commit
d161b70537
|
@ -25,19 +25,8 @@ const avatarColors: string[] = [
|
||||||
export function getAvatarColor(index: number, name?: string): string {
|
export function getAvatarColor(index: number, name?: string): string {
|
||||||
// Check if the color is already stored in localStorage
|
// Check if the color is already stored in localStorage
|
||||||
const localStorageKey = "userAvatarColors";
|
const localStorageKey = "userAvatarColors";
|
||||||
// Helper function to check if local storage is available
|
|
||||||
function isLocalStorageAvailable(): boolean {
|
|
||||||
try {
|
|
||||||
const testKey = "__test__";
|
|
||||||
localStorage.setItem(testKey, "test");
|
|
||||||
localStorage.removeItem(testKey);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check if local storage is available
|
// Check if local storage is available
|
||||||
if (isLocalStorageAvailable() && name) {
|
if (name) {
|
||||||
let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
|
let userColors = JSON.parse(localStorage.getItem(localStorageKey) || "{}");
|
||||||
|
|
||||||
// Check if the user already has an assigned color
|
// Check if the user already has an assigned color
|
||||||
|
|
|
@ -5,240 +5,225 @@ import { useToolMode } from "../../../store/store";
|
||||||
import { Html } from "@react-three/drei";
|
import { Html } from "@react-three/drei";
|
||||||
|
|
||||||
const MeasurementTool = () => {
|
const MeasurementTool = () => {
|
||||||
const { gl, raycaster, pointer, camera, scene } = useThree();
|
const { gl, raycaster, pointer, camera, scene } = useThree();
|
||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
|
|
||||||
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
||||||
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const [startConePosition, setStartConePosition] =
|
const [startConePosition, setStartConePosition] =
|
||||||
useState<THREE.Vector3 | null>(null);
|
useState<THREE.Vector3 | null>(null);
|
||||||
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [startConeQuaternion, setStartConeQuaternion] = useState(
|
const [startConeQuaternion, setStartConeQuaternion] = useState(
|
||||||
new THREE.Quaternion()
|
new THREE.Quaternion()
|
||||||
);
|
);
|
||||||
const [endConeQuaternion, setEndConeQuaternion] = useState(
|
const [endConeQuaternion, setEndConeQuaternion] = useState(
|
||||||
new THREE.Quaternion()
|
new THREE.Quaternion()
|
||||||
);
|
);
|
||||||
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
||||||
|
|
||||||
const MIN_RADIUS = 0.001,
|
const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
|
||||||
MAX_RADIUS = 0.1;
|
const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
|
||||||
const MIN_CONE_RADIUS = 0.01,
|
const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
|
||||||
MAX_CONE_RADIUS = 0.4;
|
|
||||||
const MIN_CONE_HEIGHT = 0.035,
|
|
||||||
MAX_CONE_HEIGHT = 2.0;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
let drag = false;
|
let drag = false;
|
||||||
let isLeftMouseDown = false;
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
const onMouseDown = () => {
|
const onMouseDown = () => {
|
||||||
isLeftMouseDown = true;
|
isLeftMouseDown = true;
|
||||||
drag = false;
|
drag = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseUp = (evt: any) => {
|
const onMouseUp = (evt: any) => {
|
||||||
isLeftMouseDown = false;
|
isLeftMouseDown = false;
|
||||||
if (evt.button === 0 && !drag) {
|
if (evt.button === 0 && !drag) {
|
||||||
raycaster.setFromCamera(pointer, camera);
|
raycaster.setFromCamera(pointer, camera);
|
||||||
const intersects = raycaster
|
const intersects = raycaster
|
||||||
.intersectObjects(scene.children, true)
|
.intersectObjects(scene.children, true)
|
||||||
.filter(
|
.filter(
|
||||||
(intersect) =>
|
(intersect) =>
|
||||||
!intersect.object.name.includes("Roof") &&
|
!intersect.object.name.includes("Roof") &&
|
||||||
!intersect.object.name.includes("MeasurementReference") &&
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
!intersect.object.name.includes("agv-collider") &&
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
!(intersect.object.type === "GridHelper")
|
!(intersect.object.type === "GridHelper")
|
||||||
);
|
);
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const intersectionPoint = intersects[0].point.clone();
|
const intersectionPoint = intersects[0].point.clone();
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
setPoints([...points, intersectionPoint]);
|
setPoints([...points, intersectionPoint]);
|
||||||
} else {
|
} else {
|
||||||
setPoints([intersectionPoint]);
|
setPoints([intersectionPoint]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) drag = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (evt: any) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (!drag) {
|
||||||
|
evt.preventDefault();
|
||||||
|
setPoints([]);
|
||||||
|
setTubeGeometry(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (toolMode === "MeasurementScale") {
|
||||||
|
canvasElement.addEventListener("pointerdown", onMouseDown);
|
||||||
|
canvasElement.addEventListener("pointermove", onMouseMove);
|
||||||
|
canvasElement.addEventListener("pointerup", onMouseUp);
|
||||||
|
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||||
|
} else {
|
||||||
|
resetMeasurement();
|
||||||
|
setPoints([]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("pointermove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("pointerup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
|
};
|
||||||
|
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (points.length === 1) {
|
||||||
|
raycaster.setFromCamera(pointer, camera);
|
||||||
|
const intersects = raycaster
|
||||||
|
.intersectObjects(scene.children, true)
|
||||||
|
.filter(
|
||||||
|
(intersect) =>
|
||||||
|
!intersect.object.name.includes("Roof") &&
|
||||||
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
|
!(intersect.object.type === "GridHelper")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
updateMeasurement(points[0], intersects[0].point);
|
||||||
|
}
|
||||||
|
} else if (points.length === 2) {
|
||||||
|
updateMeasurement(points[0], points[1]);
|
||||||
|
} else {
|
||||||
|
resetMeasurement();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||||
|
const distance = start.distanceTo(end);
|
||||||
|
|
||||||
|
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
|
||||||
|
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
|
||||||
|
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
|
||||||
|
|
||||||
|
setConeSize({ radius: coneRadius, height: coneHeight });
|
||||||
|
|
||||||
|
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||||
|
|
||||||
|
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
||||||
|
|
||||||
|
let tubeStart = start.clone().add(offset);
|
||||||
|
let tubeEnd = end.clone().sub(offset);
|
||||||
|
|
||||||
|
tubeStart.y = Math.max(tubeStart.y, 0);
|
||||||
|
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
||||||
|
|
||||||
|
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
||||||
|
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
||||||
|
|
||||||
|
setStartConePosition(tubeStart);
|
||||||
|
setEndConePosition(tubeEnd);
|
||||||
|
setStartConeQuaternion(getArrowOrientation(start, end));
|
||||||
|
setEndConeQuaternion(getArrowOrientation(end, start));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseMove = () => {
|
const resetMeasurement = () => {
|
||||||
if (isLeftMouseDown) drag = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onContextMenu = (evt: any) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
if (!drag) {
|
|
||||||
evt.preventDefault();
|
|
||||||
setPoints([]);
|
|
||||||
setTubeGeometry(null);
|
setTubeGeometry(null);
|
||||||
}
|
setStartConePosition(null);
|
||||||
|
setEndConePosition(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (toolMode === "MeasurementScale") {
|
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||||
canvasElement.addEventListener("pointerdown", onMouseDown);
|
const direction = new THREE.Vector3()
|
||||||
canvasElement.addEventListener("pointermove", onMouseMove);
|
.subVectors(end, start)
|
||||||
canvasElement.addEventListener("pointerup", onMouseUp);
|
.normalize()
|
||||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
.negate();
|
||||||
} else {
|
const quaternion = new THREE.Quaternion();
|
||||||
resetMeasurement();
|
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||||
setPoints([]);
|
return quaternion;
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
|
||||||
canvasElement.removeEventListener("pointermove", onMouseMove);
|
|
||||||
canvasElement.removeEventListener("pointerup", onMouseUp);
|
|
||||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
|
||||||
};
|
};
|
||||||
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
useEffect(() => {
|
||||||
if (points.length === 1) {
|
if (points.length === 2) {
|
||||||
raycaster.setFromCamera(pointer, camera);
|
// console.log(points[0].distanceTo(points[1]));
|
||||||
const intersects = raycaster
|
}
|
||||||
.intersectObjects(scene.children, true)
|
}, [points]);
|
||||||
.filter(
|
|
||||||
(intersect) =>
|
|
||||||
!intersect.object.name.includes("Roof") &&
|
|
||||||
!intersect.object.name.includes("MeasurementReference") &&
|
|
||||||
!intersect.object.name.includes("agv-collider") &&
|
|
||||||
!(intersect.object.type === "GridHelper")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
return (
|
||||||
updateMeasurement(points[0], intersects[0].point);
|
<group ref={groupRef} name="MeasurementGroup">
|
||||||
}
|
{startConePosition && (
|
||||||
} else if (points.length === 2) {
|
<mesh
|
||||||
updateMeasurement(points[0], points[1]);
|
name="MeasurementReference"
|
||||||
} else {
|
position={startConePosition}
|
||||||
resetMeasurement();
|
quaternion={startConeQuaternion}
|
||||||
}
|
>
|
||||||
});
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
{endConePosition && (
|
||||||
|
<mesh
|
||||||
|
name="MeasurementReference"
|
||||||
|
position={endConePosition}
|
||||||
|
quaternion={endConeQuaternion}
|
||||||
|
>
|
||||||
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
{tubeGeometry && (
|
||||||
|
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
|
||||||
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
{startConePosition && endConePosition && (
|
||||||
const distance = start.distanceTo(end);
|
<Html
|
||||||
|
scale={THREE.MathUtils.clamp(
|
||||||
const radius = THREE.MathUtils.clamp(
|
startConePosition.distanceTo(endConePosition) * 0.25,
|
||||||
distance * 0.02,
|
0,
|
||||||
MIN_RADIUS,
|
10
|
||||||
MAX_RADIUS
|
)}
|
||||||
|
position={[
|
||||||
|
(startConePosition.x + endConePosition.x) / 2,
|
||||||
|
(startConePosition.y + endConePosition.y) / 2,
|
||||||
|
(startConePosition.z + endConePosition.z) / 2,
|
||||||
|
]}
|
||||||
|
// class
|
||||||
|
wrapperClass="distance-text-wrapper"
|
||||||
|
className="distance-text"
|
||||||
|
// other
|
||||||
|
zIndexRange={[1, 0]}
|
||||||
|
prepend
|
||||||
|
sprite
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
);
|
);
|
||||||
const coneRadius = THREE.MathUtils.clamp(
|
|
||||||
distance * 0.05,
|
|
||||||
MIN_CONE_RADIUS,
|
|
||||||
MAX_CONE_RADIUS
|
|
||||||
);
|
|
||||||
const coneHeight = THREE.MathUtils.clamp(
|
|
||||||
distance * 0.2,
|
|
||||||
MIN_CONE_HEIGHT,
|
|
||||||
MAX_CONE_HEIGHT
|
|
||||||
);
|
|
||||||
|
|
||||||
setConeSize({ radius: coneRadius, height: coneHeight });
|
|
||||||
|
|
||||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
|
||||||
|
|
||||||
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
|
||||||
|
|
||||||
let tubeStart = start.clone().add(offset);
|
|
||||||
let tubeEnd = end.clone().sub(offset);
|
|
||||||
|
|
||||||
tubeStart.y = Math.max(tubeStart.y, 0);
|
|
||||||
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
|
||||||
|
|
||||||
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
|
||||||
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
|
||||||
|
|
||||||
setStartConePosition(tubeStart);
|
|
||||||
setEndConePosition(tubeEnd);
|
|
||||||
setStartConeQuaternion(getArrowOrientation(start, end));
|
|
||||||
setEndConeQuaternion(getArrowOrientation(end, start));
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetMeasurement = () => {
|
|
||||||
setTubeGeometry(null);
|
|
||||||
setStartConePosition(null);
|
|
||||||
setEndConePosition(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
|
||||||
const direction = new THREE.Vector3()
|
|
||||||
.subVectors(end, start)
|
|
||||||
.normalize()
|
|
||||||
.negate();
|
|
||||||
const quaternion = new THREE.Quaternion();
|
|
||||||
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
|
||||||
return quaternion;
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (points.length === 2) {
|
|
||||||
console.log(points[0].distanceTo(points[1]));
|
|
||||||
}
|
|
||||||
}, [points]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<group ref={groupRef} name="MeasurementGroup">
|
|
||||||
{startConePosition && (
|
|
||||||
<mesh
|
|
||||||
name="MeasurementReference"
|
|
||||||
position={startConePosition}
|
|
||||||
quaternion={startConeQuaternion}
|
|
||||||
>
|
|
||||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
|
||||||
<meshBasicMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
{endConePosition && (
|
|
||||||
<mesh
|
|
||||||
name="MeasurementReference"
|
|
||||||
position={endConePosition}
|
|
||||||
quaternion={endConeQuaternion}
|
|
||||||
>
|
|
||||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
|
||||||
<meshBasicMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
{tubeGeometry && (
|
|
||||||
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
|
||||||
<meshBasicMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{startConePosition && endConePosition && (
|
|
||||||
<Html
|
|
||||||
scale={THREE.MathUtils.clamp(
|
|
||||||
startConePosition.distanceTo(endConePosition) * 0.25,
|
|
||||||
0,
|
|
||||||
10
|
|
||||||
)}
|
|
||||||
position={[
|
|
||||||
(startConePosition.x + endConePosition.x) / 2,
|
|
||||||
(startConePosition.y + endConePosition.y) / 2,
|
|
||||||
(startConePosition.z + endConePosition.z) / 2,
|
|
||||||
]}
|
|
||||||
// class
|
|
||||||
wrapperClass="distance-text-wrapper"
|
|
||||||
className="distance-text"
|
|
||||||
// other
|
|
||||||
zIndexRange={[1, 0]}
|
|
||||||
prepend
|
|
||||||
sprite
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
|
|
||||||
</div>
|
|
||||||
</Html>
|
|
||||||
)}
|
|
||||||
</group>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MeasurementTool;
|
export default MeasurementTool;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useEventsStore } from '../../store/simulation/useEventsStore';
|
import { useEventsStore } from '../../store/simulation/useEventsStore';
|
||||||
import { useProductStore } from '../../store/simulation/useProductStore';
|
import { useProductStore } from '../../store/simulation/useProductStore';
|
||||||
|
import Vehicles from './vehicle/vehicles';
|
||||||
|
|
||||||
function Simulation() {
|
function Simulation() {
|
||||||
const { events } = useEventsStore();
|
const { events } = useEventsStore();
|
||||||
|
@ -16,6 +17,7 @@ function Simulation() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Vehicles />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
function VehicleAnimator() {
|
function VehicleAnimator() {
|
||||||
return (
|
return (
|
||||||
<div>VehicleAnimator</div>
|
<>
|
||||||
)
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VehicleAnimator
|
export default VehicleAnimator
|
|
@ -1,14 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import VehicleInstances from './instances/vehicleInstances';
|
|
||||||
|
|
||||||
function Vehicle() {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
|
|
||||||
<VehicleInstances />
|
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Vehicle
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import VehicleInstances from './instances/vehicleInstances';
|
||||||
|
|
||||||
|
import { useVehicleStore } from '../../../store/simulation/useVehicleStore';
|
||||||
|
|
||||||
|
function Vehicles() {
|
||||||
|
|
||||||
|
const { vehicles, addVehicle } = useVehicleStore();
|
||||||
|
|
||||||
|
const vehicleStatusSample: VehicleStatus[] = [
|
||||||
|
{
|
||||||
|
modelUuid: "veh-123",
|
||||||
|
modelName: "Autonomous Truck A1",
|
||||||
|
position: [10, 0, 5],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
state: "idle",
|
||||||
|
type: "vehicle",
|
||||||
|
speed: 2.5,
|
||||||
|
point: {
|
||||||
|
uuid: "point-789",
|
||||||
|
position: [0, 1, 0],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
action: {
|
||||||
|
actionUuid: "action-456",
|
||||||
|
actionName: "Deliver to Zone A",
|
||||||
|
actionType: "travel",
|
||||||
|
material: "crate",
|
||||||
|
unLoadDuration: 15,
|
||||||
|
loadCapacity: 5,
|
||||||
|
pickUpPoint: { x: 5, y: 0, z: 3 },
|
||||||
|
unLoadPoint: { x: 20, y: 0, z: 10 },
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
triggerUuid: "trig-001",
|
||||||
|
triggerName: "Start Travel",
|
||||||
|
triggerType: "onComplete",
|
||||||
|
delay: 0,
|
||||||
|
triggeredAsset: {
|
||||||
|
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
||||||
|
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
||||||
|
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
triggerUuid: "trig-002",
|
||||||
|
triggerName: "Complete Travel",
|
||||||
|
triggerType: "onComplete",
|
||||||
|
delay: 2,
|
||||||
|
triggeredAsset: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
productId: "prod-890",
|
||||||
|
isActive: false,
|
||||||
|
idleTime: 0,
|
||||||
|
activeTime: 0,
|
||||||
|
currentLoad: 0,
|
||||||
|
distanceTraveled: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
modelUuid: "veh-123",
|
||||||
|
modelName: "Autonomous Truck A1",
|
||||||
|
position: [10, 0, 5],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
state: "idle",
|
||||||
|
type: "vehicle",
|
||||||
|
speed: 2.5,
|
||||||
|
point: {
|
||||||
|
uuid: "point-789",
|
||||||
|
position: [0, 1, 0],
|
||||||
|
rotation: [0, 0, 0],
|
||||||
|
action: {
|
||||||
|
actionUuid: "action-456",
|
||||||
|
actionName: "Deliver to Zone A",
|
||||||
|
actionType: "travel",
|
||||||
|
material: "crate",
|
||||||
|
unLoadDuration: 15,
|
||||||
|
loadCapacity: 5,
|
||||||
|
pickUpPoint: { x: 5, y: 0, z: 3 },
|
||||||
|
unLoadPoint: { x: 20, y: 0, z: 10 },
|
||||||
|
triggers: [
|
||||||
|
{
|
||||||
|
triggerUuid: "trig-001",
|
||||||
|
triggerName: "Start Travel",
|
||||||
|
triggerType: "onStart",
|
||||||
|
delay: 0,
|
||||||
|
triggeredAsset: {
|
||||||
|
triggeredModel: { modelName: "ArmBot-X", modelUuid: "arm-001" },
|
||||||
|
triggeredPoint: { pointName: "Pickup Arm Point", pointUuid: "arm-point-01" },
|
||||||
|
triggeredAction: { actionName: "Grab Widget", actionUuid: "grab-001" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
triggerUuid: "trig-002",
|
||||||
|
triggerName: "Complete Travel",
|
||||||
|
triggerType: "onComplete",
|
||||||
|
delay: 2,
|
||||||
|
triggeredAsset: null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
productId: "prod-890",
|
||||||
|
isActive: false,
|
||||||
|
idleTime: 0,
|
||||||
|
activeTime: 0,
|
||||||
|
currentLoad: 0,
|
||||||
|
distanceTraveled: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addVehicle('123', vehicleStatusSample[0]);
|
||||||
|
addVehicle('123', vehicleStatusSample[1]);
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('vehicles: ', vehicles);
|
||||||
|
}, [vehicles])
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
|
||||||
|
<VehicleInstances />
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Vehicles;
|
|
@ -44,7 +44,7 @@ export default function Dropped3dWidgets() {
|
||||||
const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]);
|
const rotationStartRef = useRef<[number, number, number]>([0, 0, 0]);
|
||||||
const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
const mouseStartRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
const { setSelectedChartId } = useWidgetStore();
|
const { setSelectedChartId } = useWidgetStore();
|
||||||
const { measurements, duration} = useChartStore();
|
const { measurements, duration } = useChartStore();
|
||||||
let [floorPlanesVertical, setFloorPlanesVertical] = useState(
|
let [floorPlanesVertical, setFloorPlanesVertical] = useState(
|
||||||
new THREE.Plane(new THREE.Vector3(0, 1, 0))
|
new THREE.Plane(new THREE.Vector3(0, 1, 0))
|
||||||
);
|
);
|
||||||
|
@ -117,7 +117,6 @@ export default function Dropped3dWidgets() {
|
||||||
!intersect.object.name.includes("Roof") &&
|
!intersect.object.name.includes("Roof") &&
|
||||||
!intersect.object.name.includes("agv-collider") &&
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
!intersect.object.name.includes("MeasurementReference") &&
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
!intersect.object.userData.isPathObject &&
|
|
||||||
!(intersect.object.type === "GridHelper")
|
!(intersect.object.type === "GridHelper")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -154,7 +153,6 @@ export default function Dropped3dWidgets() {
|
||||||
!intersect.object.name.includes("Roof") &&
|
!intersect.object.name.includes("Roof") &&
|
||||||
!intersect.object.name.includes("agv-collider") &&
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
!intersect.object.name.includes("MeasurementReference") &&
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
!intersect.object.userData.isPathObject &&
|
|
||||||
!(intersect.object.type === "GridHelper")
|
!(intersect.object.type === "GridHelper")
|
||||||
);
|
);
|
||||||
// Update widget's position in memory
|
// Update widget's position in memory
|
||||||
|
@ -169,28 +167,28 @@ export default function Dropped3dWidgets() {
|
||||||
const onDrop = (event: any) => {
|
const onDrop = (event: any) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
hasEntered.current = false;
|
hasEntered.current = false;
|
||||||
|
|
||||||
const email = localStorage.getItem("email") || "";
|
const email = localStorage.getItem("email") || "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
|
|
||||||
const newWidget = createdWidgetRef.current;
|
const newWidget = createdWidgetRef.current;
|
||||||
if (!newWidget || !widgetSelect.startsWith("ui")) return;
|
if (!newWidget || !widgetSelect.startsWith("ui")) return;
|
||||||
|
|
||||||
// ✅ Extract 2D drop position
|
// ✅ Extract 2D drop position
|
||||||
let [x, y, z] = newWidget.position;
|
let [x, y, z] = newWidget.position;
|
||||||
|
|
||||||
// ✅ Clamp Y to at least 0
|
// ✅ Clamp Y to at least 0
|
||||||
y = Math.max(y, 0);
|
y = Math.max(y, 0);
|
||||||
newWidget.position = [x, y, z];
|
newWidget.position = [x, y, z];
|
||||||
|
|
||||||
// ✅ Prepare polygon from selectedZone.points
|
// ✅ Prepare polygon from selectedZone.points
|
||||||
const points3D = selectedZone.points as Array<[number, number, number]>;
|
const points3D = selectedZone.points as Array<[number, number, number]>;
|
||||||
const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]);
|
const zonePolygonXZ = points3D.map(([x, , z]) => [x, z] as [number, number]);
|
||||||
|
|
||||||
const isInside = isPointInPolygon([x, z], zonePolygonXZ);
|
const isInside = isPointInPolygon([x, z], zonePolygonXZ);
|
||||||
|
|
||||||
// ✅ Remove temp widget
|
// ✅ Remove temp widget
|
||||||
const prevWidgets = useZoneWidgetStore.getState().zoneWidgetData[selectedZone.zoneId] || [];
|
const prevWidgets = useZoneWidgetStore.getState().zoneWidgetData[selectedZone.zoneId] || [];
|
||||||
const cleanedWidgets = prevWidgets.filter(w => w.id !== newWidget.id);
|
const cleanedWidgets = prevWidgets.filter(w => w.id !== newWidget.id);
|
||||||
|
@ -200,29 +198,29 @@ export default function Dropped3dWidgets() {
|
||||||
[selectedZone.zoneId]: cleanedWidgets,
|
[selectedZone.zoneId]: cleanedWidgets,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// (Optional) Prevent adding if dropped outside zone
|
// (Optional) Prevent adding if dropped outside zone
|
||||||
// if (!isInside) {
|
// if (!isInside) {
|
||||||
// createdWidgetRef.current = null;
|
// createdWidgetRef.current = null;
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ✅ Add widget
|
// ✅ Add widget
|
||||||
addWidget(selectedZone.zoneId, newWidget);
|
addWidget(selectedZone.zoneId, newWidget);
|
||||||
|
|
||||||
const add3dWidget = {
|
const add3dWidget = {
|
||||||
organization,
|
organization,
|
||||||
widget: newWidget,
|
widget: newWidget,
|
||||||
zoneId: selectedZone.zoneId,
|
zoneId: selectedZone.zoneId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (visualizationSocket) {
|
if (visualizationSocket) {
|
||||||
visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget);
|
visualizationSocket.emit("v2:viz-3D-widget:add", add3dWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
createdWidgetRef.current = null;
|
createdWidgetRef.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
canvasElement.addEventListener("dragenter", handleDragEnter);
|
canvasElement.addEventListener("dragenter", handleDragEnter);
|
||||||
|
@ -258,7 +256,7 @@ export default function Dropped3dWidgets() {
|
||||||
widgetToDuplicate.position[2] + 0.5,
|
widgetToDuplicate.position[2] + 0.5,
|
||||||
],
|
],
|
||||||
rotation: widgetToDuplicate.rotation || [0, 0, 0],
|
rotation: widgetToDuplicate.rotation || [0, 0, 0],
|
||||||
Data:{
|
Data: {
|
||||||
measurements: measurements,
|
measurements: measurements,
|
||||||
duration: duration
|
duration: duration
|
||||||
},
|
},
|
||||||
|
@ -365,7 +363,7 @@ export default function Dropped3dWidgets() {
|
||||||
// floorPlanesVertical,
|
// floorPlanesVertical,
|
||||||
// planeIntersect.current
|
// planeIntersect.current
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// setintersectcontextmenu(intersect1.y);
|
// setintersectcontextmenu(intersect1.y);
|
||||||
|
|
||||||
if (rightSelect === "RotateX" || rightSelect === "RotateY") {
|
if (rightSelect === "RotateX" || rightSelect === "RotateY") {
|
||||||
|
@ -385,7 +383,7 @@ export default function Dropped3dWidgets() {
|
||||||
rotationStartRef.current = selectedWidget.rotation || [0, 0, 0];
|
rotationStartRef.current = selectedWidget.rotation || [0, 0, 0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = (event: MouseEvent) => {
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
@ -429,7 +427,7 @@ export default function Dropped3dWidgets() {
|
||||||
intersect.z + horizontalZ,
|
intersect.z + horizontalZ,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
|
updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,24 +435,24 @@ export default function Dropped3dWidgets() {
|
||||||
|
|
||||||
if (rightSelect === "Vertical Move") {
|
if (rightSelect === "Vertical Move") {
|
||||||
const intersect = raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current);
|
const intersect = raycaster.ray.intersectPlane(floorPlanesVertical, planeIntersect.current);
|
||||||
|
|
||||||
if (intersect && typeof intersectcontextmenu === "number") {
|
if (intersect && typeof intersectcontextmenu === "number") {
|
||||||
const diff = intersect.y - intersectcontextmenu;
|
const diff = intersect.y - intersectcontextmenu;
|
||||||
const unclampedY = selectedWidget.position[1] + diff;
|
const unclampedY = selectedWidget.position[1] + diff;
|
||||||
const newY = Math.max(0, unclampedY); // Prevent going below floor (y=0)
|
const newY = Math.max(0, unclampedY); // Prevent going below floor (y=0)
|
||||||
|
|
||||||
setintersectcontextmenu(intersect.y);
|
setintersectcontextmenu(intersect.y);
|
||||||
|
|
||||||
const newPosition: [number, number, number] = [
|
const newPosition: [number, number, number] = [
|
||||||
selectedWidget.position[0],
|
selectedWidget.position[0],
|
||||||
newY,
|
newY,
|
||||||
selectedWidget.position[2],
|
selectedWidget.position[2],
|
||||||
];
|
];
|
||||||
|
|
||||||
updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
|
updateWidgetPosition(selectedZoneId, rightClickSelected, newPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightSelect?.startsWith("Rotate")) {
|
if (rightSelect?.startsWith("Rotate")) {
|
||||||
const axis = rightSelect.slice(-1).toLowerCase(); // "x", "y", or "z"
|
const axis = rightSelect.slice(-1).toLowerCase(); // "x", "y", or "z"
|
||||||
const currentX = event.pageX;
|
const currentX = event.pageX;
|
||||||
|
|
|
@ -25,7 +25,7 @@ interface StorageUnitStore {
|
||||||
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
|
setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void;
|
||||||
|
|
||||||
// Load updates
|
// Load updates
|
||||||
updateStorageUnitLoad: (modelUuid: string, load: number) => void;
|
updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
|
||||||
// Time tracking
|
// Time tracking
|
||||||
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
incrementActiveTime: (modelUuid: string, incrementBy: number) => void;
|
||||||
|
@ -95,11 +95,11 @@ export const useStorageUnitStore = create<StorageUnitStore>()(
|
||||||
},
|
},
|
||||||
|
|
||||||
// Load updates
|
// Load updates
|
||||||
updateStorageUnitLoad: (modelUuid, load) => {
|
updateStorageUnitLoad: (modelUuid, incrementBy) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
const unit = state.storageUnits.find(s => s.modelUuid === modelUuid);
|
||||||
if (unit) {
|
if (unit) {
|
||||||
unit.currentLoad = load;
|
unit.currentLoad += incrementBy;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,7 +30,6 @@ interface VehiclesStore {
|
||||||
getVehiclesByProduct: (productId: string) => VehicleStatus[];
|
getVehiclesByProduct: (productId: string) => VehicleStatus[];
|
||||||
getVehiclesByState: (state: string) => VehicleStatus[];
|
getVehiclesByState: (state: string) => VehicleStatus[];
|
||||||
getActiveVehicles: () => VehicleStatus[];
|
getActiveVehicles: () => VehicleStatus[];
|
||||||
getIdleVehicles: () => VehicleStatus[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useVehicleStore = create<VehiclesStore>()(
|
export const useVehicleStore = create<VehiclesStore>()(
|
||||||
|
@ -125,10 +124,6 @@ export const useVehicleStore = create<VehiclesStore>()(
|
||||||
|
|
||||||
getActiveVehicles: () => {
|
getActiveVehicles: () => {
|
||||||
return get().vehicles.filter(v => v.isActive);
|
return get().vehicles.filter(v => v.isActive);
|
||||||
},
|
|
||||||
|
|
||||||
getIdleVehicles: () => {
|
|
||||||
return get().vehicles.filter(v => !v.isActive && v.currentLoad > 0);
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
|
@ -42,7 +42,7 @@ interface VehiclePointSchema {
|
||||||
actionUuid: string;
|
actionUuid: string;
|
||||||
actionName: string;
|
actionName: string;
|
||||||
actionType: "travel";
|
actionType: "travel";
|
||||||
material: string;
|
material: string | null;
|
||||||
unLoadDuration: number;
|
unLoadDuration: number;
|
||||||
loadCapacity: number;
|
loadCapacity: number;
|
||||||
pickUpPoint: { x: number; y: number, z: number } | null;
|
pickUpPoint: { x: number; y: number, z: number } | null;
|
||||||
|
@ -125,4 +125,13 @@ type productsSchema = {
|
||||||
productName: string;
|
productName: string;
|
||||||
productId: string;
|
productId: string;
|
||||||
eventsData: EventsSchema[];
|
eventsData: EventsSchema[];
|
||||||
}[]
|
}[]
|
||||||
|
|
||||||
|
interface VehicleStatus extends VehicleEventSchema {
|
||||||
|
productId: string;
|
||||||
|
isActive: boolean;
|
||||||
|
idleTime: number;
|
||||||
|
activeTime: number;
|
||||||
|
currentLoad: number;
|
||||||
|
distanceTraveled: number;
|
||||||
|
}
|
Loading…
Reference in New Issue