diff --git a/examples/vfx-particle-simulation/index.html b/examples/vfx-particle-simulation/index.html index 38cbbd3..eaaa339 100644 --- a/examples/vfx-particle-simulation/index.html +++ b/examples/vfx-particle-simulation/index.html @@ -2,15 +2,25 @@ - Forge • Hello World + Forge • VFX Particle Simulation + - + diff --git a/examples/vfx-particle-simulation/main.js b/examples/vfx-particle-simulation/main.js new file mode 100644 index 0000000..e643532 --- /dev/null +++ b/examples/vfx-particle-simulation/main.js @@ -0,0 +1,203 @@ +import { + ForgeRenderer, + PointerControls, + SplatMesh, + VRButton, + generators, +} from "@forge-gfx/forge"; +import GUI from "lil-gui"; +import * as THREE from "three"; + +export const SNOW_HEIGHT = 8; + +async function main() { + const canvas = document.getElementById("canvas"); + const renderer = new THREE.WebGLRenderer({ canvas }); + const scene = new THREE.Scene(); + + const forge = new ForgeRenderer({ renderer }); + scene.add(forge); + + const camera = new THREE.PerspectiveCamera( + 75, + canvas.width / canvas.height, + 0.1, + 1000, + ); + camera.position.set(-1, 12, 100); + camera.rotateY((3 * Math.PI) / 2); + scene.add(camera); + + const pointerControls = new PointerControls({ canvas }); + + function handleResize() { + const width = canvas.clientWidth; + const height = canvas.clientHeight; + renderer.setSize(width, height, false); + camera.aspect = width / height; + camera.updateProjectionMatrix(); + } + + handleResize(); + window.addEventListener("resize", handleResize); + + const splatURL = + "https://asundqui-public.s3.us-west-2.amazonaws.com/gsplats/snow_street.spz"; + // "./snow_street_crop8.ply"; + const background = new SplatMesh({ url: splatURL }); + background.quaternion.set(1, 0, 0, 0); + scene.add(background); + + const halfWidth = 25; + const halfDepth = 50; + const snowMinYClamp = -0.7; + const cam = camera.position; + + const snowVolumeBox = new THREE.Box3( + new THREE.Vector3( + cam.x - halfWidth, + cam.y - SNOW_HEIGHT / 1.5, + cam.z - halfDepth, + ), + new THREE.Vector3( + cam.x + halfWidth, + cam.y + SNOW_HEIGHT * 2, + cam.z + halfDepth, + ), + ); + + const snow = generators.snowBox({ + ...generators.DEFAULT_SNOW, + box: snowVolumeBox.clone(), + minY: snowMinYClamp, + color1: new THREE.Color(1.0, 1.0, 1.0), + color2: new THREE.Color(0.91, 0.9, 0.97), + density: 1000, + maxScale: 0.01, + }); + scene.add(snow.snow); + + const gui = new GUI({ title: "Clouds" }); + const controls = { + fallVelocity: generators.DEFAULT_SNOW.fallVelocity, + wanderScale: generators.DEFAULT_SNOW.wanderScale, + wanderVariance: generators.DEFAULT_SNOW.wanderVariance, + maxScale: 0.01, + color1: new THREE.Color(1.0, 1.0, 1.0), + color2: new THREE.Color(0.91, 0.9, 0.97), + isPaused: false, + fallDirection: new THREE.Vector3(0, -1, 0), + windDirection: "No Direction", + _previousFallVelocity: generators.DEFAULT_SNOW.fallVelocity, + _previousWanderScale: generators.DEFAULT_SNOW.wanderScale, + _previousWanderVariance: generators.DEFAULT_SNOW.wanderVariance, + }; + + // snow parameters + gui.add(controls, "fallVelocity", 0.01, 0.5, 0.01).name("Fall Velocity"); + gui.add(controls, "wanderScale", 0.01, 1, 0.01).name("Wander Scale"); + gui.add(controls, "wanderVariance", 1, 5, 0.1).name("Wander Variance"); + gui.add(controls, "maxScale", 0.001, 0.05, 0.001).name("Snow Size"); + gui + .add(controls, "isPaused") + .name("Pause Snowfall") + .onChange((value) => { + if (value) { + controls._previousFallVelocity = controls.fallVelocity; + controls._previousWanderScale = controls.wanderScale; + controls._previousWanderVariance = controls.wanderVariance; + + snow.fallVelocity.value = 0; + snow.wanderScale.value = 0; + snow.wanderVariance.value = 0; + } else { + snow.fallVelocity.value = controls._previousFallVelocity; + snow.wanderScale.value = controls._previousWanderScale; + snow.wanderVariance.value = controls._previousWanderVariance; + } + }); + + // color + const colorFolder = gui.addFolder("Snow Colors"); + colorFolder + .addColor(controls, "color1") + .onChange((value) => { + snow.color1.value.set(value.r, value.g, value.b); + }) + .name("Color 1"); + colorFolder + .addColor(controls, "color2") + .onChange((value) => { + snow.color2.value.set(value.r, value.g, value.b); + }) + .name("Color 2"); + + const directionFolder = gui.addFolder("Wind Orientation"); + directionFolder + .add(controls.fallDirection, "x", -1, 1, 0.1) + .onChange(() => { + controls.fallDirection.normalize(); + snow.fallDirection.value.copy(controls.fallDirection); + }) + .name("East-West"); + directionFolder + .add(controls.fallDirection, "z", -1, 1, 0.1) + .onChange(() => { + controls.fallDirection.normalize(); + snow.fallDirection.value.copy(controls.fallDirection); + }) + .name("North-South"); + + const windFolder = gui.addFolder("Wind Direction Presets"); + windFolder + .add(controls, "windDirection", [ + "N", + "NE", + "E", + "SE", + "No Direction", + "S", + "SW", + "W", + "NW", + ]) + .onChange((value) => { + const directions = { + N: new THREE.Vector3(0, -1, -1), + NE: new THREE.Vector3(1, -1, -1), + E: new THREE.Vector3(1, -1, 0), + SE: new THREE.Vector3(1, -1, 1), + "No Direction": new THREE.Vector3(0, -1, 0), + S: new THREE.Vector3(0, -1, 1), + SW: new THREE.Vector3(-1, -1, 1), + W: new THREE.Vector3(-1, -1, 0), + NW: new THREE.Vector3(-1, -1, -1), + }; + controls.fallDirection.copy(directions[value]).normalize(); + snow.fallDirection.value.copy(controls.fallDirection); + }) + .name("Wind Direction"); + + const vrButton = VRButton.createButton(renderer); + if (vrButton) document.body.appendChild(vrButton); + + let lastTime; + renderer.setAnimationLoop((time) => { + const t = time * 0.001; + const dt = t - (lastTime ?? t); + lastTime = t; + + pointerControls.update(dt, camera); + + if (!controls.isPaused) { + snow.fallVelocity.value = controls.fallVelocity; + snow.wanderScale.value = controls.wanderScale; + snow.wanderVariance.value = controls.wanderVariance; + snow.maxScale.value = controls.maxScale; + } + + renderer.render(scene, camera); + }); +} + +main().catch(console.error); diff --git a/index.html b/index.html index 34158cf..81b6889 100644 --- a/index.html +++ b/index.html @@ -132,6 +132,7 @@

Examples