diff --git a/.changeset/hot-laws-scream.md b/.changeset/hot-laws-scream.md new file mode 100644 index 000000000..cf7cecdfb --- /dev/null +++ b/.changeset/hot-laws-scream.md @@ -0,0 +1,5 @@ +--- +"vfx-composer": patch +--- + +**Added:** `` will now retrieve the parent `` via context if none is specified explicitly. diff --git a/apps/examples/src/examples/Simple.tsx b/apps/examples/src/examples/Simple.tsx index 799d682a1..04cee7107 100644 --- a/apps/examples/src/examples/Simple.tsx +++ b/apps/examples/src/examples/Simple.tsx @@ -1,22 +1,27 @@ +import { useTexture } from "@react-three/drei" import { between, plusMinus, random, upTo } from "randomish" import { useState } from "react" import { OneMinus, Time } from "shader-composer" -import { Color, MeshStandardMaterial, Vector2, Vector3 } from "three" +import { MeshStandardMaterial, Vector2, Vector3 } from "three" import { Repeat } from "three-vfx" -import { makeParticles, VFX, VFXMaterial } from "vfx-composer/fiber" +import { + Emitter, + makeParticles, + Particles, + VFX, + VFXMaterial +} from "vfx-composer/fiber" import { Lifetime } from "vfx-composer/modules" import { ParticleAttribute } from "vfx-composer/units" - -const Effect = makeParticles() - -const FREQ = 8 +import { particleUrl } from "./textures" export const Simple = () => { + const texture = useTexture(particleUrl) + const [variables] = useState(() => ({ time: Time(), lifetime: ParticleAttribute(new Vector2()), - velocity: ParticleAttribute(new Vector3()), - color: ParticleAttribute(new Color()) + velocity: ParticleAttribute(new Vector3()) })) const { ParticleProgress, ParticleAge, module: lifetimeModule } = Lifetime( @@ -26,37 +31,42 @@ export const Simple = () => { return ( - + - + + - - - - - { - const t = variables.time.uniform.value - const { lifetime, velocity, color } = variables - /* Randomize the instance transform */ - position.randomDirection().multiplyScalar(upTo(6)) - rotation.random() + + { + /* Randomize the instance transform */ + position.randomDirection().multiplyScalar(upTo(1)) - /* Write values into the instanced attributes */ - const start = t + random() / FREQ - lifetime.value.set(start, start + between(1, 3)) - velocity.value.set(plusMinus(5), between(5, 18), plusMinus(5)) - color.value.setRGB(Math.random(), Math.random(), Math.random()) - }} - /> - + /* Write values into the instanced attributes */ + const t = variables.time.uniform.value + const start = t //+ random() + variables.lifetime.value.set(start, start + between(1, 3)) + variables.velocity.value.set( + plusMinus(5), + between(5, 18), + plusMinus(5) + ) + }} + /> + + ) } diff --git a/packages/vfx-composer/src/fiber/Emitter.tsx b/packages/vfx-composer/src/fiber/Emitter.tsx index 54ee0cd8b..1a23bffb9 100644 --- a/packages/vfx-composer/src/fiber/Emitter.tsx +++ b/packages/vfx-composer/src/fiber/Emitter.tsx @@ -9,26 +9,39 @@ import React, { } from "react" import { Object3D } from "three" import { InstanceSetupCallback, Particles } from "../Particles" +import { useParticlesContext } from "./Particles" export type EmitterProps = Object3DProps & { - particles: MutableRefObject | RefObject + particles?: MutableRefObject | RefObject count?: number continuous?: boolean setup?: InstanceSetupCallback } export const Emitter = forwardRef( - ({ particles, count = 1, continuous = false, setup, ...props }, ref) => { + ( + { + particles: particlesProp, + count = 1, + continuous = false, + setup, + ...props + }, + ref + ) => { const object = useRef(null!) + const particles = particlesProp?.current || useParticlesContext() useEffect(() => { + if (!particles) return if (continuous) return - particles.current?.emit(count, setup) + particles.emit(count, setup) }, [particles]) useFrame(() => { + if (!particles) return if (!continuous) return - particles.current?.emit(count, setup) + particles.emit(count, setup) }) useImperativeHandle(ref, () => object.current) diff --git a/packages/vfx-composer/src/fiber/Particles.tsx b/packages/vfx-composer/src/fiber/Particles.tsx index 4ad0cdd36..376411ba1 100644 --- a/packages/vfx-composer/src/fiber/Particles.tsx +++ b/packages/vfx-composer/src/fiber/Particles.tsx @@ -1,9 +1,12 @@ -import { extend, InstancedMeshProps, Node } from "@react-three/fiber" +import { extend, InstancedMeshProps } from "@react-three/fiber" import React, { + createContext, forwardRef, + useContext, useEffect, useImperativeHandle, - useRef + useRef, + useState } from "react" import { Particles as ParticlesImpl } from "../Particles" import { VFXMaterial as VFXMaterialImpl } from "../VFXMaterial" @@ -25,16 +28,33 @@ declare global { } } +const Context = createContext(null) + +export const useParticlesContext = () => useContext(Context) + export const Particles = forwardRef( ( - { maxParticles = 1000, safetyBuffer = 100, geometry, material, ...props }, + { + children, + maxParticles = 1000, + safetyBuffer = 100, + geometry, + material, + ...props + }, ref ) => { + const [_, setReady] = useState(false) const particles = useRef(null!) + /* + We need to initialize the particles mesh in an effect, because in most cases, + the material driving it will only be created in its children. + */ useEffect(() => { particles.current.setupParticles() - }, []) + setReady(true) + }, [particles]) useImperativeHandle(ref, () => particles.current) @@ -43,7 +63,11 @@ export const Particles = forwardRef( args={[geometry, material, maxParticles, safetyBuffer]} ref={particles} {...props} - /> + > + + {children} + + ) } ) diff --git a/packages/vfx-composer/src/modules.ts b/packages/vfx-composer/src/modules.ts index ff7923545..7bb6fa56c 100644 --- a/packages/vfx-composer/src/modules.ts +++ b/packages/vfx-composer/src/modules.ts @@ -22,7 +22,7 @@ export type ModuleState = { export type Module = (state: ModuleState) => ModuleState export type ModuleProps = Record -export type ModuleFactory

= (props: P) => Module +export type ModuleFactory

= (props: P) => Module export type ModulePipe = Module[] @@ -97,7 +97,7 @@ export const Acceleration = ({ force, time }: AccelerationProps) => ) }) -export const Billboard = (): Module => (state) => ({ +export const Billboard: ModuleFactory = () => (state) => ({ ...state, position: BillboardUnit(state.position) })