mirror of
https://github.com/storytold/spark.git
synced 2026-10-09 00:09:53 +00:00
refactor particle simulation into one file
This commit is contained in:
committed by
Diego Marcos
parent
8b8db6c7e2
commit
854b6b7e0b
@@ -1,26 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Forge • VFX Particle Simulation</title>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
@@ -29,7 +21,224 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="main.js"></script>
|
||||
<script type="module">
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
ForgeRenderer,
|
||||
PointerControls,
|
||||
SplatMesh,
|
||||
VRButton,
|
||||
generators,
|
||||
} from "@forge-gfx/forge";
|
||||
import GUI from "lil-gui";
|
||||
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
|
||||
|
||||
const SCENE_CONFIG = {
|
||||
SNOW_HEIGHT: 8,
|
||||
HALF_WIDTH: 25,
|
||||
HALF_DEPTH: 50,
|
||||
SNOW_MIN_Y: -0.7,
|
||||
CAMERA_DURATION: 90,
|
||||
};
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
camera.position.set(-6, 12, 100);
|
||||
camera.rotateY((3 * Math.PI) / 2);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const forge = new ForgeRenderer({ renderer });
|
||||
scene.add(forge);
|
||||
|
||||
function handleResize() {
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
renderer.setSize(width, height, false);
|
||||
camera.aspect = width / height;
|
||||
camera.updateProjectionMatrix();
|
||||
}
|
||||
|
||||
handleResize();
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
// const backgroundURL = await getAssetFileURL("snow-street.spz");
|
||||
const backgroundURL =
|
||||
"./snow_street_crop37.spz";
|
||||
const background = new SplatMesh({ url: backgroundURL });
|
||||
background.quaternion.set(1, 0, 0, 0);
|
||||
scene.add(background);
|
||||
await background.initialized;
|
||||
|
||||
const controls = new PointerControls({ canvas: renderer.domElement });
|
||||
|
||||
const cam = camera.position;
|
||||
const snowVolumeBox = new THREE.Box3(
|
||||
new THREE.Vector3(
|
||||
cam.x - SCENE_CONFIG.HALF_WIDTH,
|
||||
cam.y - SCENE_CONFIG.SNOW_HEIGHT / 1.5,
|
||||
cam.z - SCENE_CONFIG.HALF_DEPTH,
|
||||
),
|
||||
new THREE.Vector3(
|
||||
cam.x + SCENE_CONFIG.HALF_WIDTH,
|
||||
cam.y + SCENE_CONFIG.SNOW_HEIGHT * 2,
|
||||
cam.z + SCENE_CONFIG.HALF_DEPTH,
|
||||
),
|
||||
);
|
||||
|
||||
const snow = generators.snowBox({
|
||||
...generators.DEFAULT_SNOW,
|
||||
box: snowVolumeBox.clone(),
|
||||
minY: SCENE_CONFIG.SNOW_MIN_Y,
|
||||
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: "Snow Particle Simulation" });
|
||||
const params = {
|
||||
fallVelocity: 0.1,
|
||||
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 parameter controls
|
||||
gui.add(params, "fallVelocity", 0.01, 0.5, 0.01).name("Fall Velocity");
|
||||
gui.add(params, "wanderScale", 0.01, 1, 0.01).name("Wander Scale");
|
||||
gui.add(params, "wanderVariance", 1, 5, 0.1).name("Wander Variance");
|
||||
gui.add(params, "maxScale", 0.001, 0.05, 0.001).name("Snow Size");
|
||||
gui
|
||||
.add(params, "isPaused")
|
||||
.name("Pause Snowfall")
|
||||
.onChange((value) => {
|
||||
if (value) {
|
||||
params._previousFallVelocity = params.fallVelocity;
|
||||
params._previousWanderScale = params.wanderScale;
|
||||
params._previousWanderVariance = params.wanderVariance;
|
||||
|
||||
snow.fallVelocity.value = 0;
|
||||
snow.wanderScale.value = 0;
|
||||
snow.wanderVariance.value = 0;
|
||||
} else {
|
||||
snow.fallVelocity.value = params._previousFallVelocity;
|
||||
snow.wanderScale.value = params._previousWanderScale;
|
||||
snow.wanderVariance.value = params._previousWanderVariance;
|
||||
}
|
||||
});
|
||||
|
||||
// color
|
||||
const colorFolder = gui.addFolder("Snow Colors");
|
||||
colorFolder
|
||||
.addColor(params, "color1")
|
||||
.onChange((value) => {
|
||||
snow.color1.value.set(value.r, value.g, value.b);
|
||||
})
|
||||
.name("Color 1");
|
||||
colorFolder
|
||||
.addColor(params, "color2")
|
||||
.onChange((value) => {
|
||||
snow.color2.value.set(value.r, value.g, value.b);
|
||||
})
|
||||
.name("Color 2");
|
||||
|
||||
// wind
|
||||
const directionFolder = gui.addFolder("Wind Orientation");
|
||||
directionFolder
|
||||
.add(params.fallDirection, "x", -1, 1, 0.1)
|
||||
.onChange(() => {
|
||||
params.fallDirection.normalize();
|
||||
snow.fallDirection.value.copy(params.fallDirection);
|
||||
})
|
||||
.name("East-West");
|
||||
directionFolder
|
||||
.add(params.fallDirection, "z", -1, 1, 0.1)
|
||||
.onChange(() => {
|
||||
params.fallDirection.normalize();
|
||||
snow.fallDirection.value.copy(params.fallDirection);
|
||||
})
|
||||
.name("North-South");
|
||||
|
||||
const windFolder = gui.addFolder("Wind Direction Presets");
|
||||
windFolder
|
||||
.add(params, "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),
|
||||
};
|
||||
params.fallDirection.copy(directions[value]).normalize();
|
||||
snow.fallDirection.value.copy(params.fallDirection);
|
||||
})
|
||||
.name("Wind Direction");
|
||||
|
||||
const vrButton = VRButton.createButton(renderer);
|
||||
if (vrButton) document.body.appendChild(vrButton);
|
||||
|
||||
let lastTime;
|
||||
renderer.setAnimationLoop(function animate(time) {
|
||||
const t = time * 0.001;
|
||||
const dt = t - (lastTime ?? t);
|
||||
lastTime = t;
|
||||
|
||||
controls.update(dt, camera);
|
||||
|
||||
// camera movement
|
||||
const targetX = 0.5;
|
||||
const startX = -6;
|
||||
const duration = 90;
|
||||
const progress = Math.min(1, t / duration);
|
||||
const smoothProgress = Math.sin(progress * Math.PI * 0.5);
|
||||
camera.position.x = startX + (targetX - startX) * smoothProgress;
|
||||
|
||||
if (!params.isPaused) {
|
||||
snow.fallVelocity.value = params.fallVelocity;
|
||||
snow.wanderScale.value = params.wanderScale;
|
||||
snow.wanderVariance.value = params.wanderVariance;
|
||||
snow.maxScale.value = params.maxScale;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
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);
|
||||
Reference in New Issue
Block a user