Renamed "forge" to "spark". Updated meta-references and future domain. (#40)

This commit is contained in:
Andreas Sundquist
2025-06-09 13:50:08 -07:00
committed by GitHub
parent ea9e4e3da9
commit acb541cc4e
72 changed files with 396 additions and 395 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
name: Forge CI Linux
name: Spark CI Linux
on:
push:
branches:
@@ -10,7 +10,7 @@ permissions:
contents: read
jobs:
test:
name: forge
name: spark
runs-on: ubuntu-latest
strategy:
matrix:
+2 -2
View File
@@ -1,4 +1,4 @@
name: Forge CI Windows
name: Spark CI Windows
on:
push:
branches:
@@ -10,7 +10,7 @@ permissions:
contents: read
jobs:
test:
name: forge
name: spark
runs-on: windows-latest
strategy:
matrix:
+20 -20
View File
@@ -1,27 +1,27 @@
<p align="center">
![Forge logo](https://github.com/user-attachments/assets/d9cec150-8ca1-47f4-b7d9-4782aa629166#gh-dark-mode-only)
![Forge logo](https://github.com/user-attachments/assets/adb2f0d7-df30-49e5-be7f-75c14f008735#gh-light-mode-only)
![Spark logo](https://github.com/user-attachments/assets/d9cec150-8ca1-47f4-b7d9-4782aa629166#gh-dark-mode-only)
![Spark logo](https://github.com/user-attachments/assets/adb2f0d7-df30-49e5-be7f-75c14f008735#gh-light-mode-only)
<h3 align="center">An advanced 3D Gaussian Splatting renderer for THREE.js</h3>
<div align="center">
[Features](#features) -
[Getting Started](#getting-started) -
<a href="https://forge.dev/">Documentation</a> -
<a href="https://forge.dev/">FAQ</a>
<a href="https://sparkjs.dev/">Documentation</a> -
<a href="https://sparkjs.dev/">FAQ</a>
</div>
</p>
<div align="center">
[![License](https://img.shields.io/badge/license-MIT-%23d43e4c)](https://github.com/forge-gfx/forge/blob/main/LICENSE)
[![npm version](https://img.shields.io/npm/v/forge?color=d43e4c)](https://www.npmjs.com/package/forge)
[![License](https://img.shields.io/badge/license-MIT-%23d43e4c)](https://github.com/sparkjs-dev/spark/blob/main/LICENSE)
[![npm version](https://img.shields.io/npm/v/@sparkjs-dev/spark?color=d43e4c)](https://www.npmjs.com/package/@sparkjs-dev/spark)
</div>
<p>
<a href="https://forge.dev" target="_blank">
<a href="https://sparkjs.dev" target="_blank">
<picture>
</picture>
</a>
@@ -38,7 +38,7 @@
- Real-time splat color editing, displacement, and skeletal animation
- Shader graph system to dynamically create/edit splats on the GPU
Check out all the [examples](https://forge.dev/examples/)
Check out all the [examples](https://sparkjs.dev/examples/)
## Getting Started
@@ -53,13 +53,13 @@ Copy the following code into an `index.html` file.
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.174.0/three.module.js",
"@forge-gfx/forge": "https://forge.dev/releases/forge/0.1.0/forge.module.js"
"@sparkjs-dev/spark": "https://sparkjs.dev/releases/spark/0.2.0/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
@@ -67,7 +67,7 @@ Copy the following code into an `index.html` file.
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)
const splatURL = "https://forge.dev/assets/splats/butterfly.spz";
const splatURL = "https://sparkjs.dev/assets/splats/butterfly.spz";
const butterfly = new SplatMesh({ url: splatURL });
butterfly.quaternion.set(1, 0, 0, 0);
butterfly.position.set(0, 0, -3);
@@ -82,7 +82,7 @@ Copy the following code into an `index.html` file.
### Web Editor
Remix the [glitch starter template](https://glitch.com/edit/#!/forge-dev)
Remix the [glitch starter template](https://glitch.com/edit/#!/sparkjs-dev)
### CDN
@@ -91,7 +91,7 @@ Remix the [glitch starter template](https://glitch.com/edit/#!/forge-dev)
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.174.0/three.module.js",
"forge": "https://forge.dev/releases/forge/0.1.0/forge.module.js"
"@sparkjs-dev/spark": "https://sparkjs.dev/releases/spark/0.2.0/spark.module.js"
}
}
</script>
@@ -100,26 +100,26 @@ Remix the [glitch starter template](https://glitch.com/edit/#!/forge-dev)
### NPM
```shell
npm install @forge-gfx/forge
npm install @sparkjs-dev/spark
```
## Run Examples locally
Install [Rust](https://www.rust-lang.org/tools/install) if it's not already installed in your machine.
Next, build Forge by running:
Next, build Spark by running:
```
npm install
npm run build
```
This will first build the Rust Wasm component (can be invoked via `npm run build:wasm`), then Forge itself (`npm run build`).
This will first build the Rust Wasm component (can be invoked via `npm run build:wasm`), then Spark itself (`npm run build`).
The examples fetch assets from a remote URL. This step is optional, but offline development and faster loading times are possible if you download and cache the assets files locally with the following command:
```
npm run assets:download
```
Once you've built Forge and optionally downloaded the assets, you can now run the examples:
Once you've built Spark and optionally downloaded the assets, you can now run the examples:
```
npm start
```
@@ -183,7 +183,7 @@ Edit markdown in `/docs` directory
npm run docs
```
### Build Forge website
### Build Spark website
Build the static site and docs in a `site` directory.
@@ -197,9 +197,9 @@ You can run any static server in the `site` directory but for convenience you ca
npm run site:serve
```
### Deploy Forge website
### Deploy Spark website
The following command will generate a static site from the `docs` directory and push it to the [repo](https://github.com/forge-gfx/forge-gfx.github.io) that hosts the site via `gh-pages`
The following command will generate a static site from the `docs` directory and push it to the [repo](https://github.com/sparkjs-dev/sparkjs-dev.github.io) that hosts the site via `gh-pages`
```
npm run site:deploy
+1 -1
View File
@@ -11,7 +11,7 @@
"dist/*",
"examples/showcase/hello-gsplat",
"rust/target",
"rust/forge-internal-rs/pkg",
"rust/spark-internal-rs/pkg",
"src/vrButton.ts",
"site",
"site-repo",
+7 -7
View File
@@ -1,16 +1,16 @@
# Community Resources
A collection of resources and examples created by the community. To add your own, please submit a PR to edit this page at [forge-gfx/forge](https://github.com/forge-gfx/forge/tree/main/docs/docs/community-resources.md).
A collection of resources and examples created by the community. To add your own, please submit a PR to edit this page at [sparkjs-dev/spark](https://github.com/sparkjs-dev/spark/tree/main/docs/docs/community-resources.md).
## Discord
Join the [Forge Discord](https://discord.gg/W39qmSKemS) to connect with other users and developers.
Join the [Spark Discord](https://discord.gg/W39qmSKemS) to connect with other users and developers.
## React Examples
Forge can be used alongside or within React for declarative scene management, dynamic rendering, and state management between your user interface and the 3D scene. See the following examples for how to use Forge with React.
Spark can be used alongside or within React for declarative scene management, dynamic rendering, and state management between your user interface and the 3D scene. See the following examples for how to use Spark with React.
- [`forge-react-basic`](https://github.com/forge-gfx/forge-react-basic): A basic example of creating a `<canvas>` and THREE.js scene with Forge.
- [`forge-react-r3f`](https://github.com/forge-gfx/forge-react-r3f): Use Forge declaratively in React with [React Three Fiber](https://r3f.docs.pmnd.rs).
- [`forge-react-router`](https://github.com/forge-gfx/forge-react-router): An example of using Forge and React Three Fiber with [React Router](https://reactrouter.com) v7 framework mode with SSR.
- [`forge-react-nextjs`](https://github.com/forge-gfx/forge-react-nextjs): An example of using Forge and React Three Fiber with Next.js App Router.
- [`spark-react-basic`](https://github.com/sparkjs-dev/spark-react-basic): A basic example of creating a `<canvas>` and THREE.js scene with Spark.
- [`spark-react-r3f`](https://github.com/sparkjs-dev/spark-react-r3f): Use Spark declaratively in React with [React Three Fiber](https://r3f.docs.pmnd.rs).
- [`spark-react-router`](https://github.com/sparkjs-dev/spark-react-router): An example of using Spark and React Three Fiber with [React Router](https://reactrouter.com) v7 framework mode with SSR.
- [`spark-react-nextjs`](https://github.com/sparkjs-dev/spark-react-nextjs): An example of using Spark and React Three Fiber with Next.js App Router.
+3 -3
View File
@@ -1,9 +1,9 @@
# Controls
A program using `Forge` can use any camera control scheme that is compatible with THREE.js and will typically manipulate a `THREE.Camera` object's transform. `Forge` also ships with simple, intuitive controls for navigating 3D space that use the keyboard + mouse, game pad, or mobile multi-touch. To add these controls, you can create a `ForgeControls` instance:
A program using `Spark` can use any camera control scheme that is compatible with THREE.js and will typically manipulate a `THREE.Camera` object's transform. `Spark` also ships with simple, intuitive controls for navigating 3D space that use the keyboard + mouse, game pad, or mobile multi-touch. To add these controls, you can create a `SparkControls` instance:
```typescript
const controls = new ForgeControls({
const controls = new SparkControls({
canvas: HTMLCanvasElement;
});
@@ -13,7 +13,7 @@ renderer.setAnimationLoop((time) => {
});
```
`ForgeControls` instantiates two classes `FpsMovement` and `PointerControls` that it updates internally. You can also instantiate and use these two classes separately:
`SparkControls` instantiates two classes `FpsMovement` and `PointerControls` that it updates internally. You can also instantiate and use these two classes separately:
## `class FpsMovement`
+5 -5
View File
@@ -1,12 +1,12 @@
# Dyno shaders
The `dyno` shader graph system is one of the architectural pillars of Forge, allowing you to create custom computation graphs using Javascript (and optionally GLSL) that are compiled to GLSL and run on the GPU, similar to shader graph systems in modern 3D graphics engines.
The `dyno` shader graph system is one of the architectural pillars of Spark, allowing you to create custom computation graphs using Javascript (and optionally GLSL) that are compiled to GLSL and run on the GPU, similar to shader graph systems in modern 3D graphics engines.
A core component of this system is the class `Dyno` and its subclasses, which can be thought of as function blocks with multiple typed inputs and outputs. Values passed between such blocks are of type `DynoVal<T>`, where `T` must be a `DynoType`, representing a GPU type in GLSL. Using TypeScript, Forge ensures type safety and static validation of the GPU computation graph.
A core component of this system is the class `Dyno` and its subclasses, which can be thought of as function blocks with multiple typed inputs and outputs. Values passed between such blocks are of type `DynoVal<T>`, where `T` must be a `DynoType`, representing a GPU type in GLSL. Using TypeScript, Spark ensures type safety and static validation of the GPU computation graph.
All `dyno` code is contained within `src/dyno`, and has definitions which cover all built-in GLSL ES 3.0 types (`"int"`, `"float"`, `"vec"`, etc.) and most of the standard functions (`mul`, `cross`, `texelFetch`, etc.). Note that regular Javascript functions can't be part of a `dyno` computation graph: instead of `x + y` you should use `dyno.add(x, y)`. You can also define your own custom types and `dyno` blocks, both by compositing existing `dyno` functions or by writing GLSL code directly.
Forge currently uses these Dynos in two main places:
Spark currently uses these Dynos in two main places:
- Dynamically generating splats from `SplatGenerator`/`SplatMesh` into the scene
- Computing the splat distance metric for CPU readback and sorting
@@ -17,7 +17,7 @@ Learning to build and use `dyno` programs is probably best approached by startin
## `type DynoType`
A `DynoType` can be either a string that corresponds to a built-in GLSL type, or `{ type: "MyType" }` for a user-defined type. These types are used both for identifying value types `DynoVal<T extends DynoType>` and for declaring input/output types for `Dyno` blocks, for example `{ index: "int" }` or `{ gsplat: Gsplat }` (`Gsplat` is defined as `{ type: "Gsplat" }`). Forge uses these to enforce TypeScript constraints on inputs+outputs of `Dyno` blocks to generate correct GLSL code.
A `DynoType` can be either a string that corresponds to a built-in GLSL type, or `{ type: "MyType" }` for a user-defined type. These types are used both for identifying value types `DynoVal<T extends DynoType>` and for declaring input/output types for `Dyno` blocks, for example `{ index: "int" }` or `{ gsplat: Gsplat }` (`Gsplat` is defined as `{ type: "Gsplat" }`). Spark uses these to enforce TypeScript constraints on inputs+outputs of `Dyno` blocks to generate correct GLSL code.
### Build-in types
@@ -25,7 +25,7 @@ A built-in GLSL type can be single-valued (`"int"`, `"uint"`, `"float"`, `"bool"
### Custom types
Forge defines a handful of custom types that are useful: `Gsplat`, `TPackedSplats`, `SdfArray`, `TRgbaArray`, and `SplatSkinning`. For example, in `src/dyno/splats.ts` we define `Gsplat` as `{ type: "Gsplat" }` along with a helper function:
Spark defines a handful of custom types that are useful: `Gsplat`, `TPackedSplats`, `SdfArray`, `TRgbaArray`, and `SplatSkinning`. For example, in `src/dyno/splats.ts` we define `Gsplat` as `{ type: "Gsplat" }` along with a helper function:
```typescript
export const defineGsplat = unindent(`
+4 -4
View File
@@ -1,6 +1,6 @@
# Dyno Standard Library
The Forge `dyno` system provides a standard library of `Dyno` blocks that cover most of the built-in functions in GLSL ES 3.0, including data conversion, logic, math, trigonometry, linear algebra, texture lookups, transforms, managing uniform variables, hashing & RNG, and of course managing splat data.
The Spark `dyno` system provides a standard library of `Dyno` blocks that cover most of the built-in functions in GLSL ES 3.0, including data conversion, logic, math, trigonometry, linear algebra, texture lookups, transforms, managing uniform variables, hashing & RNG, and of course managing splat data.
We use the convention of PascalCase for the names of the `Dyno` classes, and camelCase for the names of equivalent helper functions that are more ergonomic to use. For example, you can equivalently write:
```typescript
@@ -157,7 +157,7 @@ The following math functions follow standard GLSL ES 3.0 semantics, for example
## Uniform variables
Constant values and literals in `dyno` programs should not be changed often because it incurs a recompilation. To have a variable that can be changed every frame, you can declare a "uniform". The base class for uniforms provided by Forge is `DynoUniform`, which importantly contains a type, current value, and update function.
Constant values and literals in `dyno` programs should not be changed often because it incurs a recompilation. To have a variable that can be changed every frame, you can declare a "uniform". The base class for uniforms provided by Spark is `DynoUniform`, which importantly contains a type, current value, and update function.
To update a uniform, simply assign a new value to the `value` property of the uniform. Alternatively, you can construct a `DynoUniform` with an `update` function that is called for each execution. This function can either update `value` directly, or return any non-`undefined` value to have it updated.
@@ -193,7 +193,7 @@ Use the following helper functions for more ergonomic creation of uniforms.
## Hashing & Random number generation
When a `dyno` program executes, each invocation for a given splat/index is effectively run in parallel, separate from the rest. In order to incorporate randomness into a `dyno` program, you must use the inputs available to the program, which is often just the `index` of the splat itself. Forge provides functions to hash any scalar or vector (integer or float) into 1-4 components of either a uint32 or float, using the PCG random number generator.
When a `dyno` program executes, each invocation for a given splat/index is effectively run in parallel, separate from the rest. In order to incorporate randomness into a `dyno` program, you must use the inputs available to the program, which is often just the `index` of the splat itself. Spark provides functions to hash any scalar or vector (integer or float) into 1-4 components of either a uint32 or float, using the PCG random number generator.
| **Function** | **Description** |
|----------|-------------|
@@ -211,7 +211,7 @@ When a `dyno` program executes, each invocation for a given splat/index is effec
## Splat data
Forge makes it easier to work with splat data by defining the GLSL struct `Gsplat` which contains the following fields:
Spark makes it easier to work with splat data by defining the GLSL struct `Gsplat` which contains the following fields:
| **Field** | **Type** | **Description** |
|----------|-------------|-------------|
+10 -10
View File
@@ -2,7 +2,7 @@
## Quick Start
Copy and paste code below in an `index.html` file or remix in the [Web Playground](https://glitch.com/edit/#!/forge-dev)
Copy and paste code below in an `index.html` file or remix in the [Web Playground](https://glitch.com/edit/#!/sparkjs-dev)
```html
<style> body {margin: 0;} </style>
@@ -10,13 +10,13 @@ Copy and paste code below in an `index.html` file or remix in the [Web Playgroun
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.174.0/three.module.js",
"@forge-gfx/forge": "https://forge.dev/releases/forge/0.1.0/forge.module.js"
"@sparkjs-dev/spark": "https://sparkjs.dev/releases/spark/0.2.0/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
@@ -24,7 +24,7 @@ Copy and paste code below in an `index.html` file or remix in the [Web Playgroun
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)
const splatURL = "https://forge.dev/assets/splats/butterfly.spz";
const splatURL = "https://sparkjs.dev/assets/splats/butterfly.spz";
const butterfly = new SplatMesh({ url: splatURL });
butterfly.quaternion.set(1, 0, 0, 0);
butterfly.position.set(0, 0, -3);
@@ -39,11 +39,11 @@ Copy and paste code below in an `index.html` file or remix in the [Web Playgroun
## Install with NPM
```shell
npm install @forge-gfx/forge
npm install @sparkjs-dev/spark
```
## Develop and contribute to Forge
## Develop and contribute to Spark
Build Forge (It requires [Rust](https://www.rust-lang.org/tools/install) installed in your machine)
Build Spark (It requires [Rust](https://www.rust-lang.org/tools/install) installed in your machine)
```
npm install
npm run dev
@@ -53,10 +53,10 @@ This will run a Web server at [http://localhost:8080/](http://localhost:8080/) w
## Table of Contents
- [Forge Overview](overview.md)
- [Spark Overview](overview.md)
- [System Design](system-design.md)
- [ForgeRenderer](forge-renderer.md)
- [ForgeViewpoint](forge-viewpoint.md)
- [SparkRenderer](spark-renderer.md)
- [SparkViewpoint](spark-viewpoint.md)
- [SplatMesh](splat-mesh.md)
- [PackedSplats](packed-splats.md)
- [Loading Gsplats](loading-splats.md)
+2 -2
View File
@@ -1,8 +1,8 @@
# Loading Splats
Forge provides loaders for most popular splat file formats, including `.ply` (original "gsplat" format, compressed SuperSplat variant, and plain x/y/z/r/g/b point clouds) and `.spz` (Niantic open source compressed format), both auto-detected from the file contents.
Spark provides loaders for most popular splat file formats, including `.ply` (original "gsplat" format, compressed SuperSplat/gsplat variant, and plain x/y/z/r/g/b point clouds) and `.spz` (Niantic open source compressed format), both auto-detected from the file contents.
Forge can also load formats `.splat` (from [`antimatter15/splat`](https://github.com/antimatter15/splat)) and `.ksplat` (from [`mkkellogg/GaussianSplats3D`](https://github.com/mkkellogg/GaussianSplats3D)) if the file type can be inferred from the URL/path extension, or set explicitly using the `fileType` property when creating a `SplatMesh` or `PackedSplats`.
Spark can also load formats `.splat` (from [`antimatter15/splat`](https://github.com/antimatter15/splat)) and `.ksplat` (from [`mkkellogg/GaussianSplats3D`](https://github.com/mkkellogg/GaussianSplats3D)) if the file type can be inferred from the URL/path extension, or set explicitly using the `fileType` property when creating a `SplatMesh` or `PackedSplats`.
## Loading auto-detectable formats `.ply` and `.spz`
+5 -5
View File
@@ -1,16 +1,16 @@
*Forge aims to expand what's possible with Gaussian Splatting and help 3D/4D creators bring their visions to life and share it with others.*
*Spark aims to expand what's possible with Gaussian Splatting and help 3D/4D creators bring their visions to life and share it with others.*
# Overview
3D Gaussian Splatting has emerged as a frontrunner in generative AI and 3D reconstruction. By representing 3D scenes and objects as collections of tiny oriented Gaussian-shaped blobs (aka "splats"), machine learning techniques can be used to create detailed, photorealistic 3D content that can be rendered in real-time. However, 3DGS is a relatively new technique that that can't be used in many traditional triangle-based 3D mesh rendering engines. Tools for creating, editing, and rendering 3DGS are in their infancy, mostly able to only work with a single, static 3DGS object at time.
The web's most popular 3D graphics library THREE.js, can't render 3DGS directly. Although libraries exist to render 3DGS on the web, they each come with different limitations and don't treat 3DGS objects as first-class citizens in the scene hierarchy. Limitations include: rendering only one 3DGS object at time, incorrect occlusion between 3DGS objects, inability to dynamically modify the objects, requiring WebGPU, or slow/laggy rendering. We believe 3DGS will play an important role in future 3D/4D content creation, and we built Forge to make it easy to incorporate 3DGS into online experiences.
The web's most popular 3D graphics library THREE.js, can't render 3DGS directly. Although libraries exist to render 3DGS on the web, they each come with different limitations and don't treat 3DGS objects as first-class citizens in the scene hierarchy. Limitations include: rendering only one 3DGS object at time, incorrect occlusion between 3DGS objects, inability to dynamically modify the objects, requiring WebGPU, or slow/laggy rendering. We believe 3DGS will play an important role in future 3D/4D content creation, and we built Spark to make it easy to incorporate 3DGS into online experiences.
## Forge
## Spark
Forge is a dynamic 3DGS renderer built for THREE.js and WebGL2 that runs in any web browser (desktop, mobile, and WebXR). With a handful of lines of code, anyone using THREE.js can easily add 3DGS to their scenes (even by vibe coding!). By creating one or more `SplatMesh` objects and adding them to your scene, Forge will render these alongside traditional triangle-based meshes during your regular `render(scene, camera)` call. `SplatMesh` derives from `THREE.Object3D` and can be translated and rotated like any other object, placed arbitrarily in the scene hierarchy, and animated by adjusting the values each frame. A `SplatMesh` can be created from most splat file formats via the `url` parameter or by directly creating the splats one by one.
Spark is a dynamic 3DGS renderer built for THREE.js and WebGL2 that runs in any web browser (desktop, mobile, and WebXR). With a handful of lines of code, anyone using THREE.js can easily add 3DGS to their scenes (even by vibe coding!). By creating one or more `SplatMesh` objects and adding them to your scene, Spark will render these alongside traditional triangle-based meshes during your regular `render(scene, camera)` call. `SplatMesh` derives from `THREE.Object3D` and can be translated and rotated like any other object, placed arbitrarily in the scene hierarchy, and animated by adjusting the values each frame. A `SplatMesh` can be created from most splat file formats via the `url` parameter or by directly creating the splats one by one.
3DGS is still in its infancy, and we expect new techniques will be developed for recoloring/relighting, animation, transitions, and other creative or interactive effects. We designed Forge to be a programmable splat engine from the ground up, giving you unprecedented control over how individual splats are generated, animated, and rendered into the scene. Similar to shader graph systems in modern 3D graphics engines, Forge allows you to compose blocks of functions (called `Dyno`s) into computation graphs that can generate splats procedurally, modify them arbitrarily, or anything other computation you can imagine, and will be converted to GLSL to run on the GPU.
3DGS is still in its infancy, and we expect new techniques will be developed for recoloring/relighting, animation, transitions, and other creative or interactive effects. We designed Spark to be a programmable splat engine from the ground up, giving you unprecedented control over how individual splats are generated, animated, and rendered into the scene. Similar to shader graph systems in modern 3D graphics engines, Spark allows you to compose blocks of functions (called `Dyno`s) into computation graphs that can generate splats procedurally, modify them arbitrarily, or anything other computation you can imagine, and will be converted to GLSL to run on the GPU.
## Features
- Integrates with THREE.js rendering pipeline to fuse splat and mesh-based objects
+4 -4
View File
@@ -49,7 +49,7 @@ Utility functions are provided in Javascript to pack/unpack these encodings:
packedSplats.setSplat(index, center, scales, quaternion, opacity, color);
// Set underlying Uint32 array directly
import { utils } from "@forge-gfx/forge";
import { utils } from "@sparkjs-dev/spark";
utils.setPackedSplat(packedSplats.packedArray, index, x, y, z, scaleX, scaleY, ...);
// Set rotation components of underlying Uint32 array directly
@@ -111,7 +111,7 @@ Opacity is encoded on a linear scale where 0..255 maps to 0..1.
### Splat center encoding
The center x/y/z components are encoded as float16, which provides 10 bits of mantissa, or approximately 1K steps (0.1%) of resolution between each successive power of 0 from the origin, with a range of up to 32K in distance. If most of the splats are positioned relative to the origin this provides enough positional resolution. Splats that are transformed far from the origin, however (for example when bringing multiple `SplatMesh`es together in a scene that are far apart) may lose precision when mapped to the space of `ForgeRenderer`. For scenes where the user camera may move far from the origin, you may want to tie the `ForgeRenderer` origin to your camera by adding it as a child of the camera.
The center x/y/z components are encoded as float16, which provides 10 bits of mantissa, or approximately 1K steps (0.1%) of resolution between each successive power of 0 from the origin, with a range of up to 32K in distance. If most of the splats are positioned relative to the origin this provides enough positional resolution. Splats that are transformed far from the origin, however (for example when bringing multiple `SplatMesh`es together in a scene that are far apart) may lose precision when mapped to the space of `SparkRenderer`. For scenes where the user camera may move far from the origin, you may want to tie the `SparkRenderer` origin to your camera by adding it as a child of the camera.
### Splat scales encoding
@@ -171,7 +171,7 @@ Can be used where you need an uninitialized `THREE.DataArrayTexture` like a unif
To generate a large number of splats we can use the `dyno` shader graph system, which allows you to create a computation graph mapping `{ index: DynoVal<"int"> }` to `{ gsplat: DynoVal<Gsplat> }` via Javascript code, then have that synthesize GLSL code, which is finally compiled and executed in parallel on the GPU.
This building block is used by `ForgeRenderer` to traverse each visible `SplatMesh`/`SplatGenerator` and have it "generate" its splats into the global `PackedSplats` array managed by a `SplatAccumulator`. At its core a `PackedSplats` has the ability to run `dyno` computation graphs to produce its contents using the following methods, which are typically managed by `ForgeRenderer`:
This building block is used by `SparkRenderer` to traverse each visible `SplatMesh`/`SplatGenerator` and have it "generate" its splats into the global `PackedSplats` array managed by a `SplatAccumulator`. At its core a `PackedSplats` has the ability to run `dyno` computation graphs to produce its contents using the following methods, which are typically managed by `SparkRenderer`:
### `generateMapping(splatCounts: number[]): { maxSplats, mapping[] }`
@@ -183,7 +183,7 @@ Ensures our `PackedSplats.target` render target has enough space to generate `ma
### `generate({ generator, base, count, ... })`
Executes a `dyno` program specified by `generator` which is any `DynoBlock` that maps `{ index: "int" }` to `{ gsplat: Gsplat }`. This is invoked from `ForgeRenderer.updateInternal()` to re-generate splats in the scene for `SplatGenerator` instances whose version is newer than last generated version.
Executes a `dyno` program specified by `generator` which is any `DynoBlock` that maps `{ index: "int" }` to `{ gsplat: Gsplat }`. This is invoked from `SparkRenderer.updateInternal()` to re-generate splats in the scene for `SplatGenerator` instances whose version is newer than last generated version.
## Using dynamic PackedSplats inputs in `dyno`
+2 -2
View File
@@ -13,7 +13,7 @@ Each splat incurs overhead in transforming it via SplatAccumulator for sorting a
## `maxStdDev`
Adjust `ForgeRenderer.maxStdDev` (either directly on `ForgeRenderer` or via constructor options) to a value less than the default `Math.sqrt(8)`. This limits the extent of the Gaussian fall-off, which by default is approx 2.8. For VR a good value is `Math.sqrt(5)`, which is perceptually very similar to the default.
Adjust `SparkRenderer.maxStdDev` (either directly on `SparkRenderer` or via constructor options) to a value less than the default `Math.sqrt(8)`. This limits the extent of the Gaussian fall-off, which by default is approx 2.8. For VR a good value is `Math.sqrt(5)`, which is perceptually very similar to the default.
## `THREE.WebGLRenderer.antialias`
@@ -21,4 +21,4 @@ When constructing a `THREE.WebGLRenderer` you should set `antialias: false` (def
## `renderer.setPixelRatio(window.devicePixelRatio)`
Although `ForgeRenderer` is designed to work with any `devicePixelRatio`, it may impact performance due to the increased number of pixels to render and blend. If your scene consists of mostly splats, consider whether you have enough splats to justify the high DPI rendering.
Although `SparkRenderer` is designed to work with any `devicePixelRatio`, it may impact performance due to the increased number of pixels to render and blend. If your scene consists of mostly splats, consider whether you have enough splats to justify the high DPI rendering.
+4 -4
View File
@@ -1,6 +1,6 @@
# Procedural Splats
Forge makes it easy to create Gaussian splat collections procedurally, and includes some splat constructors that are useful for common tasks like creating a grid or text made of splats. The example "Procedural Splats" puts some of these to use in a scene.
Spark makes it easy to create Gaussian splat collections procedurally, and includes some splat constructors that are useful for common 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
@@ -55,7 +55,7 @@ scene.add(mesh);
## Grid
```javascript
import { constructGrid } from "@forge-gfx/forge";
import { constructGrid } from "@sparkjs-dev/spark";
const grid = new SplatMesh({
constructSplats: (splats) => constructGrid({
@@ -90,7 +90,7 @@ scene.add(grid);
## XYZ axis
```javascript
import { constructAxes } from "@forge-gfx/forge";
import { constructAxes } from "@sparkjs-dev/spark";
const axes = new SplatMesh({
constructSplats: (splats) => constructAxes({ splats }),
@@ -118,7 +118,7 @@ scene.add(axes);
## Splat sphere
```javascript
import { constructSpherePoints } from "@forge-gfx/forge";
import { constructSpherePoints } from "@sparkjs-dev/spark";
const sphere = new SplatMesh({
constructSplats: (splats) => constructSpherePoints({
@@ -1,31 +1,31 @@
# ForgeRenderer
# SparkRenderer
## Optionally adding to your `THREE.Scene`
Forge internally uses a `ForgeRenderer` object in your `THREE.Scene` to perform splat rendering. Forge will automatically create a `ForgeRenderer` and add it to your scene if you don't create one yourself. For more advanced use cases such as multiple viewpoints or rendering environment maps, you can create your own `ForgeRenderer` and add it anywhere in the scene, for example at the root:
Spark internally uses a `SparkRenderer` object in your `THREE.Scene` to perform splat rendering. Spark will automatically create a `SparkRenderer` and add it to your scene if you don't create one yourself. For more advanced use cases such as multiple viewpoints or rendering environment maps, you can create your own `SparkRenderer` and add it anywhere in the scene, for example at the root:
```typescript
const forge = new ForgeRenderer({
const spark = new SparkRenderer({
renderer: myThreeJsWebGlRenderer,
});
const scene = new THREE.Scene();
scene.add(forge);
scene.add(spark);
```
## Larger scenes
All scene splats are accumulated by SplatAccumulator into a single global PackedSplats, whose coordinates are relative to the ForgeRenderer's origin. Splats that are far away from this origin may exhibit float16 quantization artifacts, so if you plan on moving the camera large distances you can instead add `ForgeRenderer` as a child of your `THREE.Camera`, ensuring that coordinates will have higher precision near the camera viewpoint:
All scene splats are accumulated by SplatAccumulator into a single global PackedSplats, whose coordinates are relative to the SparkRenderer's origin. Splats that are far away from this origin may exhibit float16 quantization artifacts, so if you plan on moving the camera large distances you can instead add `SparkRenderer` as a child of your `THREE.Camera`, ensuring that coordinates will have higher precision near the camera viewpoint:
```javascript
const aspect = canvas.width / canvas.height;
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
scene.add(camera);
// Add ForgeRenderer as a child of camera to follow it
camera.add(forge);
// Add SparkRenderer as a child of camera to follow it
camera.add(spark);
```
## Creating a `ForgeRenderer`
## Creating a `SparkRenderer`
```typescript
const forge = new ForgeRenderer({
const spark = new SparkRenderer({
renderer: THREE.WebGLRenderer;
clock?: THREE.Clock;
autoUpdate?: boolean;
@@ -37,37 +37,37 @@ const forge = new ForgeRenderer({
blurAmount?: number;
falloff?: number;
clipXY?: number;
view?: ForgeViewpointOptions;
view?: SparkViewpointOptions;
});
```
### Required parameters
| **Parameter** | Description |
| ------------- | ----------- |
| **renderer** | Pass in your `THREE.WebGLRenderer` instance so Forge can perform work outside the usual render loop. Should be created with `antialias: false` (default setting) as WebGL anti-aliasing doesn't improve Gaussian Splatting rendering and significantly reduces performance.
| **renderer** | Pass in your `THREE.WebGLRenderer` instance so Spark can perform work outside the usual render loop. Should be created with `antialias: false` (default setting) as WebGL anti-aliasing doesn't improve Gaussian Splatting rendering and significantly reduces performance.
### Optional parameters
| **Parameter** | Description |
| ----------------- | ----------- |
| **clock** | Pass in a `THREE.Clock` to synchronize time-based effects across different systems. Alternatively, you can set the `ForgeRenderer` properties `time` and `deltaTime` directly. (default: `new THREE.Clock`)
| **clock** | Pass in a `THREE.Clock` to synchronize time-based effects across different systems. Alternatively, you can set the `SparkRenderer` properties `time` and `deltaTime` directly. (default: `new THREE.Clock`)
| **autoUpdate** | Controls whether to check and automatically update splat collection after each frame render. (default: `true`)
| **preUpdate** | Controls whether to update the splats before or after rendering. For WebXR this *must* be false in order to complete rendering as soon as possible. (default: `false`)
| **originDistance** | Distance threshold for `ForgeRenderer` movement triggering a splat update at the new origin. (default: `1.0`) This can be useful when your `ForgeRenderer` is a child of your camera and you want to retain high precision coordinates near the camera.
| **originDistance** | Distance threshold for `SparkRenderer` movement triggering a splat update at the new origin. (default: `1.0`) This can be useful when your `SparkRenderer` is a child of your camera and you want to retain high precision coordinates near the camera.
| **maxStdDev** | Maximum standard deviations from the center to render Gaussians. Values `Math.sqrt(5)`..`Math.sqrt(9)` produce good results and can be tweaked for performance. (default: `Math.sqrt(8)`)
| **enable2DGS** | Enable 2D Gaussian splatting rendering ability. When this mode is enabled, any `scale` x/y/z component that is exactly `0` (minimum quantized value) results in the other two non-zero axes being interpreted as an oriented 2D Gaussian Splat instead of the usual approximate projected 3DGS Z-slice. When reading PLY files, scale values less than e^-20 will be interpreted as `0`. (default: `true`)
| **preBlurAmount** | Scalar value to add to 2D splat covariance diagonal, effectively blurring + enlarging splats. In scenes trained without the splat anti-aliasing tweak this value was typically 0.3, but with anti-aliasing it is 0.0 (default: `0.0`)
| **blurAmount** | Scalar value to add to 2D splat covariance diagonal, with opacity adjustment to correctly account for "blurring" when anti-aliasing. Typically 0.3 (equivalent to approx 0.5 pixel radius) in scenes trained with anti-aliasing.
| **falloff** | Modulate Gaussian kernel falloff. 0 means "no falloff, flat shading", while 1 is the normal Gaussian kernel. (default: `1.0`)
| **clipXY** | X/Y clipping boundary factor for splat centers against view frustum. 1.0 clips any centers that are exactly out of bounds (but the splat's entire projection may still be in bounds), while 1.4 clips centers that are 40% beyond the bounds. (default: `1.4`)
| **view** | Configures the `ForgeViewpointOptions` for the default `ForgeViewpoint` associated with this `ForgeRenderer`. Notable option: `sortRadial` (sort by radial distance or Z-depth)
| **view** | Configures the `SparkViewpointOptions` for the default `SparkViewpoint` associated with this `SparkRenderer`. Notable option: `sortRadial` (sort by radial distance or Z-depth)
## `newViewpoint(options: ForgeViewpointOptions)`
## `newViewpoint(options: SparkViewpointOptions)`
Create a new `ForgeViewpoint` for this `ForgeRenderer`. Note that every `ForgeRenderer` has an initial `forge.defaultView: ForgeViewpoint` created during construction, which is used for default canvas rendering. Calling this method allows you to create additional viewpoints, which can be updated automatically each frame (performing splat sorting every time there is an update), or updated on-demand for controlled rendering for video frame rendering or similar applications.
Create a new `SparkViewpoint` for this `SparkRenderer`. Note that every `SparkRenderer` has an initial `spark.defaultView: SparkViewpoint` created during construction, which is used for default canvas rendering. Calling this method allows you to create additional viewpoints, which can be updated automatically each frame (performing splat sorting every time there is an update), or updated on-demand for controlled rendering for video frame rendering or similar applications.
## `update({ scene })`
If `forge.autoUpdate` is `false` then you must manually call `forge.update({ scene })` to have the scene splats re-generated.
If `spark.autoUpdate` is `false` then you must manually call `spark.update({ scene })` to have the scene splats re-generated.
## `renderEnvMap({ renderer, scene, worldCenter, ... })`
@@ -1,13 +1,13 @@
# ForgeViewpoint
# SparkViewpoint
A `ForgeViewpoint` is created from and tied to a `ForgeRenderer`, and represents an independent viewpoint of all the scene splats and their sort order. Making these viewpoints explicit allows us to have multiple, simultaneous viewpoint renders, for example for camera preview panes or overhead map views.
A `SparkViewpoint` is created from and tied to a `SparkRenderer`, and represents an independent viewpoint of all the scene splats and their sort order. Making these viewpoints explicit allows us to have multiple, simultaneous viewpoint renders, for example for camera preview panes or overhead map views.
When creating a `ForgeRenderer` it automatically creates a default viewpoint `.defaultView` that is used in the normal render loop when drawing to the canvas, and is automatically updated whenever the camera moves. Additional viewpoints can be created and configured separately:
When creating a `SparkRenderer` it automatically creates a default viewpoint `.defaultView` that is used in the normal render loop when drawing to the canvas, and is automatically updated whenever the camera moves. Additional viewpoints can be created and configured separately:
## Creating a `ForgeViewpoint`
## Creating a `SparkViewpoint`
```typescript
const viewpoint = forge.newViewpoint({
const viewpoint = spark.newViewpoint({
autoUpdate?: boolean;
camera?: THREE.Camera;
viewToWorld?: THREE.Matrix4;
@@ -30,7 +30,7 @@ const viewpoint = forge.newViewpoint({
| **Parameter** | Description |
| ----------------- | ----------- |
| **autoUpdate** | Controls whether to auto-update its sort order whenever the ForgeRenderer updates the splats. If you expect to render/display from this viewpoint most frames, set this to `true`. (default: `false`)
| **autoUpdate** | Controls whether to auto-update its sort order whenever the SparkRenderer updates the splats. If you expect to render/display from this viewpoint most frames, set this to `true`. (default: `false`)
| **camera** | Set a `THREE.Camera` for this viewpoint to follow. (default: `undefined`)
| **viewToWorld** | Set an explicit view-to-world transformation matrix for this viewpoint (equivalent to `camera.matrixWorld`), overrides any `camera` setting. (default: `undefined`)
| **target** | Configure viewpoint with an off-screen render target. (default: `undefined`)
@@ -43,19 +43,19 @@ const viewpoint = forge.newViewpoint({
| **sortDistance** | Distance threshold for re-sorting splats. If the viewpoint moves more than this distance, splats will be re-sorted. (default: `0.01` units)
| **sortCoorient** | View direction dot product threshold for re-sorting splats. For `sortRadial: true` it defaults to 0.99 while `sortRadial: false` uses 0.999 because it is more sensitive to view direction. (default: `0.99` if `sortRadial` else `0.999`)
| **depthBias** | Constant added to Z-depth to bias values into the positive range for `sortRadial: false`, but also used for culling splats "well behind" the viewpoint origin (default: `1.0`)
| **sort360** | Set this to true if rendering a 360 to disable "behind the viewpoint" culling during sorting. This is set automatically when rendering 360 envMaps using the `ForgeRenderer.renderEnvMap()` utility function. (default: `false`)
| **sort360** | Set this to true if rendering a 360 to disable "behind the viewpoint" culling during sorting. This is set automatically when rendering 360 envMaps using the `SparkRenderer.renderEnvMap()` utility function. (default: `false`)
## `dispose()`
Call this when you are done with the `ForgeViewpoint` and want to free up its resources (GPU targets, pixel buffers, etc.)
Call this when you are done with the `SparkViewpoint` and want to free up its resources (GPU targets, pixel buffers, etc.)
## `setAutoUpdate(autoUpdate: boolean)`
Use this function to change whether this viewpoint will auto-update its sort order whenever the attached `ForgeRenderer` updates the splats. Turn this on or off depending on whether you expect to do renders from this viewpoint for most frames.
Use this function to change whether this viewpoint will auto-update its sort order whenever the attached `SparkRenderer` updates the splats. Turn this on or off depending on whether you expect to do renders from this viewpoint for most frames.
## `async prepareRenderPixels({ scene, camera?, viewToWorld?, update?, forceOrigin? })`
Render out a viewpoint as a Uint8Array of RGBA values for the provided scene and any `camera`/`viewToWorld` viewpoint overrides. By default `update` is `true`, which triggers its `ForgeRenderer` to check and potentially update the splats. Setting `update` to `false` disables this and sorts the splats as they are. Setting `forceOrigin` (default: `false`) to `true` forces the view update to recalculate the splats with this view origin, potentially altering any view-dependent effects. If you expect view-dependent effects to play a role in the rendering quality, enable this.
Render out a viewpoint as a Uint8Array of RGBA values for the provided scene and any `camera`/`viewToWorld` viewpoint overrides. By default `update` is `true`, which triggers its `SparkRenderer` to check and potentially update the splats. Setting `update` to `false` disables this and sorts the splats as they are. Setting `forceOrigin` (default: `false`) to `true` forces the view update to recalculate the splats with this view origin, potentially altering any view-dependent effects. If you expect view-dependent effects to play a role in the rendering quality, enable this.
Underneath, `prepareRenderPixels()` simply calls `await this.prepare(...)`, `this.renderTarget(...)`, and finally returns the result of `this.readTarget()`, a Promise to a Uint8Array with RGBA values for all the pixels (potentially downsampled if the `superXY` parameter was used). These steps can also be called manually, for example if you need to alter the scene before and after `this.renderTarget(...)` to hide UI elements from being rendered.
@@ -73,8 +73,8 @@ Read back the previously rendered target image as a Uint8Array of packed RGBA va
## `autoPoll()`
This is called automatically by `ForgeRenderer`, there is no need to call it! The method cannot be private because then ForgeRenderer would not be able to call it.
This is called automatically by `SparkRenderer`, there is no need to call it! The method cannot be private because then SparkRenderer would not be able to call it.
## `ForgeViewpoint.EMPTY_TEXTURE`
## `SparkViewpoint.EMPTY_TEXTURE`
If you need an empty `THREE.Texture` to use to initialize a uniform that is updated via `onTextureUpdated(texture)`, this static texture can be handy.
+1 -1
View File
@@ -1,6 +1,6 @@
# Splat Editing
Forge provides the ability to apply "edits" to splats as part of the standard `SplatMesh` pipeline. These edits take the form of a sequence of operations, applied one at a time to the set of splats in its `packedSplats`. Each operation evaluates a 7-dimensional field (RGBA and XYZ displacement) at each splat's center point in space that derives from 1 or more Signed Distance Field shapes (such as spheres, boxes, planes, etc.), blended together and across inside-outside boundaries.
Spark provides the ability to apply "edits" to splats as part of the standard `SplatMesh` pipeline. These edits take the form of a sequence of operations, applied one at a time to the set of splats in its `packedSplats`. Each operation evaluates a 7-dimensional field (RGBA and XYZ displacement) at each splat's center point in space that derives from 1 or more Signed Distance Field shapes (such as spheres, boxes, planes, etc.), blended together and across inside-outside boundaries.
The result is a an RGBA,XYZ value for each splat, which combined with SplatEditRgbaBlendMode.MULTIPLY/SET_RGB/ADD_RGBA can be used to create special effects. For example, simulating simple lighting can be done with MULTIPLY or ADD_RGBA to light up or adjust the color in regions of space, using spheres for point light sources or infinite cone for a spotlight. Using MULTIPLY with opacity=0 can be used to delete splats from a region of space. The splats can also be displaced in space using the XYZ values, and adjusted each frame to create smooth deformations in space.
+3 -3
View File
@@ -42,7 +42,7 @@ scene.add(splats);
### Optional parameters
You can create a `new SplatMesh()` with no options, which will create a new default instance with `.numSplats=0`. Alternatively, you can provide an input `url` to fetch and decode, `fileBytes`, or `packedSplats` (an existing collection of "packed" splats). Forge supports most splat file types, including .ply (including SuperSplat compressed), .splat, .ksplat, .spz. To load filetypes .splat and .ksplat (which can't be reliably auto-detected), use the optional `fileType` argument.
You can create a `new SplatMesh()` with no options, which will create a new default instance with `.numSplats=0`. Alternatively, you can provide an input `url` to fetch and decode, `fileBytes`, or `packedSplats` (an existing collection of "packed" splats). Spark supports most splat file types, including .ply (including SuperSplat/gsplat compressed), .splat, .ksplat, .spz. To load filetypes .splat and .ksplat (which can't be reliably auto-detected), use the optional `fileType` argument.
Constructor argument callbacks can be used like `constructSplats` to create a collection of splats procedurally at initialization, `onLoad` when loading and initialization completes, `onFrame` to update state every frame. Splat effects can be injected into the standard splat processing pipeline that operate in object-space and world-space via `objectModifier` and `worldModifier` respectively.
@@ -83,7 +83,7 @@ Additional properties on a `SplatMesh` instance:
| **skinning** | Optional `SplatSkinning` instance for animating splats with dual-quaternion skeletal animation. (default: `null`)
| **edits** | Optional list of `SplatEdit`s to apply to the mesh. If `null`, any `SplatEdit` children in the scene graph will be added automatically. (default: `null`)
| **splatRgba** | Optional `RgbaArray` to overwrite splat RGBA values with custom values. Useful for "baking" RGB and opacity edits into the `SplatMesh`. (default: `null`)
| **maxSh** | Maximum Spherical Harmonics level to use. Forge supports up to SH3. Call `updateGenerator()` after changing. (default: `3`)
| **maxSh** | Maximum Spherical Harmonics level to use. Spark supports up to SH3. Call `updateGenerator()` after changing. (default: `3`)
## `dispose()`
@@ -103,7 +103,7 @@ Call this whenever something changes in the splat processing pipeline, for examp
## `update(...)`
This is called automatically by `ForgeRenderer` and you should not have to call it. It updates parameters for the generated pipeline and calls `updateGenerator()` if the pipeline needs to change.
This is called automatically by `SparkRenderer` and you should not have to call it. It updates parameters for the generated pipeline and calls `updateGenerator()` if the pipeline needs to change.
## `raycast(raycaster, intersects: { distance, point, object}[])`
+9 -9
View File
@@ -4,27 +4,27 @@ One of the biggest challenges in real-time Gaussian splat rendering is sorting t
## Rendering data flow cycle
`ForgeRenderer` is a key component in Forge that manages this process. It traverses the visible THREE.js scene graph and compiles a complete list of all splats across the scene, generated by instances of `SplatMesh` in the scene hierarchy.
`SparkRenderer` is a key component in Spark that manages this process. It traverses the visible THREE.js scene graph and compiles a complete list of all splats across the scene, generated by instances of `SplatMesh` in the scene hierarchy.
Each `ForgeRenderer` has a default `ForgeViewpoint` that reads back a list of all splat viewpoint distances from the GPU, then determines the splat draw order using an efficient bucket sort algorithm, run in a background worker thread via `SplatWorker`. You can spawn additional `ForgeViewpoint`s to create multiple simultaneous render viewpoints.
Each `SparkRenderer` has a default `SparkViewpoint` that reads back a list of all splat viewpoint distances from the GPU, then determines the splat draw order using an efficient bucket sort algorithm, run in a background worker thread via `SplatWorker`. You can spawn additional `SparkViewpoint`s to create multiple simultaneous render viewpoints.
Finally, on the next THREE.js render() call, `ForgeRenderer` invokes a single instanced geometry draw call to draw all the scene's splats in the correct back-to-front order, merging with other opaque THREE.js geometry using the Z buffer. The sort order lags the render by at least one frame, but possibly more on older devices, but is not usually perceptible.
Finally, on the next THREE.js render() call, `SparkRenderer` invokes a single instanced geometry draw call to draw all the scene's splats in the correct back-to-front order, merging with other opaque THREE.js geometry using the Z buffer. The sort order lags the render by at least one frame, but possibly more on older devices, but is not usually perceptible.
```typescript
// Optionally add a ForgeRenderer to the scene to manage SplatMesh rendering.
// If none is created, Forge will create one for you automatically.
const forge = new ForgeRenderer({ renderer: webGlRenderer });
scene.add(forge);
// Optionally add a SparkRenderer to the scene to manage SplatMesh rendering.
// If none is created, Spark will create one for you automatically.
const spark = new SparkRenderer({ renderer: webGlRenderer });
scene.add(spark);
```
This design allows splats from distinct objects/scenes to coexist in space and sort correctly w.r.t. each other's splats. Splats from independent `SplatMesh`es are aggregated using a `SplatAccumulator`, which produces a `PackedSplats`, a collection splats stored in a cache-efficient 16-byte/splat format.
## "Programmable Splats"
Forge also uses this opportunity to run a user-programmable data pipeline on each splat on the GPU. The standard pipeline provides high-level functionality, such as applying rigid transforms, adjusting RGB / opacity, and spherical harmonics, but also color editing and perturbations (via `SplatEdit`) and a dual-quaternion skeletal animation system (`SplatSkinning`). The standard pipeline also allows injecting arbitrary code to modify each splat via `dyno` shader graph system.
Spark also uses this opportunity to run a user-programmable data pipeline on each splat on the GPU. The standard pipeline provides high-level functionality, such as applying rigid transforms, adjusting RGB / opacity, and spherical harmonics, but also color editing and perturbations (via `SplatEdit`) and a dual-quaternion skeletal animation system (`SplatSkinning`). The standard pipeline also allows injecting arbitrary code to modify each splat via `dyno` shader graph system.
`SplatMesh` derives from a more general base class `SplatGenerator`, which itself derives from `THREE.Object3D`. As such, it can be placed anywhere in the scene hierarchy and obeys expected local and global coordinate transforms. A `SplatGenerator` is the most general form of a "splat object", whose splats are produced programmatically via a `dyno` shader graph function that maps `{ index: "int" }` to `{ gsplat: "Gsplat" }`. A `SplatMesh` is a higher-level object that implements such a mapping, reading source splats from a template (loaded via a `url` constructor parameter or otherwise) at the given `index`, then applying functions such as transforming to world space.
In contrast, implementing a `SplatGenerator` gives you full control to write any function that programmatically computes a splat's attributes (center, scales, quaternion, rgba). These could be stateless (relying only on `index`, random-number generators, etc), or could rely on a complex combination of splat files, textures, and other global parameters for real-time procedural generation, and can vary with time to produce real-time animations.
The `dyno` shader graph system allows you to create these programmatic pipelines with Javascript code, which is synthesized into GLSL code and compiled and run on the GPU. This `dyno` system powers other components of Forge as well, such as `Readback` (which can perform any computation and read back the resulting value), used to compute the sort distance metric for pairs of splats and read them back for CPU sorting.
The `dyno` shader graph system allows you to create these programmatic pipelines with Javascript code, which is synthesized into GLSL code and compiled and run on the GPU. This `dyno` system powers other components of Spark as well, such as `Readback` (which can perform any computation and read back the resulting value), used to compute the sort distance metric for pairs of splats and read them back for CPU sorting.
+1 -1
View File
@@ -24,7 +24,7 @@
</svg>
</a>
</div>
<a rel="noopener noreferrer" class="contrast" aria-label="GitHub repository" href="https://github.com/forge-gfx/forge" target="_blank" style="margin-top: 5px;">
<a rel="noopener noreferrer" class="contrast" aria-label="GitHub repository" href="https://github.com/sparkjs-dev/spark" target="_blank" style="margin-top: 5px;">
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 496 475" class="icon-github">
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path>
</svg>
+8 -8
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Forge &#8226; Examples</title>
<title>Spark &#8226; Examples</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
@@ -221,7 +221,7 @@
if (initialLink) {
const path = initialLink.getAttribute('href');
iframe.src = path;
codeButton.href = `https://github.com/forge-gfx/forge/blob/main${path}`;
codeButton.href = `https://github.com/sparkjs-dev/spark/blob/main${path}`;
}
panel.querySelectorAll('.example-link').forEach((link) => {
@@ -232,7 +232,7 @@
document.querySelectorAll('.example-link').forEach(l => l.classList.remove('active'));
link.classList.add('active');
codeButton.href = `https://github.com/forge-gfx/forge/blob/main/${path.substring(1)}`;
codeButton.href = `https://github.com/sparkjs-dev/spark/blob/main/${path.substring(1)}`;
});
});
}
@@ -318,7 +318,7 @@
link.classList.add('active');
panel.querySelector(`[href="${path}"]`)?.classList.add('active');
codeButton.href = `https://github.com/forge-gfx/forge/blob/main${path}`;
codeButton.href = `https://github.com/sparkjs-dev/spark/blob/main${path}`;
closeMenu();
}
});
@@ -336,7 +336,7 @@
<div class="logo-section">
<a class="logo" href="/">
<svg>
<use xlink:href="#forge-logo" />
<use xlink:href="#spark-logo" />
</svg>
</a>
<!-- <span class="title">EXAMPLES</span> -->
@@ -353,7 +353,7 @@
<div class="header">
<a class="logo" href="/">
<svg style="width: 100%; height: 100%;">
<use xlink:href="#forge-logo" style="position: absolute; width: 100%; height: 100%;"/>
<use xlink:href="#spark-logo" style="position: absolute; width: 100%; height: 100%;"/>
</svg>
</a>
<span class="title">EXAMPLES</span>
@@ -375,7 +375,7 @@
</div>
<svg style="display: none" xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="forge-logo" viewBox="0 0 501 94" fill="none">
<symbol id="spark-logo" viewBox="0 0 501 94" fill="none">
<path d="M0.618225 2.26904H137.346L132.185 20.4418C88.172 20.805 78.5674 74.9667 120.859 79.8385C120.859 79.8385 120.859 79.8386 118.408 91.5049L20.8455 91.5634L20.8455 69.2102C20.8455 69.2102 58.0664 68.1922 51.5113 49.0215C44.9561 29.8507 13.0163 42.7018 0.618225 2.26904Z" fill="white"/>
<path d="M230.17 1.91944L226.42 21.9194H203.42V38.9194H224.17L220.42 58.9194H203.42V91.9194H179.045V19.0444L172.17 1.91944H230.17Z" fill="white"/>
<path d="M264.215 93.1694C256.048 93.1694 249.465 91.7944 244.465 89.0444C239.465 86.2111 235.798 81.4611 233.465 74.7944C231.215 68.0444 230.09 58.7944 230.09 47.0444C230.09 35.2944 231.215 26.0861 233.465 19.4194C235.798 12.6694 239.465 7.91944 244.465 5.16944C249.465 2.3361 256.048 0.919434 264.215 0.919434C272.381 0.919434 278.965 2.3361 283.965 5.16944C288.965 7.91944 292.59 12.6694 294.84 19.4194C297.173 26.0861 298.34 35.2944 298.34 47.0444C298.34 58.7944 297.173 68.0444 294.84 74.7944C292.59 81.4611 288.965 86.2111 283.965 89.0444C278.965 91.7944 272.381 93.1694 264.215 93.1694ZM272.09 46.7944C272.09 39.6278 271.84 34.1694 271.34 30.4194C270.923 26.5861 270.131 23.9611 268.965 22.5444C267.881 21.0444 266.298 20.2944 264.215 20.2944C262.215 20.2944 260.631 21.0444 259.465 22.5444C258.298 23.9611 257.465 26.5861 256.965 30.4194C256.548 34.1694 256.34 39.6278 256.34 46.7944C256.34 53.8778 256.548 59.3778 256.965 63.2944C257.465 67.2111 258.298 69.9611 259.465 71.5444C260.631 73.0444 262.215 73.7944 264.215 73.7944C266.298 73.7944 267.881 73.0444 268.965 71.5444C270.131 69.9611 270.923 67.2111 271.34 63.2944C271.84 59.3778 272.09 53.8778 272.09 46.7944Z" fill="white"/>
@@ -384,7 +384,7 @@
<path d="M439.87 1.91944H499.745L495.995 21.9194H471.12V35.6694H494.495L490.745 55.6694H471.12V71.9194H500.62L496.87 91.9194H446.745V19.0444L439.87 1.91944Z" fill="white"/>
</symbol>
</defs>
<use href="#forge-logo"/>
<use href="#spark-logo"/>
</svg>
</body>
</html>
+25 -25
View File
@@ -1,102 +1,102 @@
{
"robot-head.spz": {
"url": "https://forge.dev/assets/splats/robot-head.spz",
"url": "https://sparkjs.dev/assets/splats/robot-head.spz",
"directory": "splats"
},
"forge.spz": {
"url": "https://forge.dev/assets/splats/forge.spz",
"url": "https://sparkjs.dev/assets/splats/forge.spz",
"directory": "splats"
},
"butterfly-wings-closed.spz": {
"url": "https://forge.dev/assets/splats/butterfly-wings-closed.spz",
"url": "https://sparkjs.dev/assets/splats/butterfly-wings-closed.spz",
"directory": "splats"
},
"butterfly.spz": {
"url": "https://forge.dev/assets/splats/butterfly.spz",
"url": "https://sparkjs.dev/assets/splats/butterfly.spz",
"directory": "splats"
},
"butterfly-ai.spz": {
"url": "https://forge.dev/assets/splats/butterfly-ai.spz",
"url": "https://sparkjs.dev/assets/splats/butterfly-ai.spz",
"directory": "splats"
},
"fireplace.spz": {
"url": "https://forge.dev/assets/splats/fireplace.spz",
"url": "https://sparkjs.dev/assets/splats/fireplace.spz",
"directory": "splats"
},
"snow-street.spz": {
"url": "https://forge.dev/assets/splats/snow-street.spz",
"url": "https://sparkjs.dev/assets/splats/snow-street.spz",
"directory": "splats"
},
"branzino-amarin.spz": {
"url": "https://forge.dev/assets/splats/food/branzino-amarin.spz",
"url": "https://sparkjs.dev/assets/splats/food/branzino-amarin.spz",
"directory": "splats/food"
},
"burger-from-amboy.spz": {
"url": "https://forge.dev/assets/splats/food/burger-from-amboy.spz",
"url": "https://sparkjs.dev/assets/splats/food/burger-from-amboy.spz",
"directory": "splats/food"
},
"clams-and-caviar-by-ikoyi.spz": {
"url": "https://forge.dev/assets/splats/food/clams-and-caviar-by-ikoyi.spz",
"url": "https://sparkjs.dev/assets/splats/food/clams-and-caviar-by-ikoyi.spz",
"directory": "splats/food"
},
"coral-caviar.spz": {
"url": "https://forge.dev/assets/splats/food/coral-caviar.spz",
"url": "https://sparkjs.dev/assets/splats/food/coral-caviar.spz",
"directory": "splats/food"
},
"double-double-from-InNOut.spz": {
"url": "https://forge.dev/assets/splats/food/double-Double-from-InNOut.spz",
"url": "https://sparkjs.dev/assets/splats/food/double-Double-from-InNOut.spz",
"directory": "splats/food"
},
"gyro.spz": {
"url": "https://forge.dev/assets/splats/food/gyro.spz",
"url": "https://sparkjs.dev/assets/splats/food/gyro.spz",
"directory": "splats/food"
},
"iberico-sandwich-by-reserve.spz": {
"url": "https://forge.dev/assets/splats/food/iberico-sandwich-by-reserve.spz",
"url": "https://sparkjs.dev/assets/splats/food/iberico-sandwich-by-reserve.spz",
"directory": "splats/food"
},
"primerib-tamos.spz": {
"url": "https://forge.dev/assets/splats/food/primerib-tamos.spz",
"url": "https://sparkjs.dev/assets/splats/food/primerib-tamos.spz",
"directory": "splats/food"
},
"steaksandwich-mels.spz": {
"url": "https://forge.dev/assets/splats/food/steaksandwich-mels.spz",
"url": "https://sparkjs.dev/assets/splats/food/steaksandwich-mels.spz",
"directory": "splats/food"
},
"pad-thai.spz": {
"url": "https://forge.dev/assets/splats/food/pad-thai.spz",
"url": "https://sparkjs.dev/assets/splats/food/pad-thai.spz",
"directory": "splats/food"
},
"tomahawk-niku.spz": {
"url": "https://forge.dev/assets/splats/food/tomahawk-niku.spz",
"url": "https://sparkjs.dev/assets/splats/food/tomahawk-niku.spz",
"directory": "splats/food"
},
"rubberduck.glb": {
"url": "https://forge.dev/assets/models/rubberduck.glb",
"url": "https://sparkjs.dev/assets/models/rubberduck.glb",
"directory": "models"
},
"table.glb": {
"url": "https://forge.dev/assets/models/table.glb",
"url": "https://sparkjs.dev/assets/models/table.glb",
"directory": "models"
},
"butterfly.png": {
"url": "https://forge.dev/assets/images/butterfly.png",
"url": "https://sparkjs.dev/assets/images/butterfly.png",
"directory": "images"
},
"sky.jpeg": {
"url": "https://forge.dev/assets/images/sky.jpeg",
"url": "https://sparkjs.dev/assets/images/sky.jpeg",
"directory": "images"
},
"furry-logo-pedestal.spz": {
"url": "https://forge.dev/assets/splats/furry-logo-pedestal.spz",
"url": "https://sparkjs.dev/assets/splats/furry-logo-pedestal.spz",
"directory": "splats"
},
"cat.spz": {
"url": "https://forge.dev/assets/splats/cat.spz",
"url": "https://sparkjs.dev/assets/splats/cat.spz",
"directory": "splats"
},
"arrow.glb": {
"url": "https://forge.dev/assets/models/arrow.glb",
"url": "https://sparkjs.dev/assets/models/arrow.glb",
"directory": "models"
}
}
+7 -7
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • VFX Dynamic Lighting</title>
<title>Spark • VFX Dynamic Lighting</title>
<style>
html,
body {
@@ -68,7 +68,7 @@
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
@@ -77,13 +77,13 @@
import {
SplatMesh,
SplatEdit,
ForgeRenderer,
SparkRenderer,
SplatEditSdf,
SplatEditSdfType,
SplatEditRgbaBlendMode,
FpsMovement,
PointerControls,
} from "@forge-gfx/forge";
} from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const canvas = document.getElementById("canvas");
@@ -96,9 +96,9 @@
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)
// Create a ForgeRenderer and add it to the scene to render all the Gsplats.
const forge = new ForgeRenderer({ renderer });
scene.add(forge);
// Create a SparkRenderer and add it to the scene to render all the Gsplats.
const spark = new SparkRenderer({ renderer });
scene.add(spark);
const splatURL = await getAssetFileURL("fireplace.spz");
const fireplace = new SplatMesh({ url: splatURL });
+8 -8
View File
@@ -2,7 +2,7 @@
<head>
<meta charset="utf-8">
<title>Forge • Editor</title>
<title>Spark • Editor</title>
<style>
body {
margin: 0;
@@ -53,7 +53,7 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js",
"@sparkjs-dev/spark": "/dist/spark.module.js",
"lil-gui": "../node_modules/lil-gui/dist/lil-gui.esm.js",
"stats.js": "../node_modules/stats.js/build/stats.min.js"
}
@@ -68,7 +68,7 @@
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GUI } from "lil-gui";
import Stats from "stats.js";
import { constructGrid, ForgeControls, ForgeRenderer, SplatMesh, textSplats, dyno, transcodeSpz } from "@forge-gfx/forge";
import { constructGrid, SparkControls, SparkRenderer, SplatMesh, textSplats, dyno, transcodeSpz } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const scene = new THREE.Scene();
@@ -77,7 +77,7 @@
const canvas = document.getElementById("canvas");
const renderer = new THREE.WebGLRenderer({ canvas });
const forge = new ForgeRenderer({ renderer });
const spark = new SparkRenderer({ renderer });
function handleResize() {
const width = canvas.clientWidth;
@@ -119,7 +119,7 @@
container: document.getElementById("second-gui")
}).close();
const controls = new ForgeControls({ canvas });
const controls = new SparkControls({ canvas });
// Setup mouse controls to orbit the camera around
const orbitControls = new OrbitControls(camera, renderer.domElement);
@@ -463,7 +463,7 @@
gui.add(guiOptions, "stats").name("Show frame stats").onChange((value) => {
stats.dom.style.display = value ? "block" : "none";
});
gui.add(forge.defaultView, "sortRadial").name("Radial sort").listen();
gui.add(spark.defaultView, "sortRadial").name("Radial sort").listen();
gui.add(grid, "opacity", 0, 1, 0.01).name("Grid opacity").listen();
const normalColor = dyno.dynoBool(false);
@@ -472,8 +472,8 @@
const splatsFolder = secondGui.addFolder("Files");
const editFolder = gui.addFolder("Edit Splats").close();
editFolder.add(forge, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
editFolder.add(forge, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
editFolder.add(spark, "maxStdDev", 0.1, 3.0, 0.01).name("Max Gsplat stddev").listen();
editFolder.add(spark, "falloff", 0, 1, 0.01).name("Gaussian falloff").listen();
function updateFrameSplats() {
frame.children.forEach((child) => {
+6 -6
View File
@@ -2,7 +2,7 @@
<head>
<meta charset="utf-8">
<title>Forge • Environment Mapping</title>
<title>Spark • Environment Mapping</title>
<style>
body {
margin: 0;
@@ -33,13 +33,13 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh, ForgeControls, ForgeRenderer } from "@forge-gfx/forge";
import { SplatMesh, SparkControls, SparkRenderer } from "@sparkjs-dev/spark";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
@@ -51,8 +51,8 @@
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement)
// Explicitly create a ForgeRenderer to render environment maps
const forge = new ForgeRenderer({ renderer });
// Explicitly create a SparkRenderer to render environment maps
const spark = new SparkRenderer({ renderer });
const splatURL = await getAssetFileURL("fireplace.spz");
const background = new SplatMesh({ url: splatURL });
@@ -88,7 +88,7 @@
// Make sure we only launch async renderEnvMap once
if (!renderedEnvMap) {
renderedEnvMap = true;
forge.renderEnvMap({
spark.renderEnvMap({
scene,
// World center to render from
worldCenter: duck.position,
+3 -3
View File
@@ -2,7 +2,7 @@
<head>
<meta charset="utf-8">
<title>Forge • Carousel</title>
<title>Spark • Carousel</title>
<style>
body {
margin: 0;
@@ -16,13 +16,13 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Hello World</title>
<title>Spark • Hello World</title>
<style>
body {
margin: 0;
@@ -16,13 +16,13 @@
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const scene = new THREE.Scene();
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Multiple Splats</title>
<title>Spark • Multiple Splats</title>
<link rel="stylesheet" href="style.css">
</head>
@@ -25,13 +25,13 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { EXRLoader } from "three/addons/loaders/EXRLoader.js";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+1 -1
View File
@@ -4,7 +4,7 @@ export async function getAssetFileURL(assetFile) {
const assetsDirectory = "/examples/assets/";
const assetsInfo = await response.json();
let url = assetsInfo[assetFile].url;
if (window.forgeLocalAssets) {
if (window.sparkLocalAssets) {
url = `${assetsDirectory}${assetsInfo[assetFile].directory}/${assetFile}`;
}
return url;
+4 -4
View File
@@ -1,11 +1,11 @@
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
// Add loading icon in the center of the screen
function addLoader(loading_icon_color) {
const loaderIcon = new Image();
loaderIcon.src = `/examples/js/forge_loading_${loading_icon_color}.gif`;
loaderIcon.id = "_forge_loader";
loaderIcon.src = `/examples/js/spark_loading_${loading_icon_color}.gif`;
loaderIcon.id = "_spark_loader";
loaderIcon.style.position = "absolute";
loaderIcon.style.zIndex = 99999;
loaderIcon.style.left = "50%";
@@ -16,7 +16,7 @@ function addLoader(loading_icon_color) {
}
function removeLoader() {
const el = document.getElementById("_forge_loader");
const el = document.getElementById("_spark_loader");
el.parentNode.removeChild(el);
}

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Multiple Splats</title>
<title>Spark • Multiple Splats</title>
<style>
body {
margin: 0;
@@ -28,13 +28,13 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh } from "@forge-gfx/forge";
import { SplatMesh } from "@sparkjs-dev/spark";
import { EXRLoader } from "three/addons/loaders/EXRLoader.js";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
+7 -7
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Multiple Viewpoints</title>
<title>Spark • Multiple Viewpoints</title>
<link rel="stylesheet" href="style.css">
</head>
@@ -18,13 +18,13 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { ForgeRenderer, ForgeViewpoint, SplatMesh } from "@forge-gfx/forge";
import { SparkRenderer, SparkViewpoint, SplatMesh } from "@sparkjs-dev/spark";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
@@ -37,8 +37,8 @@
renderer.setSize(window.innerWidth, window.innerHeight - header_height);
document.body.appendChild(renderer.domElement)
// Explicitly create a ForgeRenderer in the scene to spawn new viewpoints
const forge = new ForgeRenderer({ renderer });
// Explicitly create a SparkRenderer in the scene to spawn new viewpoints
const spark = new SparkRenderer({ renderer });
const splatURL = await getAssetFileURL("butterfly.spz");
const butterfly = new SplatMesh({ url: splatURL });
@@ -52,7 +52,7 @@
const height = 240;
const screen = new THREE.Mesh(
new THREE.PlaneGeometry(0.5 * camera.near, 0.5 * camera.near * height / width),
new THREE.MeshBasicMaterial({ map: ForgeViewpoint.EMPTY_TEXTURE }),
new THREE.MeshBasicMaterial({ map: SparkViewpoint.EMPTY_TEXTURE }),
);
screen.position.set(screenPos.x, screenPos.y, -1).multiplyScalar(camera.near);
scene.add(screen);
@@ -88,7 +88,7 @@
// Spawn a new viewpoint that auto-updates its sort order, double
// buffered in case the texture is used recursively in the scene
const viewpoint = forge.newViewpoint({
const viewpoint = spark.newViewpoint({
autoUpdate: true,
camera: viewCamera,
target: { width, height, doubleBuffer: true },
+6 -6
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Particle Animation</title>
<title>Spark • Particle Animation</title>
<style>
html, body {
margin: 0;
@@ -26,18 +26,18 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"lil-gui": "/examples/js/vendor/lil-gui/dist/lil-gui.esm.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import {
ForgeRenderer,
SparkRenderer,
PointerControls,
SplatMesh,
VRButton,
} from "@forge-gfx/forge";
} from "@sparkjs-dev/spark";
import GUI from "lil-gui";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
@@ -273,8 +273,8 @@
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const forge = new ForgeRenderer({ renderer });
scene.add(forge);
const spark = new SparkRenderer({ renderer });
scene.add(spark);
function handleResize() {
const width = window.innerWidth;
+6 -6
View File
@@ -2,7 +2,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Particle Simulation</title>
<title>Spark • Particle Simulation</title>
<style>
html, body {
margin: 0;
@@ -19,18 +19,18 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"lil-gui": "/examples/js/vendor/lil-gui/dist/lil-gui.esm.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import {
ForgeRenderer,
SparkRenderer,
PointerControls,
SplatMesh,
generators,
} from "@forge-gfx/forge";
} from "@sparkjs-dev/spark";
import GUI from "lil-gui";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
@@ -54,8 +54,8 @@
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const forge = new ForgeRenderer({ renderer });
scene.add(forge);
const spark = new SparkRenderer({ renderer });
scene.add(spark);
function handleResize() {
const width = window.innerWidth;
+4 -4
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Procedural Splats</title>
<title>Spark • Procedural Splats</title>
<style>
body {
margin: 0;
@@ -16,13 +16,13 @@
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { ForgeControls, SplatMesh, constructGrid, constructAxes, textSplats, imageSplats } from "@forge-gfx/forge";
import { SparkControls, SplatMesh, constructGrid, constructAxes, textSplats, imageSplats } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const scene = new THREE.Scene();
@@ -114,7 +114,7 @@
image.position.set(-0.5, -1, -2);
scene.add(image);
const controls = new ForgeControls({ canvas: renderer.domElement });
const controls = new SparkControls({ canvas: renderer.domElement });
renderer.setAnimationLoop(function animate(time) {
controls.update(camera);
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Raycasting</title>
<title>Spark • Raycasting</title>
<style>
body {
margin: 0;
@@ -26,13 +26,13 @@
{
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh, PackedSplats } from "@forge-gfx/forge";
import { SplatMesh, PackedSplats } from "@sparkjs-dev/spark";
import { getAssetFileURL } from "/examples/js/get-asset-url.js";
const scene = new THREE.Scene();
+15 -15
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Forge • Viewer</title>
<title>Spark • Viewer</title>
<style>
* {
box-sizing: border-box;
@@ -38,7 +38,7 @@
align-items: center;
}
.forge-canvas {
.spark-canvas {
display: block;
position: fixed;
top: 0;
@@ -179,22 +179,22 @@
"imports": {
"three": "/examples/js/vendor/three/build/three.module.js",
"three/addons/": "/examples/js/vendor/three/examples/jsm/",
"@forge-gfx/forge": "/dist/forge.module.js"
"@sparkjs-dev/spark": "/dist/spark.module.js"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { SplatMesh, transcodeSpz } from "@forge-gfx/forge";
import { SplatMesh, transcodeSpz } from "@sparkjs-dev/spark";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const forgeCanvasEl = document.querySelector(".forge-canvas");
const sparkCanvasEl = document.querySelector(".spark-canvas");
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.set(0, 0, 1);
const renderer = new THREE.WebGLRenderer({canvas: forgeCanvasEl});
const width = forgeCanvasEl.clientWidth;
const height = forgeCanvasEl.clientHeight;
const renderer = new THREE.WebGLRenderer({canvas: sparkCanvasEl});
const width = sparkCanvasEl.clientWidth;
const height = sparkCanvasEl.clientHeight;
var fileName;
var fileBytes;
renderer.setSize(width, height, false);
@@ -250,13 +250,13 @@
};
dropZoneEl.addEventListener('dragover', onDragover);
forgeCanvasEl.addEventListener('dragover', onDragover);
sparkCanvasEl.addEventListener('dragover', onDragover);
dropZoneEl.addEventListener('dragleave', onDragLeave);
forgeCanvasEl.addEventListener('dragleave', onDragLeave);
sparkCanvasEl.addEventListener('dragleave', onDragLeave);
dropZoneEl.addEventListener('drop', onDrop);
forgeCanvasEl.addEventListener('drop', onDrop);
sparkCanvasEl.addEventListener('drop', onDrop);
const fileInput = document.querySelector('#file-input');
fileInput.onchange = function(event) {
@@ -332,7 +332,7 @@
</script>
<div class="canvas-container invisible">
<div class="logo">
<a href="/"><img src="forge-logo.svg" alt="Forge Logo" /></a>
<a href="/"><img src="spark-logo.svg" alt="Spark Logo" /></a>
</div>
<button class="spz-button">
<span class="icon" aria-hidden="true">
@@ -341,12 +341,12 @@
<span class="label">.spz</span>
</button>
</button>
<canvas class="forge-canvas"></canvas>
<canvas class="spark-canvas"></canvas>
</div>
<div class="container">
<div class="drop-container">
<div class="hero-logo">
<a href="/"><img src="forge-viewer.svg" alt="Forge Viewer Logo" /></a>
<a href="/"><img src="spark-viewer.svg" alt="Spark Viewer Logo" /></a>
</div>
<div class="drop-zone">Drag and drop a splat file here</div>
<label for="file-input" class="button upload"><img class="upload-icon" src="upload-icon.svg" alt="upload" />Choose file</label>

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 689 B

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+4 -4
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Forge Examples</title>
<title>Spark Examples</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
@@ -120,10 +120,10 @@
</head>
<body>
<header class="header">
<h1>Forge Examples</h1>
<h1>Spark Examples</h1>
<ul id="resources" class="resources">
<li><a href="https://forge.dev/">forge.dev</a></li>
<li><a href="https://github.com/forge-gfx/forge">GitHub</a></li>
<li><a href="https://sparkjs.dev/">sparkjs.dev</a></li>
<li><a href="https://github.com/sparkjs-dev/spark">GitHub</a></li>
</ul>
</header>
+5 -5
View File
@@ -1,19 +1,19 @@
site_name: Forge
site_url: https://forge.dev/
site_name: Spark
site_url: https://sparkjs.dev/
theme:
name: material
custom_dir: docs/overrides
features:
- content.code.copy
extra_css:
- stylesheets/forge.css
- stylesheets/spark.css
nav:
- Home: index.md
- Getting Started: docs/index.md
- Overview: docs/overview.md
- System Design: docs/system-design.md
- ForgeRenderer: docs/forge-renderer.md
- ForgeViewpoint: docs/forge-viewpoint.md
- SparkRenderer: docs/spark-renderer.md
- SparkViewpoint: docs/spark-viewpoint.md
- SplatMesh: docs/splat-mesh.md
- PackedSplats: docs/packed-splats.md
- Loading Gsplats: docs/loading-splats.md
+12 -11
View File
@@ -1,15 +1,16 @@
{
"name": "@forge-gfx/forge",
"version": "0.0.1",
"name": "@sparkjs-dev/spark",
"version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@forge-gfx/forge",
"version": "0.0.1",
"name": "@sparkjs-dev/spark",
"version": "0.2.0",
"license": "MIT",
"dependencies": {
"fflate": "^0.8.2",
"forge-internal-rs": "file:rust/forge-internal-rs/pkg"
"spark-internal-rs": "file:rust/spark-internal-rs/pkg"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
@@ -1749,10 +1750,6 @@
"node": ">=8"
}
},
"node_modules/forge-internal-rs": {
"resolved": "rust/forge-internal-rs/pkg",
"link": true
},
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
@@ -2556,6 +2553,10 @@
"node": ">=0.10.0"
}
},
"node_modules/spark-internal-rs": {
"resolved": "rust/spark-internal-rs/pkg",
"link": true
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -2920,8 +2921,8 @@
"node": ">=6"
}
},
"rust/forge-internal-rs/pkg": {
"name": "forge-internal-rs",
"rust/spark-internal-rs/pkg": {
"name": "spark-internal-rs",
"version": "0.1.0",
"license": "Proprietary"
}
+10 -10
View File
@@ -1,16 +1,16 @@
{
"name": "@forge-gfx/forge",
"version": "0.1.0",
"name": "@sparkjs-dev/spark",
"version": "0.2.0",
"description": "An advanced 3D Gaussian Splatting renderer for THREE.js",
"homepage": "https://forge.dev/",
"homepage": "https://sparkjs.dev/",
"type": "module",
"main": "./dist/forge.cjs.js",
"module": "./dist/forge.module.js",
"main": "./dist/spark.cjs.js",
"module": "./dist/spark.module.js",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/forge.module.js",
"require": "./dist/forge.cjs.js"
"import": "./dist/spark.module.js",
"require": "./dist/spark.cjs.js"
}
},
"scripts": {
@@ -22,7 +22,7 @@
"build:production": "vite build --mode production",
"build:wasm": "node rust/build_wasm.js",
"build:watch": "onchange 'src/**/*.{ts,glsl}' -- npm run build:dev",
"clean": "rm -rf dist/ && rm -rf node_modules/ && rm -rf rust/target/ && rm -rf rust/forge-internal-rs/pkg/ && npm run assets:clean",
"clean": "rm -rf dist/ && rm -rf node_modules/ && rm -rf rust/target/ && rm -rf rust/spark-internal-rs/pkg/ && npm run assets:clean",
"dev": "npm run build:dev && (vite --host & npm run build:watch)",
"docs": "node scripts/clean-site-files.js && node scripts/copy-site-files.js && mkdocs serve",
"format": "biome format .",
@@ -38,7 +38,7 @@
"start": "npm run dev",
"test": "node --no-warnings --loader ts-node/esm --test test/**/*.test.ts"
},
"repository": "forge-fgx/forge",
"repository": "sparkjs-dev/spark",
"files": ["dist"],
"license": "MIT",
"devDependencies": {
@@ -57,7 +57,7 @@
},
"dependencies": {
"fflate": "^0.8.2",
"forge-internal-rs": "file:rust/forge-internal-rs/pkg"
"spark-internal-rs": "file:rust/spark-internal-rs/pkg"
},
"keywords": ["3d", "three.js", "gsplats", "3dgs", "gaussian", "splats"]
}
+1 -1
View File
@@ -1,2 +1,2 @@
target/
forge-internal-rs/pkg/
spark-internal-rs/pkg/
+10 -10
View File
@@ -26,16 +26,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "forge-internal-rs"
version = "0.1.0"
dependencies = [
"anyhow",
"half",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "half"
version = "2.6.0"
@@ -92,6 +82,16 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "spark-internal-rs"
version = "0.1.0"
dependencies = [
"anyhow",
"half",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "syn"
version = "2.0.100"
+2 -2
View File
@@ -1,6 +1,6 @@
[workspace]
members = [
"forge-internal-rs",
"spark-internal-rs",
]
resolver = "2"
@@ -9,7 +9,7 @@ rust-version = "1.82"
edition = "2021"
license = "Proprietary"
authors = ["World Labs Technologies"]
repository = "https://github.com/forge-gfx/forge"
repository = "https://github.com/sparkjs-dev/spark"
[workspace.dependencies]
anyhow = "1.0.98"
+1 -1
View File
@@ -20,5 +20,5 @@ if (-not (Get-Command wasm-pack -ErrorAction SilentlyContinue)) {
}
# Change directory and build using wasm-pack
Set-Location -Path "./forge-internal-rs"
Set-Location -Path "./spark-internal-rs"
wasm-pack build --target web
+1 -1
View File
@@ -16,7 +16,7 @@ rustup target add wasm32-unknown-unknown
# Make sure wasm-pack is installed
cargo install wasm-pack
cd forge-internal-rs
cd spark-internal-rs
# Build the project
wasm-pack build --target web
@@ -1,5 +1,5 @@
[package]
name = "forge-internal-rs"
name = "spark-internal-rs"
version = "0.1.0"
rust-version.workspace = true
edition.workspace = true
@@ -1,6 +1,6 @@
# forge-internal-rs
# spark-internal-rs
Rust WebAssembly functions for forge-internal.
Rust WebAssembly functions for spark-internal.
## Installing build tools
@@ -21,15 +21,15 @@ cargo install wasm-pack
## Building
Run the following script inside `forge-internal/rust`:
Run the following script inside `spark-internal/rust`:
```
./build_rust_wasm.sh
```
You can also build it manually by running these commands:
```
cd forge-internal-rs
cd spark-internal-rs
wasm-pack build --target web
```
The generated files will be in the `pkg/` subdirectory, which is already symlinked in `forge-internal/package.json`.
The generated files will be in the `pkg/` subdirectory, which is already symlinked in `spark-internal/package.json`.
+1 -1
View File
@@ -6,7 +6,7 @@ import path from "node:path";
import { URL } from "node:url";
// Import directly from source to avoid worker system
import { transcodeSpz } from "../dist/forge.module.js";
import { transcodeSpz } from "../dist/spark.module.js";
async function main() {
const args = process.argv.slice(2);
+2 -2
View File
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
import { rm } from "node:fs/promises";
import path from "node:path";
const siteRemoteURL = "git@github.com:forge-gfx/forge-gfx.github.io.git";
const siteRemoteURL = "git@github.com:sparkjs-dev/sparkjs-dev.github.io.git";
const siteRepoDir = "site-repo";
const siteBuild = "site";
@@ -64,5 +64,5 @@ run("git merge --ff-only origin/main", { cwd: siteRepoDir });
await emptyDirectory(siteRepoDir);
await copyDir(siteBuild, siteRepoDir);
run("git add .", { cwd: siteRepoDir });
run('git commit -m "Update Forge site"', { cwd: siteRepoDir });
run('git commit -m "Update Spark site"', { cwd: siteRepoDir });
run("git push origin main", { cwd: siteRepoDir });
+1 -1
View File
@@ -535,7 +535,7 @@ export class PackedSplats {
// Executes a dyno program specified by generator which is any DynoBlock that
// maps { index: "int" } to { gsplat: Gsplat }. This is called in
// ForgeRenderer.updateInternal() to re-generate Gsplats in the scene for
// SparkRenderer.updateInternal() to re-generate Gsplats in the scene for
// SplatGenerator instances whose version is newer than what was generated
// for it last time.
generate({
+1 -1
View File
@@ -9,7 +9,7 @@ import { getTextureSize } from "./utils";
// Readback can be used to run a Dyno program that maps an index to a 32-bit
// RGBA8 value, which is the only allowed, portable readback format for WebGL2.
// Using data packing and conversion you can read back any 32-bit value, which
// Forge uses to read back 2 float16 Gsplat distance values per index.
// Spark uses to read back 2 float16 Gsplat distance values per index.
export type Rgba8Readback = Dyno<{ index: "int" }, { rgba8: "vec4" }>;
+52 -52
View File
@@ -1,8 +1,8 @@
import * as THREE from "three";
import { ForgeViewpoint, type ForgeViewpointOptions } from "./ForgeViewpoint";
import { PackedSplats } from "./PackedSplats";
import { RgbaArray } from "./RgbaArray";
import { SparkViewpoint, type SparkViewpointOptions } from "./SparkViewpoint";
import { type GeneratorMapping, SplatAccumulator } from "./SplatAccumulator";
import { SplatEdit } from "./SplatEdit";
import { SplatGenerator, SplatModifier } from "./SplatGenerator";
@@ -25,7 +25,7 @@ import {
withinCoorientDist,
} from "./utils";
// ForgeRenderer aggregates splats from multiple generators into a single
// SparkRenderer aggregates splats from multiple generators into a single
// accumulated collection per frame. In normal operation we only need a
// maximum of 3 accumulators: One currently being viewed, one currently
// being sorted, and one more for generating the next frame. Accumulators
@@ -36,17 +36,17 @@ import {
const MAX_ACCUMULATORS = 5;
// Scene.onBeforeRender monkey-patch to
// inject a ForgeRenderer into a scene with SplatMeshes if there isn't
// inject a SparkRenderer into a scene with SplatMeshes if there isn't
// one already. Restore original Scene.onBeforeRenderer and Scene.add when done.
let hasSplatMesh = false;
let hasForgeRenderer = false;
let hasSparkRenderer = false;
let forgeRendererInstance: ForgeRenderer;
let sparkRendererInstance: SparkRenderer;
const sceneAdd = THREE.Scene.prototype.add;
THREE.Scene.prototype.add = function (object) {
hasSplatMesh = hasSplatMesh || object instanceof SplatMesh;
hasForgeRenderer = hasForgeRenderer || object instanceof ForgeRenderer;
hasSparkRenderer = hasSparkRenderer || object instanceof SparkRenderer;
sceneAdd.call(this, object);
return this;
};
@@ -56,22 +56,22 @@ THREE.Scene.prototype.onBeforeRender = function (renderer) {
if (!hasSplatMesh) {
return;
}
if (!hasForgeRenderer) {
const forge = forgeRendererInstance || new ForgeRenderer({ renderer });
this.add(forge);
if (!hasSparkRenderer) {
const spark = sparkRendererInstance || new SparkRenderer({ renderer });
this.add(spark);
}
THREE.Scene.prototype.onBeforeRender = sceneOnBeforeRender;
THREE.Scene.prototype.add = sceneAdd;
};
export type ForgeRendererOptions = {
// Pass in your THREE.WebGLRenderer instance so Forge can perform work
export type SparkRendererOptions = {
// Pass in your THREE.WebGLRenderer instance so Spark can perform work
// outside the usual render loop. Should be created with antialias: false
// (default setting) as WebGL anti-aliasing doesn't improve Gaussian Splatting
// rendering and significantly reduces performance.
renderer: THREE.WebGLRenderer;
// Pass in a THREE.Clock to synchronize time-based effects across different
// systems. Alternatively, you can set the ForgeRenderer properties time and
// systems. Alternatively, you can set the SparkRenderer properties time and
// deltaTime directly. (default: new THREE.Clock)
clock?: THREE.Clock;
// Controls whether to check and automatically update Gsplat collection after
@@ -81,7 +81,7 @@ export type ForgeRendererOptions = {
// this must be false in order to complete rendering as soon as possible.
// (default: false)
preUpdate?: boolean;
// Distance threshold for ForgeRenderer movement triggering a Gsplat update at
// Distance threshold for SparkRenderer movement triggering a Gsplat update at
// the new origin. (default: 1.0)
originDistance?: number;
// Maximum standard deviations from the center to render Gaussians. Values
@@ -109,16 +109,16 @@ export type ForgeRendererOptions = {
// 1.0 clips any centers that are exactly out of bounds, while 1.4 clips
// centers that are 40% beyond the bounds. (default: 1.4)
clipXY?: number;
// Configures the ForgeViewpointOptions for the default ForgeViewpoint
// associated with this ForgeRenderer. Notable option: sortRadial (sort by
// Configures the SparkViewpointOptions for the default SparkViewpoint
// associated with this SparkRenderer. Notable option: sortRadial (sort by
// radial distance or Z-depth)
view?: ForgeViewpointOptions;
view?: SparkViewpointOptions;
};
export class ForgeRenderer extends THREE.Mesh {
export class SparkRenderer extends THREE.Mesh {
renderer: THREE.WebGLRenderer;
material: THREE.ShaderMaterial;
uniforms: ReturnType<typeof ForgeRenderer.makeUniforms>;
uniforms: ReturnType<typeof SparkRenderer.makeUniforms>;
autoUpdate: boolean;
preUpdate: boolean;
@@ -140,10 +140,10 @@ export class ForgeRenderer extends THREE.Mesh {
private freeAccumulators: SplatAccumulator[];
// Total number of accumulators currently allocated
private accumulatorCount: number;
// Default ForgeViewpoint used for rendering to the canvas
defaultView: ForgeViewpoint;
// List of ForgeViewpoints with autoUpdate enabled
autoViewpoints: ForgeViewpoint[] = [];
// Default SparkViewpoint used for rendering to the canvas
defaultView: SparkViewpoint;
// List of SparkViewpoints with autoUpdate enabled
autoViewpoints: SparkViewpoint[] = [];
// Dynos used to transform Gsplats to the accumulator coordinate system
private rotateToAccumulator = new DynoVec4({ value: new THREE.Quaternion() });
@@ -159,7 +159,7 @@ export class ForgeRenderer extends THREE.Mesh {
// Should be set to the defaultView, but can be temporarily changed to another
// viewpoint using prepareViewpoint() for rendering from a different viewpoint.
viewpoint: ForgeViewpoint;
viewpoint: SparkViewpoint;
// Holds data needed to perform a scheduled Gsplat update.
private pendingUpdate: {
@@ -167,8 +167,8 @@ export class ForgeRenderer extends THREE.Mesh {
originToWorld: THREE.Matrix4;
} | null = null;
// Internal ForgeViewpoint used for environment map rendering.
private envViewpoint: ForgeViewpoint | null = null;
// Internal SparkViewpoint used for environment map rendering.
private envViewpoint: SparkViewpoint | null = null;
// Data and buffers used for environment map rendering
private static cubeRender: {
@@ -179,8 +179,8 @@ export class ForgeRenderer extends THREE.Mesh {
} | null = null;
private static pmrem: THREE.PMREMGenerator | null = null;
constructor(options: ForgeRendererOptions) {
const uniforms = ForgeRenderer.makeUniforms();
constructor(options: SparkRendererOptions) {
const uniforms = SparkRenderer.makeUniforms();
const shaders = getShaders();
const material = new THREE.ShaderMaterial({
glslVersion: THREE.GLSL3,
@@ -240,19 +240,19 @@ export class ForgeRenderer extends THREE.Mesh {
this.accumulatorCount += 1;
}
// Create a default ForgeViewpoint that is used when we call render()
// Create a default SparkViewpoint that is used when we call render()
// on the scene and has the sorted Gsplat collection from that viewpoint.
this.defaultView = new ForgeViewpoint({
this.defaultView = new SparkViewpoint({
...options.view,
autoUpdate: true,
forge: this,
spark: this,
});
this.viewpoint = this.defaultView;
this.prepareViewpoint(this.viewpoint);
this.clock = options.clock ? cloneClock(options.clock) : new THREE.Clock();
forgeRendererInstance = this;
sparkRendererInstance = this;
}
static makeUniforms() {
@@ -323,15 +323,15 @@ export class ForgeRenderer extends THREE.Mesh {
}
}
newViewpoint(options: ForgeViewpointOptions) {
// Create a new ForgeViewpoint for this ForgeRenderer.
// Note that every ForgeRenderer has an initial forge.defaultView: ForgeViewpoint
newViewpoint(options: SparkViewpointOptions) {
// Create a new SparkViewpoint for this SparkRenderer.
// Note that every SparkRenderer has an initial spark.defaultView: SparkViewpoint
// from construction, which is used for the default canvas render loop.
// Calling this method allows you to create additional viewpoints, which can be
// updated automatically each frame (performing Gsplat sorting every time there
// is an update), or updated on-demand for controlled rendering for video render
// or similar applications.
return new ForgeViewpoint({ ...options, forge: this });
return new SparkViewpoint({ ...options, spark: this });
}
onBeforeRender(
@@ -339,7 +339,7 @@ export class ForgeRenderer extends THREE.Mesh {
scene: THREE.Scene,
camera: THREE.Camera,
) {
// Called by Three.js before rendering this ForgeRenderer.
// Called by Three.js before rendering this SparkRenderer.
// At this point we can't modify the geometry or material, all these must
// be set in the scene already before this is called. Update the uniforms
// to render the Gsplats from the current active viewpoint.
@@ -433,7 +433,7 @@ export class ForgeRenderer extends THREE.Mesh {
// Note that the client expects to be able to call render() at any point
// to update the canvas, so we must switch the viewpoint back to
// defaultView when we're finished.
prepareViewpoint(viewpoint?: ForgeViewpoint) {
prepareViewpoint(viewpoint?: SparkViewpoint) {
this.viewpoint = viewpoint ?? this.viewpoint;
if (this.viewpoint.display) {
@@ -449,13 +449,13 @@ export class ForgeRenderer extends THREE.Mesh {
}
}
// If forge.autoUpdate is false then you must manually call
// forge.update({ scene }) to have the scene Gsplats be re-generated.
// If spark.autoUpdate is false then you must manually call
// spark.update({ scene }) to have the scene Gsplats be re-generated.
update({
scene,
viewToWorld,
}: { scene: THREE.Scene; viewToWorld?: THREE.Matrix4 }) {
// Compute the transform for the ForgeRenderer to use as origin
// Compute the transform for the SparkRenderer to use as origin
// for Gsplat generation and accumulation.
const originToWorld = this.matrixWorld.clone();
// Either do the update now, or in the next "tick" depending on preUpdate
@@ -493,7 +493,7 @@ export class ForgeRenderer extends THREE.Mesh {
return false;
}
// Figure out the frame of the ForgeRenderer and current view
// Figure out the frame of the SparkRenderer and current view
if (!originToWorld) {
originToWorld = this.active.toWorld;
}
@@ -710,13 +710,13 @@ export class ForgeRenderer extends THREE.Mesh {
this.envViewpoint = this.newViewpoint({ sort360: true });
}
if (
!ForgeRenderer.cubeRender ||
ForgeRenderer.cubeRender.target.width !== size ||
ForgeRenderer.cubeRender.near !== near ||
ForgeRenderer.cubeRender.far !== far
!SparkRenderer.cubeRender ||
SparkRenderer.cubeRender.target.width !== size ||
SparkRenderer.cubeRender.near !== near ||
SparkRenderer.cubeRender.far !== far
) {
if (ForgeRenderer.cubeRender) {
ForgeRenderer.cubeRender.target.dispose();
if (SparkRenderer.cubeRender) {
SparkRenderer.cubeRender.target.dispose();
}
const target = new THREE.WebGLCubeRenderTarget(size, {
format: THREE.RGBAFormat,
@@ -724,18 +724,18 @@ export class ForgeRenderer extends THREE.Mesh {
minFilter: THREE.LinearMipMapLinearFilter,
});
const camera = new THREE.CubeCamera(near, far, target);
ForgeRenderer.cubeRender = { target, camera, near, far };
SparkRenderer.cubeRender = { target, camera, near, far };
}
if (!ForgeRenderer.pmrem) {
ForgeRenderer.pmrem = new THREE.PMREMGenerator(renderer ?? this.renderer);
if (!SparkRenderer.pmrem) {
SparkRenderer.pmrem = new THREE.PMREMGenerator(renderer ?? this.renderer);
}
// Prepare the viewpoint, sorting Gsplats for this view origin.
const viewToWorld = new THREE.Matrix4().setPosition(worldCenter);
await this.envViewpoint?.prepare({ scene, viewToWorld, update });
const { target, camera } = ForgeRenderer.cubeRender;
const { target, camera } = SparkRenderer.cubeRender;
camera.position.copy(worldCenter);
// Save the visibility state of objects we want to hide before render
@@ -756,7 +756,7 @@ export class ForgeRenderer extends THREE.Mesh {
}
// Pre-filter the cube map using THREE.PMREMGenerator
return ForgeRenderer.pmrem?.fromCubemap(target.texture).texture;
return SparkRenderer.pmrem?.fromCubemap(target.texture).texture;
}
// Utility function to recursively set the envMap property for any
+42 -42
View File
@@ -1,8 +1,8 @@
import * as THREE from "three";
import type { ForgeRenderer } from "./ForgeRenderer";
import { DynoPackedSplats } from "./PackedSplats";
import { Readback } from "./Readback";
import type { SparkRenderer } from "./SparkRenderer";
import type { SplatAccumulator } from "./SplatAccumulator";
import { SplatGeometry } from "./SplatGeometry";
import {
@@ -28,8 +28,8 @@ import {
import { withWorker } from "./splatWorker";
import { FreeList, withinCoorientDist } from "./utils";
export type ForgeViewpointOptions = {
// Controls whether to auto-update its sort order whenever the ForgeRenderer
export type SparkViewpointOptions = {
// Controls whether to auto-update its sort order whenever the SparkRenderer
// updates the Gsplats. If you expect to render/display from this viewpoint
// most frames, set this to true. (default: false)
autoUpdate?: boolean;
@@ -75,22 +75,22 @@ export type ForgeViewpointOptions = {
depthBias?: number;
// Set this to true if rendering a 360 to disable "behind the viewpoint"
// culling during sorting. This is set automatically when rendering 360 envMaps
// using the ForgeRenderer.renderEnvMap() utility function. (default: false)
// using the SparkRenderer.renderEnvMap() utility function. (default: false)
sort360?: boolean;
};
// A ForgeViewpoint is created from and tied to a ForgeRenderer, and represents
// A SparkViewpoint is created from and tied to a SparkRenderer, and represents
// an independent viewpoint of all the scene Gsplats and their sort order. Making
// these viewpoints explicit allows us to have multiple, simultaneous viewpoint
// renders, for example for camera preview panes or overhead map views.
//
// When creating a ForgeRenderer it automatically creates a default viewpoint
// When creating a SparkRenderer it automatically creates a default viewpoint
// .defaultView that is used in the normal render loop when drawing to the canvas,
// and is automatically updated whenever the camera moves. Additional viewpoints
// can be created and configured separately.
export class ForgeViewpoint {
forge: ForgeRenderer;
export class SparkViewpoint {
spark: SparkRenderer;
autoUpdate: boolean;
camera?: THREE.Camera;
viewToWorld: THREE.Matrix4;
@@ -127,8 +127,8 @@ export class ForgeViewpoint {
private readback: Uint16Array = new Uint16Array(0);
private orderingFreelist: FreeList<Uint32Array, number>;
constructor(options: ForgeViewpointOptions & { forge: ForgeRenderer }) {
this.forge = options.forge;
constructor(options: SparkViewpointOptions & { spark: SparkRenderer }) {
this.spark = options.spark;
this.camera = options.camera;
this.viewToWorld = options.viewToWorld ?? new THREE.Matrix4();
@@ -179,7 +179,7 @@ export class ForgeViewpoint {
this.setAutoUpdate(options.autoUpdate ?? false);
}
// Call this when you are done with the ForgeViewpoint and want to
// Call this when you are done with the SparkViewpoint and want to
// free up its resources (GPU targets, pixel buffers, etc.)
dispose() {
this.setAutoUpdate(false);
@@ -192,25 +192,25 @@ export class ForgeViewpoint {
this.back = undefined;
}
if (this.display) {
this.forge.releaseAccumulator(this.display.accumulator);
this.spark.releaseAccumulator(this.display.accumulator);
this.display.geometry.dispose();
this.display = null;
}
if (this.pending?.accumulator) {
this.forge.releaseAccumulator(this.pending.accumulator);
this.spark.releaseAccumulator(this.pending.accumulator);
this.pending = null;
}
}
// Use this function to change whether this viewpoint will auto-update
// its sort order whenever the attached ForgeRenderer updates the Gsplats.
// its sort order whenever the attached SparkRenderer updates the Gsplats.
// Turn this on or off depending on whether you expect to do renders from
// this viewpoint most frames.
setAutoUpdate(autoUpdate: boolean) {
if (!this.autoUpdate && autoUpdate) {
this.forge.autoViewpoints.push(this);
this.spark.autoViewpoints.push(this);
} else if (this.autoUpdate && !autoUpdate) {
this.forge.autoViewpoints = this.forge.autoViewpoints.filter(
this.spark.autoViewpoints = this.spark.autoViewpoints.filter(
(v) => v !== this,
);
}
@@ -247,7 +247,7 @@ export class ForgeViewpoint {
// Force an update, possibly with origin centered at this camera
// to yield the best quality output.
const originToWorld = forceOrigin ? this.viewToWorld : undefined;
const updated = this.forge.updateInternal({ scene, originToWorld });
const updated = this.spark.updateInternal({ scene, originToWorld });
if (updated) {
break;
}
@@ -255,9 +255,9 @@ export class ForgeViewpoint {
await new Promise((resolve) => setTimeout(resolve, 10));
}
const accumulator = this.forge.active;
const accumulator = this.spark.active;
if (accumulator !== this.display?.accumulator) {
this.forge.active.refCount += 1;
this.spark.active.refCount += 1;
}
await this.sortUpdate({ accumulator, viewToWorld: this.viewToWorld });
}
@@ -271,7 +271,7 @@ export class ForgeViewpoint {
}: { scene: THREE.Scene; camera?: THREE.Camera }) {
const target = this.back ?? this.target;
if (!target) {
throw new Error("Must initialize ForgeViewpoint with target");
throw new Error("Must initialize SparkViewpoint with target");
}
camera = camera ?? this.camera;
@@ -287,13 +287,13 @@ export class ForgeViewpoint {
this.viewToWorld = camera.matrixWorld.clone();
try {
this.forge.renderer.setRenderTarget(target);
this.forge.prepareViewpoint(this);
this.spark.renderer.setRenderTarget(target);
this.spark.prepareViewpoint(this);
this.forge.renderer.render(scene, camera);
this.spark.renderer.render(scene, camera);
} finally {
this.forge.prepareViewpoint(this.forge.defaultView);
this.forge.renderer.setRenderTarget(null);
this.spark.prepareViewpoint(this.spark.defaultView);
this.spark.renderer.setRenderTarget(null);
}
if (target !== this.target) {
@@ -310,14 +310,14 @@ export class ForgeViewpoint {
// will reuse the same buffers to minimize memory allocations.
async readTarget(): Promise<Uint8Array> {
if (!this.target) {
throw new Error("Must initialize ForgeViewpoint with target");
throw new Error("Must initialize SparkViewpoint with target");
}
const { width, height } = this.target;
const byteSize = width * height * 4;
if (!this.superPixels || this.superPixels.length < byteSize) {
this.superPixels = new Uint8Array(byteSize);
}
await this.forge.renderer.readRenderTargetPixelsAsync(
await this.spark.renderer.readRenderTargetPixelsAsync(
this.target,
0,
0,
@@ -370,7 +370,7 @@ export class ForgeViewpoint {
// Render out a viewpoint as a Uint8Array of RGBA values for the provided scene
// and any camera/viewToWorld viewpoint overrides. By default update is true,
// which triggers its ForgeRenderer to check and potentially update the Gsplats.
// which triggers its SparkRenderer to check and potentially update the Gsplats.
// Setting update to false disables this and sorts the Gsplats as they are.
// Setting forceOrigin (default: false) to true forces the view update to
// recalculate the splats with this view origin, potentially altering any
@@ -401,8 +401,8 @@ export class ForgeViewpoint {
return this.readTarget();
}
// This is called automatically by ForgeRenderer, there is no need to call it!
// The method cannot be private because then ForgeRenderer would
// This is called automatically by SparkRenderer, there is no need to call it!
// The method cannot be private because then SparkRenderer would
// not be able to call it.
autoPoll({ accumulator }: { accumulator?: SplatAccumulator }) {
if (this.camera) {
@@ -423,7 +423,7 @@ export class ForgeViewpoint {
// Splat mapping has not changed, so reuse the existing sorted
// geometry to show updates faster. We will still fire off
// a re-sort if necessary. First release old accumulator.
this.forge.releaseAccumulator(this.display.accumulator);
this.spark.releaseAccumulator(this.display.accumulator);
this.display.accumulator = accumulator;
displayed = true;
}
@@ -462,7 +462,7 @@ export class ForgeViewpoint {
this.pending?.accumulator &&
this.pending.accumulator !== this.display?.accumulator
) {
this.forge.releaseAccumulator(this.pending.accumulator);
this.spark.releaseAccumulator(this.pending.accumulator);
}
this.pending = { accumulator, viewToWorld: this.viewToWorld, displayed };
@@ -479,7 +479,7 @@ export class ForgeViewpoint {
const { viewToWorld, displayed } = this.pending;
let accumulator = this.pending.accumulator ?? this.display?.accumulator;
if (!accumulator) {
accumulator = this.forge.active;
accumulator = this.spark.active;
accumulator.refCount += 1;
}
this.pending = null;
@@ -508,7 +508,7 @@ export class ForgeViewpoint {
}
this.sortingCheck = true;
accumulator = accumulator ?? this.forge.active;
accumulator = accumulator ?? this.spark.active;
const { numSplats, maxSplats } = accumulator.splats;
let activeSplats = 0;
let ordering = this.orderingFreelist.alloc(maxSplats);
@@ -523,7 +523,7 @@ export class ForgeViewpoint {
dynoDepthBias,
dynoSort360,
dynoSplats,
} = ForgeViewpoint.makeSorter();
} = SparkViewpoint.makeSorter();
const halfMaxSplats = Math.ceil(maxSplats / 2);
this.readback = reader.ensureBuffer(halfMaxSplats, this.readback);
@@ -542,7 +542,7 @@ export class ForgeViewpoint {
dynoSplats.packedSplats = accumulator.splats;
await reader.renderReadback({
renderer: this.forge.renderer,
renderer: this.spark.renderer,
reader: doubleSortReader,
count: Math.ceil(numSplats / 2),
readback: this.readback,
@@ -595,7 +595,7 @@ export class ForgeViewpoint {
};
} else {
if (!displayed && accumulator !== this.display.accumulator) {
this.forge.releaseAccumulator(this.display.accumulator);
this.spark.releaseAccumulator(this.display.accumulator);
this.display.accumulator = accumulator;
}
@@ -611,8 +611,8 @@ export class ForgeViewpoint {
}
this.orderingFreelist.free(oldOrdering);
}
if (this.forge.viewpoint === this) {
this.forge.prepareViewpoint(this);
if (this.spark.viewpoint === this) {
this.spark.prepareViewpoint(this);
}
}
@@ -632,7 +632,7 @@ export class ForgeViewpoint {
} | null = null;
private static makeSorter() {
if (!ForgeViewpoint.dynos) {
if (!SparkViewpoint.dynos) {
const dynoSortRadial = new DynoBool({ value: true });
const dynoOrigin = new DynoVec3({ value: new THREE.Vector3() });
const dynoDirection = new DynoVec3({ value: new THREE.Vector3() });
@@ -676,7 +676,7 @@ export class ForgeViewpoint {
},
);
ForgeViewpoint.dynos = {
SparkViewpoint.dynos = {
dynoSortRadial,
dynoOrigin,
dynoDirection,
@@ -687,7 +687,7 @@ export class ForgeViewpoint {
doubleSortReader,
};
}
return ForgeViewpoint.dynos;
return SparkViewpoint.dynos;
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ export class SplatAccumulator {
toWorld = new THREE.Matrix4();
// An array of all Gsplat mappings that were used for generation
mapping: GeneratorMapping[] = [];
// Number of ForgeViewpoints (or other) that reference this accumulator, used
// Number of SparkViewpoints (or other) that reference this accumulator, used
// to figure out when it can be recycled for use
refCount = 0;
+1 -1
View File
@@ -11,7 +11,7 @@ import {
} from "./dyno";
import { newArray } from "./utils";
// Forge provides the ability to apply "edits" to Gsplats as part of the standard
// Spark provides the ability to apply "edits" to Gsplats as part of the standard
// SplatMesh pipeline. These edits take the form of a sequence of operations,
// applied one at a time to the set of Gsplats in its packedSplats. Each operation
// evaluates a 7-dimensional field (RGBA and XYZ displacement) at each point in
+1 -1
View File
@@ -139,7 +139,7 @@ export class SplatTransformer {
}
// SplatGenerator is an Object3D that can be placed anywhere in the scene
// to generate Gsplats into the world for ForgeRenderer. All Gsplats from
// to generate Gsplats into the world for SparkRenderer. All Gsplats from
// SplatGenerators across the scene will be accumulated into a single
// SplatAccumulator, which are sorted and rendered together.
//
+1 -1
View File
@@ -1,6 +1,6 @@
import * as THREE from "three";
// SplatGeometry is an internal class used by ForgeRenderer to render a collection
// SplatGeometry is an internal class used by SparkRenderer to render a collection
// of Gsplats in a single draw call by extending THREE.InstancedBufferGeometry.
// Each Gsplat is drawn as two triangles, with the order of the Gsplats determined
// by the instance attribute "ordering".
+2 -2
View File
@@ -1,6 +1,6 @@
import * as THREE from "three";
import init_wasm, { raycast_splats } from "forge-internal-rs";
import init_wasm, { raycast_splats } from "spark-internal-rs";
import { PackedSplats } from "./PackedSplats";
import { type RgbaArray, readRgbaArray } from "./RgbaArray";
import { SplatEdit, SplatEditSdf, SplatEdits } from "./SplatEdit";
@@ -389,7 +389,7 @@ export class SplatMesh extends SplatGenerator {
this.constructGenerator(this.context);
}
// This is called automatically by ForgeRenderer and you should not have to
// This is called automatically by SparkRenderer and you should not have to
// call it. It updates parameters for the generated pipeline and calls
// updateGenerator() if the pipeline needs to change.
update({
+3 -3
View File
@@ -1,6 +1,6 @@
import * as THREE from "three";
// Forge controls for keyboard + mouse, game pad, or mobile multi-touch
// Spark controls for keyboard + mouse, game pad, or mobile multi-touch
const DEFAULT_MOVEMENT_SPEED = 1.0;
const DEFAULT_ROLL_SPEED = 2.0;
@@ -54,11 +54,11 @@ const ARROW_KEYCODE_ROTATE = {
Delete: new THREE.Vector3(1, 0, 0),
};
// ForgeControls provides simple, intuitive controls for navigating 3D space that
// SparkControls provides simple, intuitive controls for navigating 3D space that
// use the keyboard + mouse, game pad, or mobile multi-touch. Internally it
// instantiates and updates a `FpsMovement` and `PointerControls` instance.
export class ForgeControls {
export class SparkControls {
fpsMovement: FpsMovement;
pointerControls: PointerControls;
lastTime = 0;
+3 -3
View File
@@ -1,5 +1,5 @@
export { ForgeRenderer, type ForgeRendererOptions } from "./ForgeRenderer";
export { ForgeViewpoint, type ForgeViewpointOptions } from "./ForgeViewpoint";
export { SparkRenderer, type SparkRendererOptions } from "./SparkRenderer";
export { SparkViewpoint, type SparkViewpointOptions } from "./SparkViewpoint";
export * as dyno from "./dyno";
@@ -70,7 +70,7 @@ export {
HandMovement,
} from "./hands";
export { ForgeControls, FpsMovement, PointerControls } from "./controls";
export { SparkControls, FpsMovement, PointerControls } from "./controls";
export {
isMobile,
+2 -2
View File
@@ -1,7 +1,7 @@
import { Gunzip } from "fflate";
import * as THREE from "three";
// Miscellaneous utility functions for Forge
// Miscellaneous utility functions for Spark
import {
LN_RESCALE,
@@ -598,7 +598,7 @@ export function unpackSplat(
// Compute a texture array size that is large enough to fit numSplats. The most
// common 2D texture size in WebGL2 is 4096x4096 which only allows for 16M splats,
// so Forge stores Gsplat data in a 2D texture array, which most platforms support
// so Spark stores Gsplat data in a 2D texture array, which most platforms support
// up to 2048x2048x2048 = 8G splats. Allocations that fit within a single 2D texture
// array layer will be rounded up to fill an entire texture row. Once a texture
// array layer is filled, the allocation will be rounded up to fill an entire layer.
+2 -2
View File
@@ -1,4 +1,4 @@
import init_wasm, { sort_splats } from "forge-internal-rs";
import init_wasm, { sort_splats } from "spark-internal-rs";
import type { TranscodeSpzInput } from "./SplatLoader";
import { unpackAntiSplat } from "./antisplat";
import {
@@ -24,7 +24,7 @@ import {
setPackedSplatScales,
} from "./utils";
// WebWorker for Forge's background CPU tasks, such as Gsplat file decoding
// WebWorker for Spark's background CPU tasks, such as Gsplat file decoding
// and sorting.
async function onMessage(event: MessageEvent) {
+3 -3
View File
@@ -58,10 +58,10 @@ export default defineConfig(({ mode }) => {
minify: isMinify,
lib: {
entry: path.resolve(__dirname, "src/index.ts"),
name: "forge",
name: "spark",
formats: ["es", "cjs"],
fileName: (format) => {
const base = format === "es" ? "forge.module" : `forge.${format}`;
const base = format === "es" ? "spark.module" : `spark.${format}`;
return isMinify ? `${base}.min.js` : `${base}.js`;
},
},
@@ -98,7 +98,7 @@ export default defineConfig(({ mode }) => {
},
define: {
forgeLocalAssets: localAssetsDirectoryExist,
sparkLocalAssets: localAssetsDirectoryExist,
},
};
});