@react-three/drei
Version:
useful add-ons for react-three-fiber
203 lines (196 loc) • 7.6 kB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
import * as THREE from 'three';
import * as React from 'react';
import { extend, useFrame } from '@react-three/fiber';
const _inverseMatrix = /* @__PURE__ */new THREE.Matrix4();
const _ray = /* @__PURE__ */new THREE.Ray();
const _sphere = /* @__PURE__ */new THREE.Sphere();
const _position = /* @__PURE__ */new THREE.Vector3();
class PositionPoint extends THREE.Group {
constructor() {
super();
this.size = 0;
this.color = new THREE.Color('white');
this.instance = {
current: undefined
};
this.instanceKey = {
current: undefined
};
}
// This will allow the virtual instance have bounds
get geometry() {
var _this$instance$curren;
return (_this$instance$curren = this.instance.current) == null ? void 0 : _this$instance$curren.geometry;
}
raycast(raycaster, intersects) {
var _raycaster$params$Poi, _raycaster$params$Poi2;
const parent = this.instance.current;
if (!parent || !parent.geometry) return;
const instanceId = parent.userData.instances.indexOf(this.instanceKey);
// If the instance wasn't found or exceeds the parents draw range, bail out
if (instanceId === -1 || instanceId > parent.geometry.drawRange.count) return;
const threshold = (_raycaster$params$Poi = (_raycaster$params$Poi2 = raycaster.params.Points) == null ? void 0 : _raycaster$params$Poi2.threshold) !== null && _raycaster$params$Poi !== void 0 ? _raycaster$params$Poi : 1;
_sphere.set(this.getWorldPosition(_position), threshold);
if (raycaster.ray.intersectsSphere(_sphere) === false) return;
_inverseMatrix.copy(parent.matrixWorld).invert();
_ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix);
const localThreshold = threshold / ((this.scale.x + this.scale.y + this.scale.z) / 3);
const localThresholdSq = localThreshold * localThreshold;
const rayPointDistanceSq = _ray.distanceSqToPoint(this.position);
if (rayPointDistanceSq < localThresholdSq) {
const intersectPoint = new THREE.Vector3();
_ray.closestPointToPoint(this.position, intersectPoint);
intersectPoint.applyMatrix4(this.matrixWorld);
const distance = raycaster.ray.origin.distanceTo(intersectPoint);
if (distance < raycaster.near || distance > raycaster.far) return;
intersects.push({
distance: distance,
distanceToRay: Math.sqrt(rayPointDistanceSq),
point: intersectPoint,
index: instanceId,
face: null,
object: this
});
}
}
}
let i, positionRef;
const context = /* @__PURE__ */React.createContext(null);
const parentMatrix = /* @__PURE__ */new THREE.Matrix4();
const position = /* @__PURE__ */new THREE.Vector3();
/**
* Instance implementation, relies on react + context to update the attributes based on the children of this component
*/
const PointsInstances = /* @__PURE__ */React.forwardRef(({
children,
range,
limit = 1000,
...props
}, ref) => {
const parentRef = React.useRef(null);
React.useImperativeHandle(ref, () => parentRef.current, []);
const [refs, setRefs] = React.useState([]);
const [[positions, colors, sizes]] = React.useState(() => [new Float32Array(limit * 3), Float32Array.from({
length: limit * 3
}, () => 1), Float32Array.from({
length: limit
}, () => 1)]);
React.useEffect(() => {
// We might be a frame too late? 🤷♂️
parentRef.current.geometry.attributes.position.needsUpdate = true;
});
useFrame(() => {
parentRef.current.updateMatrix();
parentRef.current.updateMatrixWorld();
parentMatrix.copy(parentRef.current.matrixWorld).invert();
parentRef.current.geometry.drawRange.count = Math.min(limit, range !== undefined ? range : limit, refs.length);
for (i = 0; i < refs.length; i++) {
positionRef = refs[i].current;
positionRef.getWorldPosition(position).applyMatrix4(parentMatrix);
position.toArray(positions, i * 3);
parentRef.current.geometry.attributes.position.needsUpdate = true;
positionRef.matrixWorldNeedsUpdate = true;
positionRef.color.toArray(colors, i * 3);
parentRef.current.geometry.attributes.color.needsUpdate = true;
sizes.set([positionRef.size], i);
parentRef.current.geometry.attributes.size.needsUpdate = true;
}
});
const api = React.useMemo(() => ({
getParent: () => parentRef,
subscribe: ref => {
setRefs(refs => [...refs, ref]);
return () => setRefs(refs => refs.filter(item => item.current !== ref.current));
}
}), []);
return /*#__PURE__*/React.createElement("points", _extends({
userData: {
instances: refs
},
matrixAutoUpdate: false,
ref: parentRef,
raycast: () => null
}, props), /*#__PURE__*/React.createElement("bufferGeometry", null, /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-position",
args: [positions, 3],
usage: THREE.DynamicDrawUsage
}), /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-color",
args: [colors, 3],
usage: THREE.DynamicDrawUsage
}), /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-size",
args: [sizes, 1],
usage: THREE.DynamicDrawUsage
})), /*#__PURE__*/React.createElement(context.Provider, {
value: api
}, children));
});
const Point = /* @__PURE__ */React.forwardRef(({
children,
...props
}, ref) => {
React.useMemo(() => extend({
PositionPoint
}), []);
const group = React.useRef(null);
React.useImperativeHandle(ref, () => group.current, []);
const {
subscribe,
getParent
} = React.useContext(context);
React.useLayoutEffect(() => subscribe(group), []);
return /*#__PURE__*/React.createElement("positionPoint", _extends({
instance: getParent(),
instanceKey: group,
ref: group
}, props), children);
});
/**
* Buffer implementation, relies on complete buffers of the correct number, leaves it to the user to update them
*/
const PointsBuffer = /* @__PURE__ */React.forwardRef(({
children,
positions,
colors,
sizes,
stride = 3,
...props
}, forwardedRef) => {
const pointsRef = React.useRef(null);
React.useImperativeHandle(forwardedRef, () => pointsRef.current, []);
useFrame(() => {
const attr = pointsRef.current.geometry.attributes;
attr.position.needsUpdate = true;
if (colors) attr.color.needsUpdate = true;
if (sizes) attr.size.needsUpdate = true;
});
return /*#__PURE__*/React.createElement("points", _extends({
ref: pointsRef
}, props), /*#__PURE__*/React.createElement("bufferGeometry", null, /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-position",
args: [positions, stride],
usage: THREE.DynamicDrawUsage
}), colors && /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-color",
args: [colors, stride],
count: colors.length / stride,
usage: THREE.DynamicDrawUsage
}), sizes && /*#__PURE__*/React.createElement("bufferAttribute", {
attach: "attributes-size",
args: [sizes, 1],
count: sizes.length / stride,
usage: THREE.DynamicDrawUsage
})), children);
});
const Points = /* @__PURE__ */React.forwardRef((props, forwardedRef) => {
if (props.positions instanceof Float32Array) {
return /*#__PURE__*/React.createElement(PointsBuffer, _extends({}, props, {
ref: forwardedRef
}));
} else return /*#__PURE__*/React.createElement(PointsInstances, _extends({}, props, {
ref: forwardedRef
}));
});
export { Point, Points, PointsBuffer, PositionPoint };