import React, { useRef, useMemo, useLayoutEffect } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import { Vector3, Object3D, Color, DoubleSide, Matrix4, InstancedMesh } from "three";
import { OrbitControls } from "@react-three/drei";
export default function Page() {
return (
<>
<Canvas shadows>
<Confetti />
<OrbitControls />
<ambientLight />
</Canvas>
</>
)
}
const randomRange = (min: number, max: number) => {
return Math.random() * (max - min) + min;
}
interface ConfettiProps {
length?: number; // 何個生成するか
positions?: [number, number, number] | Vector3; // どの範囲で生成するか
size?: number; // サイズ
rotationSpeed?: number; // 回転速度
}
const o = new Object3D();
const mat4 = new Matrix4();
const Confetti = (
{
length = 10000,
positions = [32, 32, 32],
size = 0.25,
rotationSpeed = 0.03
}: ConfettiProps
) => {
const ref = useRef<InstancedMesh>();
const c = new Color();
const colors = useMemo(() => {
const array = new Float32Array(length * 3);
// ランダムで色を指定
for (let i = 0; i < length; i++) {
c.setHSL(Math.random(), 1.0, 0.5);
c.toArray(array, i * 3);
}
return array;
}, [length]);
useLayoutEffect(() => {
let i = 0
const root = Math.round(Math.pow(length, 1 / 3))
let poss = positions;
if (poss instanceof Vector3) {
poss = [poss.x, poss.y, poss.z];
}
for (let x = 0; x < root; x++)
for (let y = 0; y < root; y++)
for (let z = 0; z < root; z++) {
const id = i++
o.rotation.set(
Math.PI * randomRange(-1, 1),
Math.PI * randomRange(-1, 1),
Math.PI * randomRange(-1, 1)
)
o.position.set(
poss[0] * randomRange(-1, 1),
poss[1] * randomRange(-1, 1),
poss[2] * randomRange(-1, 1)
)
o.updateMatrix()
ref.current.setMatrixAt(id, o.matrix)
}
ref.current.instanceMatrix.needsUpdate = true
}, [length])
// 毎フレーム事に回転処理
useFrame(({ clock }) => {
if (!ref.current) return;
const time = clock.getElapsedTime();
// ランダムで回転させる
for (let i = 0; i < length; i++) {
ref.current.getMatrixAt(i, mat4);
mat4.decompose(o.position, o.quaternion, o.scale);
o.rotation.x += Math.cos(time) * rotationSpeed;
o.rotation.y += Math.sin(time) * rotationSpeed;
o.rotation.z += Math.cos(time) * rotationSpeed;
o.updateMatrix();
ref.current.setMatrixAt(i, o.matrix);
}
// 更新
ref.current.instanceMatrix.needsUpdate = true;
});
return (
<>
<group>
<instancedMesh ref={ref} args={[undefined, undefined, length]}>
<planeGeometry args={[size, size]}>
<instancedBufferAttribute attach={"attributes-color"} args={[colors, 3]} />
</planeGeometry>
<meshStandardMaterial vertexColors={true} side={DoubleSide} />
</instancedMesh>
</group>
</>
)
}