232 lines
8.8 KiB
JavaScript
232 lines
8.8 KiB
JavaScript
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
import * as React from 'react';
|
|
import { REVISION, Quaternion, Vector3, DynamicDrawUsage, MeshLambertMaterial, Matrix4, Color } from 'three';
|
|
import { extend, useFrame, applyProps } from '@react-three/fiber';
|
|
import { useTexture } from './Texture.js';
|
|
import { setUpdateRange } from '../helpers/deprecated.js';
|
|
|
|
const CLOUD_URL = 'https://rawcdn.githack.com/pmndrs/drei-assets/9225a9f1fbd449d9411125c2f419b843d0308c9f/cloud.png';
|
|
const parentMatrix = /* @__PURE__ */new Matrix4();
|
|
const translation = /* @__PURE__ */new Vector3();
|
|
const rotation = /* @__PURE__ */new Quaternion();
|
|
const cpos = /* @__PURE__ */new Vector3();
|
|
const cquat = /* @__PURE__ */new Quaternion();
|
|
const scale = /* @__PURE__ */new Vector3();
|
|
const context = /* @__PURE__ */React.createContext(null);
|
|
const Clouds = /* @__PURE__ */React.forwardRef(({
|
|
children,
|
|
material = MeshLambertMaterial,
|
|
texture = CLOUD_URL,
|
|
range,
|
|
limit = 200,
|
|
frustumCulled,
|
|
...props
|
|
}, fref) => {
|
|
var _image$width, _image$height;
|
|
const CloudMaterial = React.useMemo(() => {
|
|
return class extends material {
|
|
constructor() {
|
|
super();
|
|
const opaque_fragment = parseInt(REVISION.replace(/\D+/g, '')) >= 154 ? 'opaque_fragment' : 'output_fragment';
|
|
this.onBeforeCompile = shader => {
|
|
shader.vertexShader = `attribute float cloudOpacity;
|
|
varying float vOpacity;
|
|
` + shader.vertexShader.replace('#include <fog_vertex>', `#include <fog_vertex>
|
|
vOpacity = cloudOpacity;
|
|
`);
|
|
shader.fragmentShader = `varying float vOpacity;
|
|
` + shader.fragmentShader.replace(`#include <${opaque_fragment}>`, `#include <${opaque_fragment}>
|
|
gl_FragColor = vec4(outgoingLight, diffuseColor.a * vOpacity);
|
|
`);
|
|
};
|
|
}
|
|
};
|
|
}, [material]);
|
|
extend({
|
|
CloudMaterial
|
|
});
|
|
const instance = React.useRef(null);
|
|
const clouds = React.useRef([]);
|
|
const opacities = React.useMemo(() => new Float32Array(Array.from({
|
|
length: limit
|
|
}, () => 1)), [limit]);
|
|
const colors = React.useMemo(() => new Float32Array(Array.from({
|
|
length: limit
|
|
}, () => [1, 1, 1]).flat()), [limit]);
|
|
const cloudTexture = useTexture(texture);
|
|
let t = 0;
|
|
let index = 0;
|
|
let config;
|
|
const qat = new Quaternion();
|
|
const dir = new Vector3(0, 0, 1);
|
|
const pos = new Vector3();
|
|
useFrame((state, delta) => {
|
|
t = state.clock.elapsedTime;
|
|
parentMatrix.copy(instance.current.matrixWorld).invert();
|
|
state.camera.matrixWorld.decompose(cpos, cquat, scale);
|
|
for (index = 0; index < clouds.current.length; index++) {
|
|
config = clouds.current[index];
|
|
config.ref.current.matrixWorld.decompose(translation, rotation, scale);
|
|
translation.add(pos.copy(config.position).applyQuaternion(rotation).multiply(scale));
|
|
rotation.copy(cquat).multiply(qat.setFromAxisAngle(dir, config.rotation += delta * config.rotationFactor));
|
|
scale.multiplyScalar(config.volume + (1 + Math.sin(t * config.density * config.speed)) / 2 * config.growth);
|
|
config.matrix.compose(translation, rotation, scale).premultiply(parentMatrix);
|
|
config.dist = translation.distanceTo(cpos);
|
|
}
|
|
|
|
// Depth-sort. Instances have no specific draw order, w/o sorting z would be random
|
|
clouds.current.sort((a, b) => b.dist - a.dist);
|
|
for (index = 0; index < clouds.current.length; index++) {
|
|
config = clouds.current[index];
|
|
opacities[index] = config.opacity * (config.dist < config.fade - 1 ? config.dist / config.fade : 1);
|
|
instance.current.setMatrixAt(index, config.matrix);
|
|
instance.current.setColorAt(index, config.color);
|
|
}
|
|
|
|
// Update instance
|
|
instance.current.geometry.attributes.cloudOpacity.needsUpdate = true;
|
|
instance.current.instanceMatrix.needsUpdate = true;
|
|
if (instance.current.instanceColor) instance.current.instanceColor.needsUpdate = true;
|
|
});
|
|
React.useLayoutEffect(() => {
|
|
const count = Math.min(limit, range !== undefined ? range : limit, clouds.current.length);
|
|
instance.current.count = count;
|
|
setUpdateRange(instance.current.instanceMatrix, {
|
|
start: 0,
|
|
count: count * 16
|
|
});
|
|
if (instance.current.instanceColor) {
|
|
setUpdateRange(instance.current.instanceColor, {
|
|
start: 0,
|
|
count: count * 3
|
|
});
|
|
}
|
|
setUpdateRange(instance.current.geometry.attributes.cloudOpacity, {
|
|
start: 0,
|
|
count: count
|
|
});
|
|
});
|
|
let imageBounds = [(_image$width = cloudTexture.image.width) !== null && _image$width !== void 0 ? _image$width : 1, (_image$height = cloudTexture.image.height) !== null && _image$height !== void 0 ? _image$height : 1];
|
|
const max = Math.max(imageBounds[0], imageBounds[1]);
|
|
imageBounds = [imageBounds[0] / max, imageBounds[1] / max];
|
|
return /*#__PURE__*/React.createElement("group", _extends({
|
|
ref: fref
|
|
}, props), /*#__PURE__*/React.createElement(context.Provider, {
|
|
value: clouds
|
|
}, children, /*#__PURE__*/React.createElement("instancedMesh", {
|
|
matrixAutoUpdate: false,
|
|
ref: instance,
|
|
args: [null, null, limit],
|
|
frustumCulled: frustumCulled
|
|
}, /*#__PURE__*/React.createElement("instancedBufferAttribute", {
|
|
usage: DynamicDrawUsage,
|
|
attach: "instanceColor",
|
|
args: [colors, 3]
|
|
}), /*#__PURE__*/React.createElement("planeGeometry", {
|
|
args: [...imageBounds]
|
|
}, /*#__PURE__*/React.createElement("instancedBufferAttribute", {
|
|
usage: DynamicDrawUsage,
|
|
attach: "attributes-cloudOpacity",
|
|
args: [opacities, 1]
|
|
})), /*#__PURE__*/React.createElement("cloudMaterial", {
|
|
key: material.name,
|
|
map: cloudTexture,
|
|
transparent: true,
|
|
depthWrite: false
|
|
}))));
|
|
});
|
|
const CloudInstance = /* @__PURE__ */React.forwardRef(({
|
|
opacity = 1,
|
|
speed = 0,
|
|
bounds = [5, 1, 1],
|
|
segments = 20,
|
|
color = '#ffffff',
|
|
fade = 10,
|
|
volume = 6,
|
|
smallestVolume = 0.25,
|
|
distribute = null,
|
|
growth = 4,
|
|
concentrate = 'inside',
|
|
seed = Math.random(),
|
|
...props
|
|
}, fref) => {
|
|
function random() {
|
|
const x = Math.sin(seed++) * 10000;
|
|
return x - Math.floor(x);
|
|
}
|
|
const parent = React.useContext(context);
|
|
const ref = React.useRef(null);
|
|
const uuid = React.useId();
|
|
const clouds = React.useMemo(() => {
|
|
return [...new Array(segments)].map((_, index) => ({
|
|
segments,
|
|
bounds: new Vector3(1, 1, 1),
|
|
position: new Vector3(),
|
|
uuid,
|
|
index,
|
|
ref,
|
|
dist: 0,
|
|
matrix: new Matrix4(),
|
|
color: new Color(),
|
|
rotation: index * (Math.PI / segments)
|
|
}));
|
|
}, [segments, uuid]);
|
|
React.useLayoutEffect(() => {
|
|
clouds.forEach((cloud, index) => {
|
|
applyProps(cloud, {
|
|
volume,
|
|
color,
|
|
speed,
|
|
growth,
|
|
opacity,
|
|
fade,
|
|
bounds,
|
|
density: Math.max(0.5, random()),
|
|
rotationFactor: Math.max(0.2, 0.5 * random()) * speed
|
|
});
|
|
// Only distribute randomly if there are multiple segments
|
|
|
|
const distributed = distribute == null ? void 0 : distribute(cloud, index);
|
|
if (distributed || segments > 1) {
|
|
var _distributed$point;
|
|
cloud.position.copy(cloud.bounds).multiply((_distributed$point = distributed == null ? void 0 : distributed.point) !== null && _distributed$point !== void 0 ? _distributed$point : {
|
|
x: random() * 2 - 1,
|
|
y: random() * 2 - 1,
|
|
z: random() * 2 - 1
|
|
});
|
|
}
|
|
const xDiff = Math.abs(cloud.position.x);
|
|
const yDiff = Math.abs(cloud.position.y);
|
|
const zDiff = Math.abs(cloud.position.z);
|
|
const max = Math.max(xDiff, yDiff, zDiff);
|
|
cloud.length = 1;
|
|
if (xDiff === max) cloud.length -= xDiff / cloud.bounds.x;
|
|
if (yDiff === max) cloud.length -= yDiff / cloud.bounds.y;
|
|
if (zDiff === max) cloud.length -= zDiff / cloud.bounds.z;
|
|
cloud.volume = ((distributed == null ? void 0 : distributed.volume) !== undefined ? distributed.volume : Math.max(Math.max(0, smallestVolume), concentrate === 'random' ? random() : concentrate === 'inside' ? cloud.length : 1 - cloud.length)) * volume;
|
|
});
|
|
}, [concentrate, bounds, fade, color, opacity, growth, volume, seed, segments, speed]);
|
|
React.useLayoutEffect(() => {
|
|
const temp = clouds;
|
|
parent.current = [...parent.current, ...temp];
|
|
return () => {
|
|
parent.current = parent.current.filter(item => item.uuid !== uuid);
|
|
};
|
|
}, [clouds]);
|
|
React.useImperativeHandle(fref, () => ref.current, []);
|
|
return /*#__PURE__*/React.createElement("group", _extends({
|
|
ref: ref
|
|
}, props));
|
|
});
|
|
const Cloud = /* @__PURE__ */React.forwardRef((props, fref) => {
|
|
const parent = React.useContext(context);
|
|
if (parent) return /*#__PURE__*/React.createElement(CloudInstance, _extends({
|
|
ref: fref
|
|
}, props));
|
|
return /*#__PURE__*/React.createElement(Clouds, null, /*#__PURE__*/React.createElement(CloudInstance, _extends({
|
|
ref: fref
|
|
}, props)));
|
|
});
|
|
|
|
export { Cloud, CloudInstance, Clouds };
|