@react-three/drei
Version:
useful add-ons for react-three-fiber
193 lines (190 loc) • 6.45 kB
JavaScript
import * as React from 'react';
import * as THREE from 'three';
import { useThree } from '@react-three/fiber';
import { Line } from '../../core/Line.js';
import { Html } from '../Html.js';
import { context } from './context.js';
const vec1 = /* @__PURE__ */new THREE.Vector3();
const vec2 = /* @__PURE__ */new THREE.Vector3();
const calculateOffset = (clickPoint, normal, rayStart, rayDir) => {
const e1 = normal.dot(normal);
const e2 = normal.dot(clickPoint) - normal.dot(rayStart);
const e3 = normal.dot(rayDir);
if (e3 === 0) {
return -e2 / e1;
}
vec1.copy(rayDir).multiplyScalar(e1 / e3).sub(normal);
vec2.copy(rayDir).multiplyScalar(e2 / e3).add(rayStart).sub(clickPoint);
const offset = -vec1.dot(vec2) / vec1.dot(vec1);
return offset;
};
const upV = /* @__PURE__ */new THREE.Vector3(0, 1, 0);
const offsetMatrix = /* @__PURE__ */new THREE.Matrix4();
const AxisArrow = ({
direction,
axis
}) => {
const {
translation,
translationLimits,
annotations,
annotationsClass,
depthTest,
scale,
lineWidth,
fixed,
axisColors,
hoveredColor,
opacity,
onDragStart,
onDrag,
onDragEnd,
userData
} = React.useContext(context);
const camControls = useThree(state => state.controls);
const divRef = React.useRef(null);
const objRef = React.useRef(null);
const clickInfo = React.useRef(null);
const offset0 = React.useRef(0);
const [isHovered, setIsHovered] = React.useState(false);
const onPointerDown = React.useCallback(e => {
if (annotations) {
divRef.current.innerText = `${translation.current[axis].toFixed(2)}`;
divRef.current.style.display = 'block';
}
e.stopPropagation();
const rotation = new THREE.Matrix4().extractRotation(objRef.current.matrixWorld);
const clickPoint = e.point.clone();
const origin = new THREE.Vector3().setFromMatrixPosition(objRef.current.matrixWorld);
const dir = direction.clone().applyMatrix4(rotation).normalize();
clickInfo.current = {
clickPoint,
dir
};
offset0.current = translation.current[axis];
onDragStart({
component: 'Arrow',
axis,
origin,
directions: [dir]
});
camControls && (camControls.enabled = false);
// @ts-ignore - setPointerCapture is not in the type definition
e.target.setPointerCapture(e.pointerId);
}, [annotations, direction, camControls, onDragStart, translation, axis]);
const onPointerMove = React.useCallback(e => {
e.stopPropagation();
if (!isHovered) setIsHovered(true);
if (clickInfo.current) {
const {
clickPoint,
dir
} = clickInfo.current;
const [min, max] = (translationLimits == null ? void 0 : translationLimits[axis]) || [undefined, undefined];
let offset = calculateOffset(clickPoint, dir, e.ray.origin, e.ray.direction);
if (min !== undefined) {
offset = Math.max(offset, min - offset0.current);
}
if (max !== undefined) {
offset = Math.min(offset, max - offset0.current);
}
translation.current[axis] = offset0.current + offset;
if (annotations) {
divRef.current.innerText = `${translation.current[axis].toFixed(2)}`;
}
offsetMatrix.makeTranslation(dir.x * offset, dir.y * offset, dir.z * offset);
onDrag(offsetMatrix);
}
}, [annotations, onDrag, isHovered, translation, translationLimits, axis]);
const onPointerUp = React.useCallback(e => {
if (annotations) {
divRef.current.style.display = 'none';
}
e.stopPropagation();
clickInfo.current = null;
onDragEnd();
camControls && (camControls.enabled = true);
// @ts-ignore - releasePointerCapture & PointerEvent#pointerId is not in the type definition
e.target.releasePointerCapture(e.pointerId);
}, [annotations, camControls, onDragEnd]);
const onPointerOut = React.useCallback(e => {
e.stopPropagation();
setIsHovered(false);
}, []);
const {
cylinderLength,
coneWidth,
coneLength,
matrixL
} = React.useMemo(() => {
const coneWidth = fixed ? lineWidth / scale * 1.6 : scale / 20;
const coneLength = fixed ? 0.2 : scale / 5;
const cylinderLength = fixed ? 1 - coneLength : scale - coneLength;
const quaternion = new THREE.Quaternion().setFromUnitVectors(upV, direction.clone().normalize());
const matrixL = new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
return {
cylinderLength,
coneWidth,
coneLength,
matrixL
};
}, [direction, scale, lineWidth, fixed]);
const color_ = isHovered ? hoveredColor : axisColors[axis];
return /*#__PURE__*/React.createElement("group", {
ref: objRef
}, /*#__PURE__*/React.createElement("group", {
matrix: matrixL,
matrixAutoUpdate: false,
onPointerDown: onPointerDown,
onPointerMove: onPointerMove,
onPointerUp: onPointerUp,
onPointerOut: onPointerOut
}, annotations && /*#__PURE__*/React.createElement(Html, {
position: [0, -coneLength, 0]
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'none',
background: '#151520',
color: 'white',
padding: '6px 8px',
borderRadius: 7,
whiteSpace: 'nowrap'
},
className: annotationsClass,
ref: divRef
})), /*#__PURE__*/React.createElement("mesh", {
visible: false,
position: [0, (cylinderLength + coneLength) / 2.0, 0],
userData: userData
}, /*#__PURE__*/React.createElement("cylinderGeometry", {
args: [coneWidth * 1.4, coneWidth * 1.4, cylinderLength + coneLength, 8, 1]
})), /*#__PURE__*/React.createElement(Line, {
transparent: true,
raycast: () => null,
depthTest: depthTest,
points: [0, 0, 0, 0, cylinderLength, 0],
lineWidth: lineWidth,
side: THREE.DoubleSide,
color: color_,
opacity: opacity,
polygonOffset: true,
renderOrder: 1,
polygonOffsetFactor: -10,
fog: false
}), /*#__PURE__*/React.createElement("mesh", {
raycast: () => null,
position: [0, cylinderLength + coneLength / 2.0, 0],
renderOrder: 500
}, /*#__PURE__*/React.createElement("coneGeometry", {
args: [coneWidth, coneLength, 24, 1]
}), /*#__PURE__*/React.createElement("meshBasicMaterial", {
transparent: true,
depthTest: depthTest,
color: color_,
opacity: opacity,
polygonOffset: true,
polygonOffsetFactor: -10,
fog: false
}))));
};
export { AxisArrow, calculateOffset };