203 lines
7.4 KiB
JavaScript
203 lines
7.4 KiB
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 };
|