Files
spark/examples/vfx-particle-simulation/main.js
T
2025-05-28 16:12:15 -07:00

204 lines
5.8 KiB
JavaScript

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);