@react-three/drei
Version:
useful add-ons for react-three-fiber
160 lines (151 loc) • 4.66 kB
JavaScript
import * as React from 'react';
import { useMemo, useCallback } from 'react';
import { useFrame } from '@react-three/fiber';
import { Texture } from 'three';
// smooth new sample (measurement) based on previous sample (current)
function smoothAverage(current, measurement, smoothing = 0.9) {
return measurement * smoothing + current * (1.0 - smoothing);
}
// default ease
const easeCircleOut = x => Math.sqrt(1 - Math.pow(x - 1, 2));
class TrailTextureImpl {
constructor({
size = 256,
maxAge = 750,
radius = 0.3,
intensity = 0.2,
interpolate = 0,
smoothing = 0,
minForce = 0.3,
blend = 'screen',
// source-over is canvas default. Others are slower
ease = easeCircleOut
} = {}) {
this.size = size;
this.maxAge = maxAge;
this.radius = radius;
this.intensity = intensity;
this.ease = ease;
this.interpolate = interpolate;
this.smoothing = smoothing;
this.minForce = minForce;
this.blend = blend;
this.trail = [];
this.force = 0;
this.initTexture();
}
initTexture() {
this.canvas = document.createElement('canvas');
this.canvas.width = this.canvas.height = this.size;
const ctx = this.canvas.getContext('2d');
if (ctx === null) {
throw new Error('2D not available');
}
this.ctx = ctx;
this.ctx.fillStyle = 'black';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.texture = new Texture(this.canvas);
this.canvas.id = 'touchTexture';
this.canvas.style.width = this.canvas.style.height = `${this.canvas.width}px`;
}
update(delta) {
this.clear();
// age points
this.trail.forEach((point, i) => {
point.age += delta * 1000;
// remove old
if (point.age > this.maxAge) {
this.trail.splice(i, 1);
}
});
// reset force when empty (when smoothing)
if (!this.trail.length) this.force = 0;
this.trail.forEach(point => {
this.drawTouch(point);
});
this.texture.needsUpdate = true;
}
clear() {
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.fillStyle = 'black';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
addTouch(point) {
const last = this.trail[this.trail.length - 1];
if (last) {
const dx = last.x - point.x;
const dy = last.y - point.y;
const dd = dx * dx + dy * dy;
const force = Math.max(this.minForce, Math.min(dd * 10000, 1));
this.force = smoothAverage(force, this.force, this.smoothing);
if (!!this.interpolate) {
const lines = Math.ceil(dd / Math.pow(this.radius * 0.5 / this.interpolate, 2));
if (lines > 1) {
for (let i = 1; i < lines; i++) {
this.trail.push({
x: last.x - dx / lines * i,
y: last.y - dy / lines * i,
age: 0,
force
});
}
}
}
}
this.trail.push({
x: point.x,
y: point.y,
age: 0,
force: this.force
});
}
drawTouch(point) {
const pos = {
x: point.x * this.size,
y: (1 - point.y) * this.size
};
let intensity = 1;
if (point.age < this.maxAge * 0.3) {
intensity = this.ease(point.age / (this.maxAge * 0.3));
} else {
intensity = this.ease(1 - (point.age - this.maxAge * 0.3) / (this.maxAge * 0.7));
}
intensity *= point.force;
// apply blending
this.ctx.globalCompositeOperation = this.blend;
const radius = this.size * this.radius * intensity;
const grd = this.ctx.createRadialGradient(pos.x, pos.y, Math.max(0, radius * 0.25), pos.x, pos.y, Math.max(0, radius));
grd.addColorStop(0, `rgba(255, 255, 255, ${this.intensity})`);
grd.addColorStop(1, `rgba(0, 0, 0, 0.0)`);
this.ctx.beginPath();
this.ctx.fillStyle = grd;
this.ctx.arc(pos.x, pos.y, Math.max(0, radius), 0, Math.PI * 2);
this.ctx.fill();
}
}
function useTrailTexture(config = {}) {
const {
size,
maxAge,
radius,
intensity,
interpolate,
smoothing,
minForce,
blend,
ease
} = config;
const trail = useMemo(() => new TrailTextureImpl(config), [size, maxAge, radius, intensity, interpolate, smoothing, minForce, blend, ease]);
useFrame((_, delta) => void trail.update(delta));
const onMove = useCallback(e => trail.addTouch(e.uv), [trail]);
return [trail.texture, onMove];
}
//
const TrailTexture = ({
children,
...config
}) => {
const ret = useTrailTexture(config);
return /*#__PURE__*/React.createElement(React.Fragment, null, children == null ? void 0 : children(ret));
};
export { TrailTexture, useTrailTexture };