141 lines
5.0 KiB
TypeScript
141 lines
5.0 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import * as THREE from "three";
|
|
import { useThree } from "@react-three/fiber";
|
|
import { Box, Sphere } from "@react-three/drei";
|
|
|
|
function PillarJibHelper({
|
|
crane,
|
|
points
|
|
}: {
|
|
crane: CraneStatus,
|
|
points: [THREE.Vector3, THREE.Vector3] | null;
|
|
}) {
|
|
const { scene } = useThree();
|
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
|
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
|
|
|
const { geometry, position } = useMemo(() => {
|
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
|
if (!model) return { geometry: null, position: null };
|
|
|
|
const base = model.getObjectByName('base');
|
|
const trolley = model.getObjectByName('trolley');
|
|
const hook = model.getObjectByName('hook');
|
|
|
|
if (!base || !trolley || !hook || !points) return { geometry: null, position: null };
|
|
|
|
const baseWorld = new THREE.Vector3();
|
|
base.getWorldPosition(baseWorld);
|
|
|
|
const trolleyWorld = new THREE.Vector3();
|
|
trolley.getWorldPosition(trolleyWorld);
|
|
|
|
const hookWorld = new THREE.Vector3();
|
|
hook.getWorldPosition(hookWorld);
|
|
|
|
const trolleyMinOffset = -1;
|
|
const trolleyMaxOffset = 1.75;
|
|
const hookMinOffset = 0.25;
|
|
const hookMaxOffset = -1.5;
|
|
|
|
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
|
const outerRadius = distFromBase + trolleyMaxOffset;
|
|
const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
|
|
const height = (hookMinOffset - (hookMaxOffset));
|
|
const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2;
|
|
|
|
const shape = new THREE.Shape();
|
|
shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
|
|
|
|
const hole = new THREE.Path();
|
|
hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
|
|
shape.holes.push(hole);
|
|
|
|
const extrudeSettings = {
|
|
depth: height,
|
|
bevelEnabled: false,
|
|
steps: 1
|
|
};
|
|
|
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
|
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
|
|
|
const yMin = hookWorld.y + hookMaxOffset;
|
|
const yMax = hookWorld.y + hookMinOffset;
|
|
|
|
function clampToCylinder(pos: THREE.Vector3) {
|
|
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
|
const distance = xzDist.length();
|
|
|
|
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
|
if (distance > 0) {
|
|
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
|
}
|
|
|
|
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
|
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
|
|
|
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
|
}
|
|
|
|
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
|
const newIsInside: [boolean, boolean] = [false, false];
|
|
|
|
points.forEach((point, i) => {
|
|
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
|
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
|
const insideY = point.y >= yMin && point.y <= yMax;
|
|
newIsInside[i] = insideXZ && insideY;
|
|
|
|
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
|
});
|
|
|
|
setClampedPoints(newClampedPoints);
|
|
setIsInside(newIsInside);
|
|
|
|
return { geometry, position };
|
|
}, [scene, crane.modelUuid, points]);
|
|
|
|
if (!geometry || !position) return null;
|
|
|
|
return (
|
|
<>
|
|
<mesh
|
|
geometry={geometry}
|
|
position={position}
|
|
rotation={[Math.PI / 2, 0, 0]}
|
|
>
|
|
<meshStandardMaterial
|
|
color={0x888888}
|
|
metalness={0.5}
|
|
roughness={0.4}
|
|
side={THREE.DoubleSide}
|
|
transparent={true}
|
|
opacity={0.3}
|
|
/>
|
|
</mesh>
|
|
|
|
{points && points.map((point, i) => (
|
|
<Box
|
|
key={`original-${i}`}
|
|
position={point}
|
|
args={[0.15, 0.15, 0.15]}
|
|
>
|
|
<meshStandardMaterial color="yellow" />
|
|
</Box>
|
|
))}
|
|
|
|
{clampedPoints && clampedPoints.map((point, i) => (
|
|
<Sphere
|
|
key={`clamped-${i}`}
|
|
position={point}
|
|
args={[0.1, 16, 16]}
|
|
>
|
|
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
|
</Sphere>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default PillarJibHelper; |