Skip to content

affromero/splattie-widget

Repository files navigation

Splattie

splattie-widget

Interactive 3D Gaussian Splatting - like Rive/Lottie for 3D

npm License TypeScript Spark Three.js Tests Bundle

Quick Start · Format Spec · API · Editor · How It Works


Splattie Widget Demo

A web component that makes gaussian splats reactive. One file, one tag. Eyes follow the cursor, face blinks and reacts to hover/click, expressions transition smoothly. 60fps, client-side.

See it live at afromero.co | Create your own at splattie.app

Quick Start

<splattie-widget src="avatar.splattie"></splattie-widget>
<script type="module" src="https://unpkg.com/@afromero/splattie-widget"></script>

Or via npm:

npm install @afromero/splattie-widget
import '@afromero/splattie-widget';

The .splattie Format

v0.x experimental. Core files (PLY, FLAME bones) follow established standards. Expression basis and states may evolve.

A ZIP bundle with a required manifest.json that declares every asset and locks the file's formatVersion to the widget version. See FORMAT.md for the full spec.

avatar.splattie
├── manifest.json             # (required) declares every asset + formatVersion
├── *.ply or *.spz            # (required) Gaussian splats
├── bone_tree.json            # (optional) Skeleton for skinning
├── lbs_weight_20k.json       # (optional) Per-splat bone weights
├── expression_basis.bin      # (optional) Blendshape basis
└── states.json               # (optional) Interaction states
Splat data (.ply or .spz) - standard 3DGS format

Each splat has position, scale, rotation, opacity, and SH color. Auto-detected from file header. Works with any 3DGS method (LAM, DreamGaussian, InstantSplat, etc.). Standard format, unlikely to change.

bone_tree.json - skeleton hierarchy

5 FLAME bones: root > neck > jaw, leftEye, rightEye. Used for SplatSkinning (dual quaternion).

{
  "bones": [{
    "name": "root",
    "position": [x, y, z],
    "children": [{
      "name": "neck",
      "position": [x, y, z],
      "children": [
        { "name": "jaw", "position": [x, y, z] },
        { "name": "leftEye", "position": [x, y, z] },
        { "name": "rightEye", "position": [x, y, z] }
      ]
    }]
  }]
}

Stable structure. Bone names are conventions, not hard requirements. Without it: no eye tracking, no jaw animation.

lbs_weight_20k.json - per-splat bone weights

2D array [num_splats][num_bones], each row sums to ~1.0. Widget selects top 4 per splat.

[[0.8, 0.1, 0.05, 0.03, 0.02], ...]

Standard LBS format from FLAME. Without it: bones exist but nothing moves.

expression_basis.bin - FLAME blendshape basis

Per-splat position displacements for each expression coefficient. Moves all splats coherently for smile, lip shapes, etc.

Header: "EXPR" (4B) + num_vertices (u32 LE) + num_expressions (u32 LE)
Data:   float32 LE array, shape (num_vertices, num_expressions, 3)

Optional sidecar expression_basis.json with semantic labels:

{ "labels": ["jawDown", "lipsUp", "lipsL", ...], "num_expressions": 50 }

Experimental format, may add compression. Without it: bone-driven expressions still work.

states.json - interaction state definitions

Each state (idle, hover, click) sets all 5 dimensions simultaneously.

{
  "defaults": {
    "camera": { "theta": 0, "phi": 75, "radius": 0.5, "fov": 60 },
    "autoBlink": { "interval": [2000, 7000], "duration": 150 }
  },
  "states": {
    "idle": {
      "ghost": { "amplitude": 0.003, "frequency": 0.4, "wobble": 0.2 },
      "expression": { "jawOpen": 0, "smile": 0 },
      "camera": { "theta": 0, "phi": 75, "radius": 0.5, "fov": 60 },
      "rotation": [0, 0, 0],
      "tracking": { "eyes": 1.0, "head": 0.1 }
    },
    "hover": { "..." : "..." },
    "click": { "..." : "..." }
  },
  "transitions": {
    "idle->hover": { "duration": 0.3, "easing": "ease-out" },
    "*->click": { "duration": 0.1, "easing": "snap" }
  }
}

Most likely to evolve. Without it: sensible defaults (eyes track, gentle float, auto-blink).

Creating Your Own

Visual editor: npm run dev, adjust sliders, click "Download .splattie".

From scratch: ZIP a .ply with any combination of the optional files.

From a photo: run LAM on a GPU, then bundle with the export script. Try it at splattie.app.

Five Dimensions of State

Dimension Controls Example
Ghost Floating/bobbing Gentle hover on idle, freeze on click
Expression FLAME blendshapes + bones Smile on hover, surprise on click
Camera Spherical position Zoom in on hover
Rotation Pitch/yaw/roll Tilt head on hover
Tracking Cursor-follow intensity Eyes on idle, head follows on hover

Interpolated between states with configurable easing and duration.

Expression system details

Two layers:

Bone-driven (SplatSkinning, 5 FLAME bones):

  • Jaw open/close, neck pitch/yaw/roll
  • Eye gaze direction (left/right, up/down)
  • Brow raise/frown (left/right independently)

Blendshape-driven (FLAME expression basis, 10+ PCA coefficients):

  • Moves all 20K splats coherently
  • Smile, lip shapes, jaw articulation, cheek/nose deformation
  • Spatial mask prevents beard/neck from deforming

API

Attribute Description
src URL to .splattie file (or .ply/.spz)
background Background color hex (default: #0e0e14)
width CSS width (default: 100%)
height CSS height (default: 400px)
widget.addEventListener('splatload', () => {});   // ready
widget.addEventListener('splathover', () => {});   // cursor on face
widget.addEventListener('splatclick', () => {});   // clicked face
widget.addEventListener('splatleave', () => {});   // cursor left
widget.setState('hover');                           // force transition

Visual Editor

npm run dev  # http://localhost:4002

Sliders for all 5 dimensions, camera sphere widget, state tabs with copy-forward, FLAME blendshape controls, drag-and-drop .splattie upload, export when done.

How It Works

Built on Spark 2.0 (MIT, World Labs).

Architecture
  1. State machine with per-dimension interpolation (lerp, slerp, ease curves)
  2. SplatSkinning (dual quaternion) driving 5 FLAME bones from expression + cursor data
  3. Expression basis - per-splat position offsets written to Spark's packed buffer (half-float, ~20K splats/frame)
  4. Hit detection via readPixels after render (pixel-perfect)
  5. Auto-blink with randomized interval and sine-curve via SplatEdit
  6. Gyroscope tracking on mobile (iOS permission prompt included)

Mobile

Touch + gyroscope. Eyes follow device orientation on mobile, touch position on tap. Return to center when finger lifts. iOS motion permission requested automatically.

Browser Support

Chrome, Firefox, Safari, Edge. WebGL 2 required. No COOP/COEP headers needed.

Acknowledgements

  • LAM (SIGGRAPH 2025) - Zixuan Zeng et al., AIGC3D team
  • FLAME - Tianye Li, Timo Bolkart, Michael J. Black, Hao Li, Javier Romero
  • Spark 2.0 - World Labs (MIT)
  • 3D Gaussian Splatting - Kerbl, Kopanas, Leimkuhler, Drettakis (INRIA)

License

MIT

About

Interactive 3D Gaussian Splatting web component — like Rive/Lottie for 3D

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors