Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
|
60c0bef5f9 | |
|
73d2cc8c73 |
|
@ -0,0 +1,146 @@
|
|||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import useModuleStore from '../../store/useModuleStore';
|
||||
import { MaterialModel } from '../simulation/materials/instances/material/materialModel';
|
||||
|
||||
type AgvSimulationProps = {
|
||||
data: any;
|
||||
assetName: string;
|
||||
assetUUID: string;
|
||||
};
|
||||
|
||||
function AgvSimulation({ data, assetName, assetUUID }: AgvSimulationProps) {
|
||||
const { scene } = useThree();
|
||||
const { activeModule } = useModuleStore();
|
||||
const materialRef = useRef<any>(null);
|
||||
const startPosRef = useRef<THREE.Vector3 | null>(null);
|
||||
const endPosRef = useRef<THREE.Vector3 | null>(null);
|
||||
const quaternionRef = useRef<THREE.Quaternion | null>(null);
|
||||
const offsetRef = useRef<THREE.Vector3 | null>(null); // store material offset
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const agvRef = useRef<THREE.Object3D | null>(null);
|
||||
|
||||
const duration = 5000; // 5 seconds
|
||||
|
||||
const lastPercentageRef = useRef<number>(0);
|
||||
const targetPercentageRef = useRef<number>(0);
|
||||
const interpolationStartTimeRef = useRef<number>(performance.now());
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== 'visualization') return;
|
||||
const AgvObject = scene.getObjectByProperty('uuid', assetUUID);
|
||||
if (!AgvObject) return;
|
||||
agvRef.current = AgvObject;
|
||||
|
||||
AgvObject.updateWorldMatrix(true, false);
|
||||
|
||||
const worldPosition = new THREE.Vector3();
|
||||
AgvObject.getWorldPosition(worldPosition);
|
||||
|
||||
const worldQuaternion = new THREE.Quaternion();
|
||||
AgvObject.getWorldQuaternion(worldQuaternion);
|
||||
|
||||
const offsetStart = new THREE.Vector3(0, 0.9, 0).applyQuaternion(worldQuaternion); // offset above AGV
|
||||
offsetRef.current = offsetStart;
|
||||
|
||||
const materialPosition = worldPosition.clone().add(offsetStart);
|
||||
|
||||
// Set start and end positions
|
||||
const startPosition = new THREE.Vector3(5.24076846034801, 0, -46.23525167395107);
|
||||
const endPosition = new THREE.Vector3(10.24076846034801, 0, -50.23525167395107);
|
||||
startPosRef.current = startPosition;
|
||||
endPosRef.current = endPosition;
|
||||
quaternionRef.current = worldQuaternion;
|
||||
|
||||
startTimeRef.current = performance.now();
|
||||
|
||||
// Place material initially
|
||||
if (materialRef.current) {
|
||||
materialRef.current.position.copy(materialPosition);
|
||||
materialRef.current.quaternion.copy(worldQuaternion);
|
||||
}
|
||||
}, [activeModule, assetUUID, scene]);
|
||||
|
||||
useFrame(() => {
|
||||
if (data.assetName !== assetName || data.event !== 'move') return;
|
||||
if (!agvRef.current || !startPosRef.current || !endPosRef.current || !startTimeRef.current || !isRunning)
|
||||
return;
|
||||
const now = performance.now();
|
||||
const elapsed = now - interpolationStartTimeRef.current;
|
||||
const duration = 2000;
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
const interpolatedPercentage =
|
||||
lastPercentageRef.current +
|
||||
(targetPercentageRef.current - lastPercentageRef.current) * t;
|
||||
const progress = Math.min(interpolatedPercentage / 100, 1);
|
||||
const currentPos = new THREE.Vector3().lerpVectors(startPosRef.current, endPosRef.current, progress);
|
||||
// if (elapsed > duration) {
|
||||
// agvRef.current.position.copy(endPosRef.current);
|
||||
// if (materialRef.current && offsetRef.current) {
|
||||
// materialRef.current.position.copy(endPosRef.current.clone().add(offsetRef.current));
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// const currentPos = new THREE.Vector3().lerpVectors(startPosRef.current, endPosRef.current, t);
|
||||
agvRef.current.position.copy(currentPos);
|
||||
if (materialRef.current && offsetRef.current) {
|
||||
materialRef.current.position.copy(currentPos.clone().add(offsetRef.current));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
if (data.state === 'running' && data.percentage !== undefined) {
|
||||
|
||||
// Reset to start if restarting
|
||||
if (data.percentage === 0) {
|
||||
lastPercentageRef.current = 0;
|
||||
targetPercentageRef.current = 0;
|
||||
|
||||
if (startPosRef.current && materialRef.current && quaternionRef.current) {
|
||||
materialRef.current.position.copy(startPosRef.current);
|
||||
materialRef.current.quaternion.copy(quaternionRef.current);
|
||||
}
|
||||
} else {
|
||||
lastPercentageRef.current = targetPercentageRef.current;
|
||||
}
|
||||
targetPercentageRef.current = data.percentage;
|
||||
interpolationStartTimeRef.current = performance.now();
|
||||
setIsRunning(true);
|
||||
} else {
|
||||
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
if (data.state === 'running' && typeof data.percentage === 'number') {
|
||||
const shouldBeVisible = data.percentage <= 100;
|
||||
// Only update visibility if it changes
|
||||
setIsVisible(prev => {
|
||||
if (prev !== shouldBeVisible) {
|
||||
return shouldBeVisible;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
return (
|
||||
<MaterialModel
|
||||
materialId={`${assetName}-mat`}
|
||||
matRef={materialRef}
|
||||
materialType={'Default material'}
|
||||
visible={isVisible}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgvSimulation;
|
|
@ -0,0 +1,281 @@
|
|||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import * as THREE from "three";
|
||||
import { useAnimationPlaySpeed } from '../../store/usePlayButtonStore';
|
||||
|
||||
type PointWithDegree = {
|
||||
position: [number, number, number];
|
||||
degree: number;
|
||||
};
|
||||
|
||||
function ArmAnimator({ armBot, ikSolver, setIkSolver, targetBone, restPosition, path, assetName, data }: any) {
|
||||
const { scene } = useThree();
|
||||
const progressRef = useRef(0);
|
||||
const curveRef = useRef<THREE.Vector3[] | null>(null);
|
||||
const totalDistanceRef = useRef(0);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
const segmentDistancesRef = useRef<number[]>([]);
|
||||
const [currentPath, setCurrentPath] = useState<[number, number, number][]>([]);
|
||||
const [circlePoints, setCirclePoints] = useState<[number, number, number][]>([]);
|
||||
const [circlePointsWithDegrees, setCirclePointsWithDegrees] = useState<PointWithDegree[]>([]);
|
||||
const [customCurvePoints, setCustomCurvePoints] = useState<THREE.Vector3[] | null>(null);
|
||||
let curveHeight = 1.75
|
||||
const CIRCLE_RADIUS = 1.6
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
let duration = 5000
|
||||
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const lastPercentageRef = useRef<number>(0);
|
||||
const targetPercentageRef = useRef<number>(0);
|
||||
const interpolationStartTimeRef = useRef<number>(performance.now());
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
setCurrentPath(path);
|
||||
}, [path]);
|
||||
|
||||
// Handle circle points based on armBot position
|
||||
useEffect(() => {
|
||||
const points = generateRingPoints(CIRCLE_RADIUS, 64)
|
||||
setCirclePoints(points);
|
||||
}, [armBot.position]);
|
||||
|
||||
//Generate Circle Points
|
||||
function generateRingPoints(radius: any, segments: any) {
|
||||
const points: [number, number, number][] = [];
|
||||
for (let i = 0; i < segments; i++) {
|
||||
// Calculate angle for current segment
|
||||
const angle = (i / segments) * Math.PI * 2;
|
||||
// Calculate x and z coordinates (y remains the same for a flat ring)
|
||||
const x = Math.cos(angle) * radius;
|
||||
const z = Math.sin(angle) * radius;
|
||||
points.push([x, 1.5, z]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
//Generate CirclePoints with Angle
|
||||
function generateRingPointsWithDegrees(radius: number, segments: number, initialRotation: [number, number, number]) {
|
||||
const points: { position: [number, number, number]; degree: number }[] = [];
|
||||
for (let i = 0; i < segments; i++) {
|
||||
const angleRadians = (i / segments) * Math.PI * 2;
|
||||
const x = Math.cos(angleRadians) * radius;
|
||||
const z = Math.sin(angleRadians) * radius;
|
||||
const degree = (angleRadians * 180) / Math.PI; // Convert radians to degrees
|
||||
points.push({
|
||||
position: [x, 1.5, z],
|
||||
degree,
|
||||
});
|
||||
}
|
||||
return points;
|
||||
}
|
||||
// Handle circle points based on armBot position
|
||||
useEffect(() => {
|
||||
const points = generateRingPointsWithDegrees(CIRCLE_RADIUS, 64, armBot.rotation);
|
||||
setCirclePointsWithDegrees(points)
|
||||
}, [armBot.rotation]);
|
||||
|
||||
// Function for find nearest Circlepoints Index
|
||||
const findNearestIndex = (nearestPoint: [number, number, number], points: [number, number, number][], epsilon = 1e-6) => {
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const [x, y, z] = points[i];
|
||||
if (
|
||||
Math.abs(x - nearestPoint[0]) < epsilon &&
|
||||
Math.abs(y - nearestPoint[1]) < epsilon &&
|
||||
Math.abs(z - nearestPoint[2]) < epsilon
|
||||
) {
|
||||
return i; // Found the matching index
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
};
|
||||
|
||||
//function to find nearest Circlepoints
|
||||
const findNearest = (target: [number, number, number]) => {
|
||||
return circlePoints.reduce((nearest, point) => {
|
||||
const distance = Math.hypot(target[0] - point[0], target[1] - point[1], target[2] - point[2]);
|
||||
const nearestDistance = Math.hypot(target[0] - nearest[0], target[1] - nearest[1], target[2] - nearest[2]);
|
||||
return distance < nearestDistance ? point : nearest;
|
||||
}, circlePoints[0]);
|
||||
};
|
||||
|
||||
// Helper function to collect points and check forbidden degrees
|
||||
const collectArcPoints = (startIdx: number, endIdx: number, clockwise: boolean) => {
|
||||
const totalSegments = 64;
|
||||
const arcPoints: [number, number, number][] = [];
|
||||
let i = startIdx;
|
||||
|
||||
while (i !== (endIdx + (clockwise ? 1 : -1) + totalSegments) % totalSegments) {
|
||||
const { degree, position } = circlePointsWithDegrees[i];
|
||||
// Skip over
|
||||
arcPoints.push(position);
|
||||
i = (i + (clockwise ? 1 : -1) + totalSegments) % totalSegments;
|
||||
}
|
||||
return arcPoints;
|
||||
};
|
||||
|
||||
//Range to restrict angle
|
||||
const hasForbiddenDegrees = (arc: [number, number, number][]) => {
|
||||
return arc.some(p => {
|
||||
const idx = findNearestIndex(p, circlePoints);
|
||||
const degree = circlePointsWithDegrees[idx]?.degree || 0;
|
||||
return degree >= 271 && degree <= 300; // Forbidden range: 271° to 300°
|
||||
});
|
||||
};
|
||||
|
||||
// Handle nearest points and final path (including arc points)
|
||||
useEffect(() => {
|
||||
if (circlePoints.length > 0 && currentPath.length > 0) {
|
||||
|
||||
const start = currentPath[0];
|
||||
const end = currentPath[currentPath.length - 1];
|
||||
|
||||
const raisedStart = [start[0], start[1] + 0.5, start[2]] as [number, number, number];
|
||||
|
||||
const raisedEnd = [end[0], end[1] + 0.5, end[2]] as [number, number, number];
|
||||
|
||||
|
||||
const nearestToStart = findNearest(raisedStart);
|
||||
const nearestToEnd = findNearest(raisedEnd);
|
||||
|
||||
const indexOfNearestStart = findNearestIndex(nearestToStart, circlePoints);
|
||||
const indexOfNearestEnd = findNearestIndex(nearestToEnd, circlePoints);
|
||||
|
||||
const totalSegments = 64;
|
||||
const clockwiseDistance = (indexOfNearestEnd - indexOfNearestStart + totalSegments) % totalSegments;
|
||||
const counterClockwiseDistance = (indexOfNearestStart - indexOfNearestEnd + totalSegments) % totalSegments;
|
||||
|
||||
// Try both directions
|
||||
const arcClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, true);
|
||||
const arcCounterClockwise = collectArcPoints(indexOfNearestStart, indexOfNearestEnd, false);
|
||||
|
||||
const clockwiseForbidden = hasForbiddenDegrees(arcClockwise);
|
||||
const counterClockwiseForbidden = hasForbiddenDegrees(arcCounterClockwise);
|
||||
|
||||
let arcPoints: [number, number, number][] = [];
|
||||
|
||||
if (!clockwiseForbidden && (clockwiseDistance <= counterClockwiseDistance || counterClockwiseForbidden)) {
|
||||
arcPoints = arcClockwise;
|
||||
} else {
|
||||
arcPoints = arcCounterClockwise;
|
||||
}
|
||||
|
||||
const pathVectors = [
|
||||
new THREE.Vector3(start[0], start[1], start[2]),
|
||||
new THREE.Vector3(start[0], curveHeight, start[2]),
|
||||
new THREE.Vector3(nearestToStart[0], curveHeight, nearestToStart[2]),
|
||||
...arcPoints.map(point => new THREE.Vector3(point[0], curveHeight, point[2])),
|
||||
new THREE.Vector3(nearestToEnd[0], curveHeight, nearestToEnd[2]),
|
||||
new THREE.Vector3(end[0], curveHeight, end[2]),
|
||||
new THREE.Vector3(end[0], end[1], end[2])
|
||||
];
|
||||
|
||||
const pathSegments: [THREE.Vector3, THREE.Vector3][] = [];
|
||||
for (let i = 0; i < pathVectors.length - 1; i++) {
|
||||
pathSegments.push([pathVectors[i], pathVectors[i + 1]]);
|
||||
}
|
||||
|
||||
const segmentDistances = pathSegments.map(([p1, p2]) => p1.distanceTo(p2));
|
||||
segmentDistancesRef.current = segmentDistances;
|
||||
const totalDistance = segmentDistances.reduce((sum, d) => sum + d, 0);
|
||||
totalDistanceRef.current = totalDistance;
|
||||
|
||||
|
||||
setCustomCurvePoints(pathVectors);
|
||||
}
|
||||
}, [circlePoints, currentPath]);
|
||||
|
||||
// Frame update for animation
|
||||
useFrame((state, delta) => {
|
||||
|
||||
if (!startTimeRef.current || !isRunning) return;
|
||||
|
||||
|
||||
if (!ikSolver || !customCurvePoints || customCurvePoints.length === 0) return;
|
||||
|
||||
|
||||
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
|
||||
if (!bone) return;
|
||||
|
||||
|
||||
const now = performance.now();
|
||||
const elapsed = now - interpolationStartTimeRef.current;
|
||||
const duration = 2000;
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
const interpolatedPercentage =
|
||||
lastPercentageRef.current +
|
||||
(targetPercentageRef.current - lastPercentageRef.current) * t;
|
||||
const progress = Math.min(interpolatedPercentage / 100, 1);
|
||||
|
||||
const distances = segmentDistancesRef.current;
|
||||
const totalDistance = totalDistanceRef.current;
|
||||
|
||||
const coveredDistance = progress * totalDistance;
|
||||
// console.log('coveredDistance: ', coveredDistance);
|
||||
|
||||
// Traverse segments to find current position
|
||||
let index = 0;
|
||||
let accumulatedDistance = 0;
|
||||
|
||||
while (index < distances.length && coveredDistance > accumulatedDistance + distances[index]) {
|
||||
accumulatedDistance += distances[index];
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < distances.length) {
|
||||
const startPoint = customCurvePoints[index];
|
||||
const endPoint = customCurvePoints[index + 1];
|
||||
const segmentDistance = distances[index];
|
||||
const t = (coveredDistance - accumulatedDistance) / segmentDistance;
|
||||
|
||||
|
||||
|
||||
if (startPoint && endPoint) {
|
||||
const position = startPoint.clone().lerp(endPoint, t);
|
||||
bone.position.copy(position);
|
||||
}
|
||||
}
|
||||
|
||||
ikSolver.update();
|
||||
|
||||
// Reset at the end
|
||||
if (progress >= 1) {
|
||||
setCurrentPath([]);
|
||||
setCustomCurvePoints([]);
|
||||
curveRef.current = null;
|
||||
progressRef.current = 0;
|
||||
startTimeRef.current = null;
|
||||
}
|
||||
if (currentPath.length === 0 && bone) {
|
||||
bone.position.copy(bone.position);
|
||||
ikSolver.update();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
if (data.state === 'running' && data.percentage !== undefined) {
|
||||
console.log('data.percentage: ', data.percentage);
|
||||
if (data.percentage === 0) {
|
||||
startTimeRef.current = performance.now();
|
||||
lastPercentageRef.current = 0;
|
||||
targetPercentageRef.current = 0
|
||||
} else {
|
||||
lastPercentageRef.current = targetPercentageRef.current;
|
||||
}
|
||||
targetPercentageRef.current = data.percentage;
|
||||
interpolationStartTimeRef.current = performance.now();
|
||||
setIsRunning(true);
|
||||
} else {
|
||||
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
return (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
|
||||
export default ArmAnimator
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useMemo, useEffect, useState } from 'react';
|
||||
import { Canvas } from '@react-three/fiber';
|
||||
import { Environment, OrbitControls } from '@react-three/drei';
|
||||
import * as THREE from 'three';
|
||||
import * as CONSTANTS from '../../types/world/worldConstants';
|
||||
import { useTileDistance, useToggleView } from '../../store/builder/store';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
|
||||
import { Vector3, Euler } from 'three';
|
||||
import background from "../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||
|
||||
export default function CanvasComponent() {
|
||||
const { toggleView } = useToggleView();
|
||||
const { gridValue, planeValue } = useTileDistance();
|
||||
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||
|
||||
const assets = [
|
||||
{
|
||||
modelName: 'conveyor',
|
||||
modelUuid: '187988af-3d50-46e7-b9d2-211465b84fc0',
|
||||
modelfileID: '7dc04e36882e4debbc1a8e3d',
|
||||
position: [-10.293992719387735, 0, 3.039958827038974],
|
||||
rotation: { x: Math.PI, y: -1.564219187623611, z: Math.PI },
|
||||
},
|
||||
{
|
||||
modelName: 'robotic arm 4 axis',
|
||||
modelUuid: '6c666c95-7d7c-47b0-9d2c-c26a95eaba90',
|
||||
modelfileID: '52e6681fbb743a890d96c914',
|
||||
position: [-7.226814829418026, 0, 1.7900395767061474],
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
{
|
||||
modelName: 'vertical machining center 5 axis',
|
||||
modelUuid: 'c05dc5df-64ff-4597-ba64-153573e80a9b',
|
||||
modelfileID: '29dee78715ad5b853f5c346d',
|
||||
position: [-7.049029908111559, 0, -0.7406233295906898],
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
{
|
||||
modelName: 'conveyor',
|
||||
modelUuid: 'd56bb1aa-219f-45d1-8204-6f5a73ee0ab2',
|
||||
modelfileID: '7dc04e36882e4debbc1a8e3d',
|
||||
position: [-3.8644953037273306, 0, 3.0765845401995224],
|
||||
rotation: { x: Math.PI, y: -1.564219187623611, z: Math.PI },
|
||||
},
|
||||
{
|
||||
modelName: 'agv',
|
||||
modelUuid: 'bf59f396-58ff-4775-80a3-2a037eb4bf49',
|
||||
modelfileID: 'a1ee92554935007b10b3eb05',
|
||||
position: [2, 0, 5.5],
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
const [loadedAssets, setLoadedAssets] = useState<any[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const loader = new GLTFLoader();
|
||||
const dracoLoader = new DRACOLoader();
|
||||
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
|
||||
loader.setDRACOLoader(dracoLoader);
|
||||
|
||||
const loadAllAssets = async () => {
|
||||
const promises = assets.map((asset) => {
|
||||
const url = `${url_Backend_dwinzo}/api/v2/AssetFile/${asset.modelfileID}`;
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(
|
||||
url,
|
||||
(gltf) => {
|
||||
gltf.scene.position.set(...asset.position as [number, number, number]);
|
||||
gltf.scene.rotation.set(
|
||||
asset.rotation.x,
|
||||
asset.rotation.y,
|
||||
asset.rotation.z
|
||||
);
|
||||
resolve({
|
||||
...asset,
|
||||
scene: gltf.scene,
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
reject
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const results = await Promise.allSettled(promises);
|
||||
const successfulAssets = results
|
||||
.filter((r): r is PromiseFulfilledResult<any> => r.status === 'fulfilled')
|
||||
.map((r) => r.value);
|
||||
|
||||
setLoadedAssets(successfulAssets);
|
||||
};
|
||||
|
||||
loadAllAssets();
|
||||
}, []);
|
||||
|
||||
const gridHelper = useMemo(() => {
|
||||
return new THREE.GridHelper(
|
||||
gridValue.size,
|
||||
gridValue.divisions,
|
||||
CONSTANTS.gridConfig.primaryColor,
|
||||
CONSTANTS.gridConfig.secondaryColor
|
||||
);
|
||||
}, [gridValue.size, gridValue.divisions]);
|
||||
|
||||
return (
|
||||
<Canvas
|
||||
eventPrefix="client"
|
||||
gl={{ powerPreference: 'high-performance', antialias: true }}
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
camera={{ position: [10, 10, 10], fov: 50 }}
|
||||
>
|
||||
<Environment files={background} environmentIntensity={1.5} />
|
||||
<OrbitControls makeDefault target={[0, 0, 0]} />
|
||||
|
||||
{/* Grid */}
|
||||
<mesh name="Ground">
|
||||
<mesh
|
||||
name="Grid"
|
||||
position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}
|
||||
>
|
||||
<gridHelper
|
||||
args={[
|
||||
gridValue.size,
|
||||
gridValue.divisions,
|
||||
CONSTANTS.gridConfig.primaryColor,
|
||||
CONSTANTS.gridConfig.secondaryColor,
|
||||
]}
|
||||
/>
|
||||
</mesh>
|
||||
<mesh
|
||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
||||
name="Plane"
|
||||
receiveShadow
|
||||
>
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||
</mesh>
|
||||
</mesh>
|
||||
|
||||
{/* Loaded Models */}
|
||||
{loadedAssets.map((asset, index) => (
|
||||
<primitive
|
||||
key={asset.modelUuid || index}
|
||||
object={asset.scene}
|
||||
/>
|
||||
))}
|
||||
</Canvas>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import useMqttConnectionStore from '../../store/iiot/iiotmqttConnectionStore';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import useModuleStore from '../../store/useModuleStore';
|
||||
import { MaterialModel } from '../simulation/materials/instances/material/materialModel';
|
||||
|
||||
type ConveyorSimulationProps = {
|
||||
data: any;
|
||||
assetName: string;
|
||||
assetUUID: string;
|
||||
};
|
||||
|
||||
function ConveyorSimulation({ data, assetName, assetUUID }: ConveyorSimulationProps) {
|
||||
const { connect } = useMqttConnectionStore();
|
||||
const { scene } = useThree();
|
||||
const { activeModule } = useModuleStore();
|
||||
const materialRef = useRef<any>(null);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
const startPosRef = useRef<THREE.Vector3 | null>(null);
|
||||
const endPosRef = useRef<THREE.Vector3 | null>(null);
|
||||
const quaternionRef = useRef<THREE.Quaternion | null>(null);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
|
||||
const lastPercentageRef = useRef<number>(0);
|
||||
const targetPercentageRef = useRef<number>(0);
|
||||
const interpolationStartTimeRef = useRef<number>(performance.now());
|
||||
|
||||
// Initialize conveyor positions
|
||||
useEffect(() => {
|
||||
if (activeModule !== 'visualization') return;
|
||||
connect();
|
||||
|
||||
const conveyorObject = scene.getObjectByProperty('uuid', assetUUID);
|
||||
if (!conveyorObject) return;
|
||||
|
||||
conveyorObject.updateWorldMatrix(true, false);
|
||||
|
||||
const worldPosition = new THREE.Vector3();
|
||||
conveyorObject.getWorldPosition(worldPosition);
|
||||
|
||||
const worldQuaternion = new THREE.Quaternion();
|
||||
conveyorObject.getWorldQuaternion(worldQuaternion);
|
||||
|
||||
const offsetStart = new THREE.Vector3(0, 0.9, 2.3).applyQuaternion(worldQuaternion);
|
||||
const offsetEnd = new THREE.Vector3(0, 0.9, -2.3).applyQuaternion(worldQuaternion);
|
||||
|
||||
const startPosition = worldPosition.clone().add(offsetStart);
|
||||
const endPosition = worldPosition.clone().add(offsetEnd);
|
||||
|
||||
startPosRef.current = startPosition;
|
||||
endPosRef.current = endPosition;
|
||||
quaternionRef.current = worldQuaternion;
|
||||
startTimeRef.current = performance.now();
|
||||
|
||||
if (materialRef.current) {
|
||||
materialRef.current.position.copy(startPosition);
|
||||
materialRef.current.quaternion.copy(worldQuaternion);
|
||||
}
|
||||
}, [activeModule, assetUUID]);
|
||||
|
||||
// Animate movement
|
||||
useFrame(() => {
|
||||
if (data.assetName !== assetName || data.event !== 'move') return;
|
||||
if (
|
||||
!startPosRef.current ||
|
||||
!endPosRef.current ||
|
||||
!materialRef.current ||
|
||||
!quaternionRef.current ||
|
||||
!isRunning
|
||||
) return;
|
||||
|
||||
const now = performance.now();
|
||||
const elapsed = now - interpolationStartTimeRef.current;
|
||||
const duration = 2000; // interpolate every 2s
|
||||
const t = Math.min(elapsed / duration, 1);
|
||||
|
||||
const interpolatedPercentage =
|
||||
lastPercentageRef.current +
|
||||
(targetPercentageRef.current - lastPercentageRef.current) * t;
|
||||
|
||||
const progress = Math.min(interpolatedPercentage / 100, 1);
|
||||
const currentPos = new THREE.Vector3().lerpVectors(startPosRef.current, endPosRef.current, progress);
|
||||
|
||||
materialRef.current.position.copy(currentPos);
|
||||
materialRef.current.quaternion.copy(quaternionRef.current);
|
||||
|
||||
});
|
||||
|
||||
// Update movement state
|
||||
useEffect(() => {
|
||||
//
|
||||
if (data.assetName !== assetName) return;
|
||||
|
||||
if (data.state === 'running' && data.percentage !== undefined) {
|
||||
|
||||
// Reset to start if restarting
|
||||
if (data.percentage === 0) {
|
||||
lastPercentageRef.current = 0;
|
||||
targetPercentageRef.current = 0;
|
||||
|
||||
if (startPosRef.current && materialRef.current && quaternionRef.current) {
|
||||
materialRef.current.position.copy(startPosRef.current);
|
||||
materialRef.current.quaternion.copy(quaternionRef.current);
|
||||
}
|
||||
} else {
|
||||
lastPercentageRef.current = targetPercentageRef.current;
|
||||
}
|
||||
|
||||
targetPercentageRef.current = data.percentage;
|
||||
interpolationStartTimeRef.current = performance.now();
|
||||
setIsRunning(true);
|
||||
} else {
|
||||
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
|
||||
if (data.state === 'running' && typeof data.percentage === 'number') {
|
||||
const shouldBeVisible = data.percentage <= 100;
|
||||
// Only update visibility if it changes
|
||||
setIsVisible(prev => {
|
||||
if (prev !== shouldBeVisible) {
|
||||
return shouldBeVisible;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MaterialModel
|
||||
materialId={`${assetName}-mat`}
|
||||
matRef={materialRef}
|
||||
materialType={'Default material'}
|
||||
visible={isVisible}
|
||||
/>
|
||||
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default ConveyorSimulation;
|
|
@ -0,0 +1,94 @@
|
|||
import { useLoader, useThree } from '@react-three/fiber';
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { CCDIKSolver, CCDIKHelper, } from "three/examples/jsm/animation/CCDIKSolver";
|
||||
import * as THREE from "three";
|
||||
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
|
||||
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
||||
|
||||
type IKInstanceProps = {
|
||||
modelUrl: string;
|
||||
ikSolver: any;
|
||||
setIkSolver: any
|
||||
armBot: any;
|
||||
groupRef: any;
|
||||
};
|
||||
|
||||
function IIotIkSolver({ modelUrl, setIkSolver, ikSolver, armBot, groupRef }: IKInstanceProps) {
|
||||
const { scene } = useThree()
|
||||
const gltf = useLoader(GLTFLoader, modelUrl, (loader) => {
|
||||
const draco = new DRACOLoader();
|
||||
draco.setDecoderPath("https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/");
|
||||
loader.setDRACOLoader(draco);
|
||||
});
|
||||
const cloned = useMemo(() => clone(gltf?.scene), [gltf]);
|
||||
const targetBoneName = "Target";
|
||||
const skinnedMeshName = "link_0";
|
||||
const [selectedArm, setSelectedArm] = useState<THREE.Group>();
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!gltf) return;
|
||||
const OOI: any = {};
|
||||
cloned.traverse((n: any) => {
|
||||
if (n.name === targetBoneName) OOI.Target_Bone = n;
|
||||
if (n.name === skinnedMeshName) OOI.Skinned_Mesh = n;
|
||||
});
|
||||
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
||||
const iks = [
|
||||
{
|
||||
target: 7,
|
||||
effector: 6,
|
||||
links: [
|
||||
{
|
||||
index: 5,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
|
||||
rotationMax: new THREE.Vector3(Math.PI / 2, 0, 0),
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(-Math.PI / 2, 0, 0),
|
||||
rotationMax: new THREE.Vector3(0, 0, 0),
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
enabled: true,
|
||||
rotationMin: new THREE.Vector3(0, 0, 0),
|
||||
rotationMax: new THREE.Vector3(2, 0, 0),
|
||||
},
|
||||
{ index: 1, enabled: true, limitation: new THREE.Vector3(0, 1, 0) },
|
||||
{ index: 0, enabled: false, limitation: new THREE.Vector3(0, 0, 0) },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
|
||||
setIkSolver(solver);
|
||||
|
||||
const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
||||
|
||||
setSelectedArm(OOI.Target_Bone);
|
||||
|
||||
// scene.add(helper);
|
||||
|
||||
}, [cloned, gltf, setIkSolver]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{armBot.position && <group ref={groupRef} position={armBot.position} rotation={armBot.rotation} onClick={() => {
|
||||
setSelectedArm(groupRef.current?.getObjectByName(targetBoneName))
|
||||
}}>
|
||||
<primitive
|
||||
uuid={`${armBot.modelUuid}_IK`}
|
||||
object={cloned}
|
||||
scale={[1, 1, 1]}
|
||||
name={armBot.modelName}
|
||||
/>
|
||||
</group>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IIotIkSolver
|
|
@ -0,0 +1,52 @@
|
|||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import useModuleStore from '../../store/useModuleStore';
|
||||
|
||||
type MachineSimulationProps = {
|
||||
data: any;
|
||||
assetName: string;
|
||||
assetUUID: string;
|
||||
};
|
||||
|
||||
function MachineSimulation({ data, assetName, assetUUID }: MachineSimulationProps) {
|
||||
const { scene } = useThree();
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
const percentageRef = useRef<number>(0);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
// Setup: Track machine if needed (optional)
|
||||
useEffect(() => {
|
||||
if (activeModule !== 'visualization') return;
|
||||
scene.getObjectByProperty('uuid', assetUUID); // Optional: could store ref if needed
|
||||
}, [activeModule, assetUUID, scene]);
|
||||
|
||||
// Update internal state when data changes
|
||||
useEffect(() => {
|
||||
if (activeModule !== 'visualization') return;
|
||||
if (data.state === 'running' && data.percentage !== undefined) {
|
||||
|
||||
percentageRef.current = data.percentage;
|
||||
setIsRunning(true);
|
||||
} else {
|
||||
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
// Frame loop (logic-only, no visuals)
|
||||
useFrame(() => {
|
||||
if (data.assetName !== assetName || data.event !== 'move') return;
|
||||
|
||||
if (!isRunning) return;
|
||||
|
||||
const percentage = percentageRef.current;
|
||||
|
||||
// 🟢 Use percentage in logic if needed
|
||||
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default MachineSimulation;
|
|
@ -0,0 +1,105 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import useMqttConnectionStore from '../../store/iiot/iiotmqttConnectionStore';
|
||||
import { IClientSubscribeOptions } from 'mqtt';
|
||||
|
||||
|
||||
interface MqttListenerProps {
|
||||
setData: (data: any) => void;
|
||||
}
|
||||
|
||||
const MqttListener: React.FC<MqttListenerProps> = ({ setData }) => {
|
||||
const { client } = useMqttConnectionStore();
|
||||
const [message, setMessage] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!client) return;
|
||||
|
||||
const topic1 = 'conveyor0001/0001/status';
|
||||
const topic2 = 'conveyor0002/0001/status';
|
||||
const topic3 = 'cnc0001/0001/status'
|
||||
const topic4 = 'agv0001/0001/status'
|
||||
const topic5 = 'arm0001/0001/status'
|
||||
|
||||
client.subscribe(topic1, {} as IClientSubscribeOptions, (err) => {
|
||||
if (err) {
|
||||
console.error('Subscription error:', err);
|
||||
}
|
||||
});
|
||||
client.subscribe(topic2, {} as IClientSubscribeOptions, (err) => {
|
||||
if (err) {
|
||||
console.error('Subscription error:', err);
|
||||
}
|
||||
});
|
||||
client.subscribe(topic3, {} as IClientSubscribeOptions, (err) => {
|
||||
if (err) {
|
||||
console.error('Subscription error:', err);
|
||||
}
|
||||
});
|
||||
client.subscribe(topic4, {} as IClientSubscribeOptions, (err) => {
|
||||
if (err) {
|
||||
console.error('Subscription error:', err);
|
||||
}
|
||||
});
|
||||
client.subscribe(topic5, {} as IClientSubscribeOptions, (err) => {
|
||||
if (err) {
|
||||
console.error('Subscription error:', err);
|
||||
}
|
||||
});
|
||||
|
||||
const handleMessage = (receivedTopic: string, payload: Buffer) => {
|
||||
if (receivedTopic === topic1) {
|
||||
const msgStr = payload.toString();
|
||||
let mqttData = JSON.parse(msgStr);
|
||||
// if (mqttData.state === "running") {
|
||||
setData(mqttData)
|
||||
// }
|
||||
}
|
||||
if (receivedTopic === topic2) {
|
||||
const msgStr = payload.toString();
|
||||
let mqttData = JSON.parse(msgStr);
|
||||
// if (mqttData.state === "running") {
|
||||
setData(mqttData)
|
||||
// }
|
||||
}
|
||||
if (receivedTopic === topic3) {
|
||||
const msgStr = payload.toString();
|
||||
let mqttData = JSON.parse(msgStr);
|
||||
// if (mqttData.state === "running") {
|
||||
setData(mqttData)
|
||||
// }
|
||||
}
|
||||
if (receivedTopic === topic4) {
|
||||
const msgStr = payload.toString();
|
||||
let mqttData = JSON.parse(msgStr);
|
||||
// if (mqttData.state === "running") {
|
||||
setData(mqttData)
|
||||
// }
|
||||
}
|
||||
if (receivedTopic === topic5) {
|
||||
const msgStr = payload.toString();
|
||||
let mqttData = JSON.parse(msgStr);
|
||||
// if (mqttData.state === "running") {
|
||||
setData(mqttData)
|
||||
// }
|
||||
}
|
||||
};
|
||||
client.on('message', handleMessage);
|
||||
|
||||
return () => {
|
||||
client.off('message', handleMessage);
|
||||
client.unsubscribe(topic1);
|
||||
client.unsubscribe(topic2);
|
||||
client.unsubscribe(topic3);
|
||||
client.unsubscribe(topic4);
|
||||
client.unsubscribe(topic5);
|
||||
};
|
||||
}, [client]);
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default MqttListener;
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
//singleConveyor
|
||||
import { useEffect, useState } from 'react';
|
||||
import MqttListener from './MqttListener';
|
||||
import ConveyorSimulation from './ConveyorSimulation';
|
||||
import MachineSimulation from './MachineSimulation';
|
||||
import AgvSimulation from './AgvSimulation';
|
||||
import RoboticArmSimulation from './RoboticArmSimulation';
|
||||
import useModuleStore from '../../store/useModuleStore';
|
||||
|
||||
type MqttData = {
|
||||
state?: string;
|
||||
remainingTime?: number;
|
||||
percentage?: number;
|
||||
assetName?: string;
|
||||
event?: string;
|
||||
};
|
||||
|
||||
function RealTimeSimulation() {
|
||||
const [data, setData] = useState<MqttData>({});
|
||||
const { activeModule } = useModuleStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== "visualization") return;
|
||||
// console.log('data: ', data);
|
||||
if (data.state === "running") {
|
||||
console.log('data: ', data);
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConveyorSimulation data={data} assetName="conveyor0001" assetUUID="3bb8aabe-3d30-4cbb-9cb6-a64b54c855da" />
|
||||
<ConveyorSimulation data={data} assetName="conveyor0002" assetUUID="1288e8f9-95dd-4a1a-a087-0673bbbc3b34" />
|
||||
<MachineSimulation data={data} assetName="cnc0001" assetUUID="d3598136-40f6-43a9-afd2-8a8899c0986d" />
|
||||
<AgvSimulation data={data} assetName="agv0001" assetUUID="1dd01c22-cc58-40cf-9e08-84c26f3f4eba" />
|
||||
<RoboticArmSimulation data={data} assetName="arm0001" assetUUID="f8344b0b-0c13-4e78-be1f-b6cc210038a4" />
|
||||
<MqttListener setData={setData} />
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RealTimeSimulation;
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import useModuleStore from "../../store/useModuleStore";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import IIotIkSolver from "./IIotIkSolver";
|
||||
import armModel from "../../assets/gltf-glb/rigged/ik_arm_1.glb";
|
||||
import * as THREE from "three";
|
||||
import ArmAnimator from "./ArmAnimator";
|
||||
import { useFloorItems } from "../../store/builder/store";
|
||||
import { MaterialModel } from "../simulation/materials/instances/material/materialModel";
|
||||
|
||||
type ArmBotSimulationProps = {
|
||||
data: any;
|
||||
assetName: string;
|
||||
assetUUID: string;
|
||||
};
|
||||
|
||||
function RoboticArmSimulation({
|
||||
data,
|
||||
assetName,
|
||||
assetUUID,
|
||||
}: ArmBotSimulationProps) {
|
||||
const [ikSolver, setIkSolver] = useState<any>(null);
|
||||
const { scene } = useThree();
|
||||
const { activeModule } = useModuleStore();
|
||||
const groupRef = useRef<any>(null);
|
||||
const [armBot, setArmBot] = useState<any>(null);
|
||||
const targetBone = "Target";
|
||||
const restPosition = new THREE.Vector3(0, 1.75, -1.6);
|
||||
const [path, setPath] = useState<[number, number, number][]>([]);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
const startTimeRef = useRef<number | null>(null);
|
||||
|
||||
const lastPercentageRef = useRef<number>(0);
|
||||
const targetPercentageRef = useRef<number>(0);
|
||||
const interpolationStartTimeRef = useRef<number>(performance.now());
|
||||
const materialRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeModule !== "visualization") return;
|
||||
if (assetUUID) {
|
||||
const targetMesh = scene?.getObjectByProperty("uuid", assetUUID);
|
||||
if (targetMesh) {
|
||||
targetMesh.visible = activeModule !== "visualization"
|
||||
}
|
||||
}
|
||||
const RoboticArmObject = scene.getObjectByProperty("uuid", assetUUID);
|
||||
if (!RoboticArmObject) return;
|
||||
const updatedArmBot = {
|
||||
modelUuid: assetUUID,
|
||||
modelName: assetName,
|
||||
position: [RoboticArmObject.position.x, RoboticArmObject.position.y, RoboticArmObject.position.z,],
|
||||
rotation: [RoboticArmObject.rotation.x, RoboticArmObject.rotation.y, RoboticArmObject.rotation.z,],
|
||||
};
|
||||
setArmBot(updatedArmBot);
|
||||
}, [scene, assetUUID, activeModule]);
|
||||
|
||||
const lastEventRef = useRef<string | null>(null);
|
||||
const lastPathRef = useRef<string>(""); // Stringified path to compare
|
||||
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
if (!ikSolver) return;
|
||||
if (!isRunning) return;
|
||||
|
||||
const targetBones = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === targetBone);
|
||||
if (!targetBones) return;
|
||||
//action 1
|
||||
const startPoint = new THREE.Vector3(-0.5119150023588372, 1.08653750252812, 1.2082537344869024);
|
||||
const endPoint = new THREE.Vector3(-0.005966507840761359, 1.101367457937616, -1.242848820163239);
|
||||
//action 2
|
||||
const startPoint1 = new THREE.Vector3(-0.005966507840761359, 1.101367457937616, -1.242848820163239);
|
||||
const endPoint1 = new THREE.Vector3(0.8449084618119792, 1.0275816535760263, 1.399557309225635);
|
||||
|
||||
if (lastEventRef.current === data.event) return;
|
||||
if (data.event === 'pick1') {
|
||||
console.log("picking");
|
||||
const curve = createCurveBetweenTwoPoints(targetBones.position.clone(), startPoint.clone());
|
||||
if (curve) {
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}
|
||||
lastEventRef.current = 'pick1';
|
||||
}
|
||||
|
||||
if (data.event === 'drop1') {
|
||||
console.log("dropping");
|
||||
const curve = createCurveBetweenTwoPoints(startPoint.clone(), endPoint.clone());
|
||||
if (curve) {
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}
|
||||
lastEventRef.current = 'drop1';
|
||||
|
||||
}
|
||||
if (data.event === 'pick2') {
|
||||
console.log("picking111");
|
||||
const curve = createCurveBetweenTwoPoints(targetBones.position.clone(), startPoint1.clone());
|
||||
if (curve) {
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}
|
||||
lastEventRef.current = 'pick2';
|
||||
|
||||
}
|
||||
if (data.event === 'drop2') {
|
||||
console.log("dropping1111");
|
||||
const curve = createCurveBetweenTwoPoints(startPoint1.clone(), endPoint1.clone());
|
||||
if (curve) {
|
||||
setPath(curve.points.map(point => [point.x, point.y, point.z]));
|
||||
}
|
||||
lastEventRef.current = 'drop2';
|
||||
|
||||
}
|
||||
}, [ikSolver, data, assetName, isRunning]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
if (data.state === 'running' && data.percentage !== undefined) {
|
||||
if (data.percentage === 0) {
|
||||
lastPercentageRef.current = 0;
|
||||
targetPercentageRef.current = 0
|
||||
} else {
|
||||
lastPercentageRef.current = targetPercentageRef.current;
|
||||
}
|
||||
targetPercentageRef.current = data.percentage;
|
||||
interpolationStartTimeRef.current = performance.now();
|
||||
setIsRunning(true);
|
||||
} else {
|
||||
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
function createCurveBetweenTwoPoints(p1: any, p2: any) {
|
||||
const mid = new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5);
|
||||
const points = [p1, mid, p2];
|
||||
return new THREE.CatmullRomCurve3(points);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!ikSolver || !materialRef.current) return;
|
||||
|
||||
const boneTarget = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === "Bone004");
|
||||
const bone = ikSolver.mesh.skeleton.bones.find((b: any) => b.name === "Effector");
|
||||
|
||||
if (!boneTarget || !bone) return;
|
||||
|
||||
if (data.state === "running" && data.assetName === assetName) {
|
||||
// Get world positions
|
||||
const boneTargetWorldPos = new THREE.Vector3();
|
||||
const boneWorldPos = new THREE.Vector3();
|
||||
boneTarget.getWorldPosition(boneTargetWorldPos);
|
||||
bone.getWorldPosition(boneWorldPos);
|
||||
|
||||
// Calculate direction
|
||||
const direction = new THREE.Vector3();
|
||||
direction.subVectors(boneWorldPos, boneTargetWorldPos).normalize();
|
||||
|
||||
const adjustedPosition = boneWorldPos.clone().addScaledVector(direction, 0.01);
|
||||
|
||||
//set position
|
||||
materialRef.current.position.copy(adjustedPosition);
|
||||
|
||||
// Set rotation
|
||||
const worldQuaternion = new THREE.Quaternion();
|
||||
bone.getWorldQuaternion(worldQuaternion);
|
||||
materialRef.current.quaternion.copy(worldQuaternion);
|
||||
}
|
||||
|
||||
}, [data])
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (data.assetName !== assetName) return;
|
||||
|
||||
if (data.state === 'running' && typeof data.percentage === 'number') {
|
||||
const shouldBeVisible = data.percentage <= 100;
|
||||
// Only update visibility if it changes
|
||||
setIsVisible(prev => {
|
||||
if (prev !== shouldBeVisible) {
|
||||
return shouldBeVisible;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
}
|
||||
}, [data, assetName]);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeModule === "visualization" && armBot && (
|
||||
<>
|
||||
<IIotIkSolver
|
||||
armBot={armBot}
|
||||
ikSolver={ikSolver}
|
||||
setIkSolver={setIkSolver}
|
||||
modelUrl={armModel}
|
||||
groupRef={groupRef}
|
||||
/>
|
||||
<ArmAnimator
|
||||
armBot={armBot}
|
||||
ikSolver={ikSolver}
|
||||
setIkSolver={setIkSolver}
|
||||
restPosition={restPosition}
|
||||
targetBone={targetBone}
|
||||
path={path}
|
||||
assetName={assetName}
|
||||
data={data}
|
||||
/>
|
||||
{/* <MaterialModel
|
||||
materialId={`${assetName}-mat`}
|
||||
matRef={materialRef}
|
||||
materialType={'Default material'}
|
||||
visible={isVisible}
|
||||
/> */}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default RoboticArmSimulation;
|
|
@ -7,6 +7,7 @@ import Visualization from "../visualization/visualization";
|
|||
import Setup from "./setup/setup";
|
||||
import Simulation from "../simulation/simulation";
|
||||
import Collaboration from "../collaboration/collaboration";
|
||||
import RealTimeSimulation from "../IIOTTemp/RealTimeSimulation";
|
||||
|
||||
export default function Scene() {
|
||||
const map = useMemo(() => [
|
||||
|
@ -33,8 +34,51 @@ export default function Scene() {
|
|||
|
||||
<Simulation />
|
||||
|
||||
<RealTimeSimulation />
|
||||
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// export default function Scene() {
|
||||
// const map = useMemo(() => [
|
||||
// { name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
// { name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
// { name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||
// { name: "right", keys: ["ArrowRight", "d", "D"] },
|
||||
// ], []);
|
||||
// const { activeModule, setActiveModule } = useModuleStore();
|
||||
|
||||
// return (
|
||||
// <KeyboardControls map={map}>
|
||||
// {activeModule !== "visualization" &&
|
||||
// <Canvas
|
||||
// eventPrefix="client"
|
||||
// gl={{ powerPreference: "high-performance", antialias: true }}
|
||||
// onContextMenu={(e) => {
|
||||
// e.preventDefault();
|
||||
// }}
|
||||
// >
|
||||
// <Setup />
|
||||
|
||||
// <Collaboration />
|
||||
|
||||
// <Builder />
|
||||
|
||||
// <Simulation />
|
||||
|
||||
|
||||
|
||||
// <Visualization />
|
||||
// </Canvas>}
|
||||
// {activeModule === "visualization" && <CanvasComponent />}
|
||||
|
||||
// </KeyboardControls>
|
||||
// );
|
||||
// }
|
|
@ -0,0 +1,49 @@
|
|||
import { create } from 'zustand';
|
||||
import mqtt, { MqttClient } from 'mqtt';
|
||||
|
||||
interface MqttConnectionState {
|
||||
client: MqttClient | null;
|
||||
connected: boolean;
|
||||
connect: () => void;
|
||||
disconnect: () => void;
|
||||
}
|
||||
|
||||
const useMqttConnectionStore = create<MqttConnectionState>((set, get) => {
|
||||
let client: MqttClient;
|
||||
|
||||
return {
|
||||
client: null,
|
||||
connected: false,
|
||||
|
||||
connect: () => {
|
||||
if (get().connected) return;
|
||||
client = mqtt.connect('ws://185.100.212.76:23457'); // Replace with your broker URL
|
||||
|
||||
|
||||
client.on('connect', () => {
|
||||
console.log('Connected to MQTT');
|
||||
set({ connected: true, client });
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
console.error('MQTT Error:', err);
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
console.warn('MQTT connection closed');
|
||||
set({ connected: false, client: null });
|
||||
});
|
||||
},
|
||||
|
||||
disconnect: () => {
|
||||
const currentClient = get().client;
|
||||
if (currentClient && currentClient.connected) {
|
||||
currentClient.end();
|
||||
set({ connected: false, client: null });
|
||||
console.log('MQTT disconnected');
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default useMqttConnectionStore;
|
Loading…
Reference in New Issue