# Procedural Splats Forge makes it easy to create Gaussian splat collections procedurally, and includes some splat constructors that may be useful for tasks like creating a grid or text made of splats. The example "Procedural Splats" puts some of these to use in a scene. ## Adding splats to a collection To create a `PackedSplats` with custom, procedurally-derived splats use the methods `pushSplat` or `setSplat`: ```javascript const splats = new PackedSplats(); const center = new THREE.Vector3(0, 0, 0); const scales = new THREE.Vector3(0.1, 0.1, 0.1); const quaternion = new THREE.Quaternion(); const opacity = 1.0; const color = new THREE.Color() splats.pushSplat(center, scales, quaternion, opacity, color); ... ``` The array in `PackedSplats` will be resized automatically to fit any splats you add. Alternatively, you can use the `construct` initializer callback: ```javascript const splats = new PackedSplats({ construct: (splats) => { ... for (let i = 0; i < NUM_SPLATS; ++i) { // Compute splat #i ... splats.pushSplat(center, scales, quaternion, opacity, color); } }, }); ``` Once you've created your `PackedSplats`, render it to the scene via a `SplatMesh`: ```javascript const mesh = new SplatMesh({ packedSplats: splats }); scene.add(mesh); ``` Alternatively, you can create the `SplatMesh` and its splats in the initializer, which internally passes your constructor to its contained `PackedSplats`: ```javascript const mesh = new SplatMesh({ constructSplats: (splats) => { for (let i = 0; i < NUM_SPLATS; ++i) { // Compute splat #i ... splats.pushSplat(center, scales, quaternion, opacity, color); } }, }); scene.add(mesh); ``` ## Grid ```javascript import { constructGrid } from "@worldlabsai/forge"; const grid = new SplatMesh({ constructSplats: (splats) => constructGrid({ splats, extents: new THREE.Box3( new THREE.Vector3(-10, -10, -10), new THREE.Vector3(10, 10, 10), ), }), }); scene.add(grid); ``` ### Required parameters | Parameter | Description | |-----------|-------------| | `splats` | PackedSplats object to add splats to | | `extents` | min and max box extents of the grid | ### Optional parameters | Parameter | Default | Description | |-----------|---------|-------------| | `stepSize` | `1` | step size along each grid axis | | `pointRadius` | `0.01` | spherical radius of each Gsplat | | `pointShadowScale` | `2.0` | relative size of the "shadow copy" of each Gsplat placed behind it | | `opacity` | `1.0` | Gsplat opacity | | `color` | RGB-modulated grid | Gsplat color (THREE.Color) or function to set color for position: ((THREE.Color, THREE.Vector3) => void) | ## XYZ axis ```javascript import { constructAxes } from "@worldlabsai/forge"; const axes = new SplatMesh({ constructSplats: (splats) => constructAxes({ splats }), }); axes.position.set(0, 0, -1); scene.add(axes); ``` ### Required parameters | Parameter | Description | |-----------|-------------| | `splats` | PackedSplats object to add splats to | ### Optional parameters | Parameter | Default | Description | |-----------|---------|-------------| | `scale` | `0.25` | scale (Gsplat scale along axis) | | `axisRadius` | `0.0075` | radius of the axes (Gsplat scale orthogonal to axis) | | `axisShadowScale` | `2.0` | relative size of the "shadow copy" of each Gsplat placed behind it | | `origins` | `[new THREE.Vector3()]` | origins of the axes (default single axis at origin) | ## Gsplat sphere ```javascript import { constructSpherePoints } from "@worldlabsai/forge"; const sphere = new SplatMesh({ constructSplats: (splats) => constructSpherePoints({ splats, maxDepth: 4, }), }); scene.add(sphere); ``` ### Required parameters | Parameter | Description | |-----------|-------------| | `splats` | PackedSplats object to add splats to | ### Optional parameters | Parameter | Default | Description | |-----------|---------|-------------| | `origin` | `new THREE.Vector3()` | center of the sphere (default: origin) | | `radius` | `1.0` | radius of the sphere | | `maxDepth` | `3` | maximum depth of recursion for subdividing the sphere. Warning: Gsplat count grows exponentially with depth | | `filter` | `null` | filter function to apply to each point, for example to select points in a certain direction or other function ((THREE.Vector3) => boolean) | | `pointRadius` | `0.02` | radius of each oriented Gsplat | | `pointThickness` | `0.001` | flatness of each oriented Gsplat | | `color` | `new THREE.Color(1, 1, 1)` | color of each Gsplat (THREE.Color) or function to set color for point: ((THREE.Color, THREE.Vector3) => void) | ## Rasterizing Text ```typescript const splats = textSplats({ text: string; font?: string; fontSize?: number; color?: THREE.Color; rgb?: THREE.Color; dotRadius?: number; textAlign?: "left" | "center" | "right" | "start" | "end"; lineHeight?: number; }); scene.add(splats); ``` ### Required parameters | Parameter | Description | |-----------|-------------| | `text` | text string to display | ### Optional parameters | Parameter | Default | Description | |-----------|---------|-------------| | `font` | `"Arial"` | browser font to render text with | | `fontSize` | `32` | font size in pixels/Gsplats | | `color` | `new THREE.Color(1, 1, 1)` | SplatMesh.recolor tint assuming white Gsplats | | `rgb` | `new THREE.Color(1, 1, 1)` | Individual Gsplat color | | `dotRadius` | `0.8` | Gsplat radius (0.8 covers 1-unit spacing well) | | `textAlign` | `"start"` | text alignment: "left", "center", "right", "start", "end" | | `lineHeight` | `1.0` | line spacing multiplier, lines delimited by "\n" | ## Turning images into Gsplats ```typescript const image = imageSplats({ url: string; dotRadius?: number; subXY?: number; forEachSplat?: ( width: number, height: number, index: number, center: THREE.Vector3, scales: THREE.Vector3, quaternion: THREE.Quaternion, opacity: number, color: THREE.Color, ) => number | null; }); scene.add(image); ``` ### Required parameters | Parameter | Description | |-----------|-------------| | `url` | URL of the image to convert to splats (example: `url: "./image.png"`) | ### Optional parameters | Parameter | Default | Description | |-----------|---------|-------------| | `dotRadius` | `0.8` | Radius of each Gsplat, default covers 1-unit spacing well | | `subXY` | `1` | Subsampling factor for the image. Higher values reduce resolution, for example 2 will halve the width and height by averaging | | `forEachSplat` | `undefined` | Optional callback function to modify each Gsplat before it's added. Return null to skip adding the Gsplat, or a number to set the opacity and add the Gsplat with parameter values in the objects center, rgba etc. were passed into the forEachSplat callback. Ending the callback in `return opacity;` will retain the original opacity. | ### Example ```javascript // Load RGBA image from image.png, subsample it 2x // horizontally and vertically, and create Gsplats for // the resulting pixels that have at least 10% opacity. const image = imageSplats({ url: "./image.png", subXY: 2, forEachSplat: (width, height, index, center, scales, quaternion, opacity, color) => { // Only keep Gsplats with opacity 10% or higher return (opacity >= 0.1) ? opacity : null; }, }); scene.add(image); ``` ## Particle Effects Some of the building blocks are meant to serve as "code inspiration", showing how a particle effect animation can be achieved using a "stateless" `dyno` computation graph that uses only the Gsplat `index` input to produce pseudo-random numbers that drive various randomized particle effects. Note that Gsplat sorting takes a little bit of time and can "lag behind" the Gsplat updates each frame, so it's important that there is a reasonably stable correspondence between each frame's Gsplat output for the same `index`. In `staticBox`, the AABB is sliced up into a 3D grid of cells, and random X/Y/Z points within those cells are sampled each frame, retaining consistency in Gsplat position between successive frames. See `examples/gsplat-sweets-garden/main.js` for an example that creates a `staticBox`. ### `generators.snowBox(...options)` Similarly, `snowBox` produces Gsplat trajectories that move in a deterministic fashion over time, with high similarity between adjacent frames. See `examples/atmospheric/main.js` for an example that creates a `snowBox`. A snowBox instance has a collection of properties that can be tuned to achieve different particle effects. DEFAULT_SNOW and DEFAULT_RAIN are example parameter sets that look a lot like snow and rain, and can be used as a starting point for further tweaking: `const mySnow = { ...DEFAULT_SNOW, density: 500 };` ```typescript const snowControls = generators.snowBox({ box, minY, numSplats, density, anisoScale, minScale, maxScale, fallDirection, fallVelocity, wanderScale, wanderVariance, color1, color2, opacity, onFrame, }); scene.add(snowControls.snow); ``` | Parameter | Default Value | Description | |-----------|---------------|-------------| | `box` | `new THREE.Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1))` | min and max box extents of the snowBox | | `minY` | `Number.NEGATIVE_INFINITY` | minimum y-coordinate to clamp particle position, which can be used to fake hitting a ground plane and lingering there for a bit | | `numSplats` | calculated from box and density | number of Gsplats to generate | | `density` | `100` | density of Gsplats per unit volume | | `anisoScale` | `new THREE.Vector3(1, 1, 1)` | The xyz anisotropic scale of the Gsplat, which can be used for example to elongate rain particles | | `minScale` | `0.001` | Minimum Gsplat particle scale | | `maxScale` | `0.005` | Maximum Gsplat particle scale | | `fallDirection` | `new THREE.Vector3(0, -1, 0)` | The average direction of fall | | `fallVelocity` | `0.02` | The average speed of the fall (multiplied with fallDirection) | | `wanderScale` | `0.01` | The world scale of wandering overlay motion | | `wanderVariance` | `2` | Controls how uniformly the particles wander in sync, more variance means more randomness in the motion | | `color1` | `new THREE.Color(1, 1, 1)` | Color 1 of the two colors interpolated between | | `color2` | `new THREE.Color(0.5, 0.5, 1)` | Color 2 of the two colors interpolated between | | `opacity` | `1` | The base opacity of the Gsplats | | `onFrame` | `undefined` | Optional callback function to call each frame |