|
|
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 decomposeIntoBasis = (e1, e2, offset) => { |
|
|
const i1 = Math.abs(e1.x) >= Math.abs(e1.y) && Math.abs(e1.x) >= Math.abs(e1.z) ? 0 : Math.abs(e1.y) >= Math.abs(e1.x) && Math.abs(e1.y) >= Math.abs(e1.z) ? 1 : 2; |
|
|
const e2DegrowthOrder = [0, 1, 2].sort((a, b) => Math.abs(e2.getComponent(b)) - Math.abs(e2.getComponent(a))); |
|
|
const i2 = i1 === e2DegrowthOrder[0] ? e2DegrowthOrder[1] : e2DegrowthOrder[0]; |
|
|
const a1 = e1.getComponent(i1); |
|
|
const a2 = e1.getComponent(i2); |
|
|
const b1 = e2.getComponent(i1); |
|
|
const b2 = e2.getComponent(i2); |
|
|
const c1 = offset.getComponent(i1); |
|
|
const c2 = offset.getComponent(i2); |
|
|
const y = (c2 - c1 * (a2 / a1)) / (b2 - b1 * (a2 / a1)); |
|
|
const x = (c1 - y * b1) / a1; |
|
|
return [x, y]; |
|
|
}; |
|
|
const ray = new THREE.Ray(); |
|
|
const intersection = new THREE.Vector3(); |
|
|
const offsetMatrix = new THREE.Matrix4(); |
|
|
const PlaneSlider = ({ |
|
|
dir1, |
|
|
dir2, |
|
|
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 offsetX0 = React.useRef(0); |
|
|
const offsetY0 = React.useRef(0); |
|
|
const [isHovered, setIsHovered] = React.useState(false); |
|
|
const onPointerDown = React.useCallback(e => { |
|
|
if (annotations) { |
|
|
divRef.current.innerText = `${translation.current[(axis + 1) % 3].toFixed(2)}, ${translation.current[(axis + 2) % 3].toFixed(2)}`; |
|
|
divRef.current.style.display = 'block'; |
|
|
} |
|
|
e.stopPropagation(); |
|
|
const clickPoint = e.point.clone(); |
|
|
const origin = new THREE.Vector3().setFromMatrixPosition(objRef.current.matrixWorld); |
|
|
const e1 = new THREE.Vector3().setFromMatrixColumn(objRef.current.matrixWorld, 0).normalize(); |
|
|
const e2 = new THREE.Vector3().setFromMatrixColumn(objRef.current.matrixWorld, 1).normalize(); |
|
|
const normal = new THREE.Vector3().setFromMatrixColumn(objRef.current.matrixWorld, 2).normalize(); |
|
|
const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(normal, origin); |
|
|
clickInfo.current = { |
|
|
clickPoint, |
|
|
e1, |
|
|
e2, |
|
|
plane |
|
|
}; |
|
|
offsetX0.current = translation.current[(axis + 1) % 3]; |
|
|
offsetY0.current = translation.current[(axis + 2) % 3]; |
|
|
onDragStart({ |
|
|
component: 'Slider', |
|
|
axis, |
|
|
origin, |
|
|
directions: [e1, e2, normal] |
|
|
}); |
|
|
camControls && (camControls.enabled = false); |
|
|
|
|
|
e.target.setPointerCapture(e.pointerId); |
|
|
}, [annotations, camControls, onDragStart, axis]); |
|
|
const onPointerMove = React.useCallback(e => { |
|
|
e.stopPropagation(); |
|
|
if (!isHovered) setIsHovered(true); |
|
|
if (clickInfo.current) { |
|
|
const { |
|
|
clickPoint, |
|
|
e1, |
|
|
e2, |
|
|
plane |
|
|
} = clickInfo.current; |
|
|
const [minX, maxX] = (translationLimits == null ? void 0 : translationLimits[(axis + 1) % 3]) || [undefined, undefined]; |
|
|
const [minY, maxY] = (translationLimits == null ? void 0 : translationLimits[(axis + 2) % 3]) || [undefined, undefined]; |
|
|
ray.copy(e.ray); |
|
|
ray.intersectPlane(plane, intersection); |
|
|
ray.direction.negate(); |
|
|
ray.intersectPlane(plane, intersection); |
|
|
intersection.sub(clickPoint); |
|
|
let [offsetX, offsetY] = decomposeIntoBasis(e1, e2, intersection); |
|
|
|
|
|
|
|
|
if (minX !== undefined) { |
|
|
offsetX = Math.max(offsetX, minX - offsetX0.current); |
|
|
} |
|
|
if (maxX !== undefined) { |
|
|
offsetX = Math.min(offsetX, maxX - offsetX0.current); |
|
|
} |
|
|
if (minY !== undefined) { |
|
|
offsetY = Math.max(offsetY, minY - offsetY0.current); |
|
|
} |
|
|
if (maxY !== undefined) { |
|
|
offsetY = Math.min(offsetY, maxY - offsetY0.current); |
|
|
} |
|
|
translation.current[(axis + 1) % 3] = offsetX0.current + offsetX; |
|
|
translation.current[(axis + 2) % 3] = offsetY0.current + offsetY; |
|
|
if (annotations) { |
|
|
divRef.current.innerText = `${translation.current[(axis + 1) % 3].toFixed(2)}, ${translation.current[(axis + 2) % 3].toFixed(2)}`; |
|
|
} |
|
|
offsetMatrix.makeTranslation(offsetX * e1.x + offsetY * e2.x, offsetX * e1.y + offsetY * e2.y, offsetX * e1.z + offsetY * e2.z); |
|
|
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); |
|
|
|
|
|
e.target.releasePointerCapture(e.pointerId); |
|
|
}, [annotations, camControls, onDragEnd]); |
|
|
const onPointerOut = React.useCallback(e => { |
|
|
e.stopPropagation(); |
|
|
setIsHovered(false); |
|
|
}, []); |
|
|
const matrixL = React.useMemo(() => { |
|
|
const dir1N = dir1.clone().normalize(); |
|
|
const dir2N = dir2.clone().normalize(); |
|
|
return new THREE.Matrix4().makeBasis(dir1N, dir2N, dir1N.clone().cross(dir2N)); |
|
|
}, [dir1, dir2]); |
|
|
const pos1 = fixed ? 1 / 7 : scale / 7; |
|
|
const length = fixed ? 0.225 : scale * 0.225; |
|
|
const color = isHovered ? hoveredColor : axisColors[axis]; |
|
|
const points = React.useMemo(() => [new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, length, 0), new THREE.Vector3(length, length, 0), new THREE.Vector3(length, 0, 0), new THREE.Vector3(0, 0, 0)], [length]); |
|
|
return React.createElement("group", { |
|
|
ref: objRef, |
|
|
matrix: matrixL, |
|
|
matrixAutoUpdate: false |
|
|
}, annotations && React.createElement(Html, { |
|
|
position: [0, 0, 0] |
|
|
}, React.createElement("div", { |
|
|
style: { |
|
|
display: 'none', |
|
|
background: '#151520', |
|
|
color: 'white', |
|
|
padding: '6px 8px', |
|
|
borderRadius: 7, |
|
|
whiteSpace: 'nowrap' |
|
|
}, |
|
|
className: annotationsClass, |
|
|
ref: divRef |
|
|
})), React.createElement("group", { |
|
|
position: [pos1 * 1.7, pos1 * 1.7, 0] |
|
|
}, React.createElement("mesh", { |
|
|
visible: true, |
|
|
onPointerDown: onPointerDown, |
|
|
onPointerMove: onPointerMove, |
|
|
onPointerUp: onPointerUp, |
|
|
onPointerOut: onPointerOut, |
|
|
scale: length, |
|
|
userData: userData |
|
|
}, React.createElement("planeGeometry", null), React.createElement("meshBasicMaterial", { |
|
|
transparent: true, |
|
|
depthTest: depthTest, |
|
|
color: color, |
|
|
polygonOffset: true, |
|
|
polygonOffsetFactor: -10, |
|
|
side: THREE.DoubleSide, |
|
|
fog: false |
|
|
})), React.createElement(Line, { |
|
|
position: [-length / 2, -length / 2, 0], |
|
|
transparent: true, |
|
|
depthTest: depthTest, |
|
|
points: points, |
|
|
lineWidth: lineWidth, |
|
|
color: color, |
|
|
opacity: opacity, |
|
|
polygonOffset: true, |
|
|
polygonOffsetFactor: -10, |
|
|
userData: userData, |
|
|
fog: false |
|
|
}))); |
|
|
}; |
|
|
|
|
|
export { PlaneSlider }; |
|
|
|