@react-three/drei
Version:
useful add-ons for react-three-fiber
122 lines (118 loc) • 4.24 kB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
import * as THREE from 'three';
import * as React from 'react';
import { useThree, useInstanceHandle, useFrame } from '@react-three/fiber';
import { misc, easing } from 'maath';
const isObject3DRef = ref => (ref == null ? void 0 : ref.current) instanceof THREE.Object3D;
const MotionContext = /* @__PURE__ */React.createContext(null);
function useMotion() {
const context = React.useContext(MotionContext);
if (!context) throw new Error('useMotion hook must be used in a MotionPathControls component.');
return context;
}
function Debug({
points = 50,
color = 'black'
}) {
const {
path
} = useMotion();
const [dots, setDots] = React.useState([]);
const material = React.useMemo(() => new THREE.MeshBasicMaterial({
color: color
}), [color]);
const geometry = React.useMemo(() => new THREE.SphereGeometry(0.025, 16, 16), []);
const last = React.useRef([]);
React.useEffect(() => {
if (path.curves !== last.current) {
setDots(path.getPoints(points));
last.current = path.curves;
}
});
return dots.map((item, index) => /*#__PURE__*/React.createElement("mesh", {
key: index,
material: material,
geometry: geometry,
position: [item.x, item.y, item.z]
}));
}
const MotionPathControls = /* @__PURE__ */React.forwardRef(({
children,
curves = [],
debug = false,
debugColor = 'black',
object,
focus,
loop = true,
offset = undefined,
smooth = false,
eps = 0.00001,
damping = 0.1,
focusDamping = 0.1,
maxSpeed = Infinity,
...props
}, fref) => {
const {
camera
} = useThree();
const ref = React.useRef(null);
const pos = React.useRef(offset !== null && offset !== void 0 ? offset : 0);
const path = React.useMemo(() => new THREE.CurvePath(), []);
const state = React.useMemo(() => ({
focus,
object: (object == null ? void 0 : object.current) instanceof THREE.Object3D ? object : {
current: camera
},
path,
current: pos.current,
offset: pos.current,
point: new THREE.Vector3(),
tangent: new THREE.Vector3(),
next: new THREE.Vector3()
}), [focus, object]);
const instanceRef = useInstanceHandle(ref);
React.useLayoutEffect(() => {
const instance = instanceRef.current;
path.curves = [];
const _curves = curves.length > 0 ? curves : instance.children.map(instance => instance.object);
for (let i = 0; i < _curves.length; i++) path.add(_curves[i]);
// Smoothen curve
if (smooth) {
const points = path.getPoints(typeof smooth === 'number' ? smooth : 1);
const catmull = new THREE.CatmullRomCurve3(points);
path.curves = [catmull];
}
path.updateArcLengths();
});
React.useImperativeHandle(fref, () => Object.assign(ref.current, {
motion: state
}), [state]);
React.useLayoutEffect(() => {
// When offset changes, normalise pos to avoid overshoot spinning
pos.current = misc.repeat(pos.current, 1);
}, [offset]);
const vec = React.useMemo(() => new THREE.Vector3(), []);
useFrame((_state, delta) => {
const lastOffset = state.offset;
easing.damp(pos, 'current', offset !== undefined ? offset : state.current, damping, delta, maxSpeed, undefined, eps);
state.offset = loop ? misc.repeat(pos.current, 1) : misc.clamp(pos.current, 0, 1);
if (path.getCurveLengths().length > 0) {
path.getPointAt(state.offset, state.point);
path.getTangentAt(state.offset, state.tangent).normalize();
path.getPointAt(misc.repeat(pos.current - (lastOffset - state.offset), 1), state.next);
const target = (object == null ? void 0 : object.current) instanceof THREE.Object3D ? object.current : camera;
target.position.copy(state.point);
if (focus) {
easing.dampLookAt(target, isObject3DRef(focus) ? focus.current.getWorldPosition(vec) : focus, focusDamping, delta, maxSpeed, undefined, eps);
}
}
});
return /*#__PURE__*/React.createElement("group", _extends({
ref: ref
}, props), /*#__PURE__*/React.createElement(MotionContext.Provider, {
value: state
}, children, debug && /*#__PURE__*/React.createElement(Debug, {
color: debugColor
})));
});
export { MotionPathControls, useMotion };