PAJAMA / LEARN
← Course overview

Add appearance and time / 06 OF 12

What the fourth dimension actually changes

After this lessonEvaluate an STG-Lite record at a timestamp and distinguish it from deformation and joint 4D Gaussians.

A moving point does not need a new file for every video frame. Its record can contain a short motion recipe plus a time window in which it matters.

Build the idea, one step at a time

Need a math refresher?

Open only the tool you need; return here to use it in the next step.

Vectors & weighted sumsExponentials & logarithms

STEP 1 / 5

Agree on the clock first

Normalize capture time with a documented start and duration. A file using t in 0–1 cannot be evaluated with seconds directly. Playback speed changes the rate at which t advances, not the learned motion coefficients. Our browser fixture maps its capture interval to the normalized model time.

t=tseconds−tstartD,Δt=t−τt=\frac{t_{\rm seconds}-t_{\rm start}}{D},\qquad\Delta t=t-\tau

PUT NUMBERS IN

For the existing teaching point A, μ₀ = (1, 2, 10), m₁ = (2, 0, 0), m₂ = (0, 4, 0), m₃ = (0, 0, 8). At t = 0.75 with τ = 0.5, Δt = 0.25 and μ = (1.5, 2.25, 10.125).

FORMULA → RUNNING CODE

Find the same operation in the code

These excerpts are read from the current repository when the site is built. Expand one to inspect it; the full snapshot opens in a separate tab.

src/splat.wgslL55–63

The shader evaluates the cubic trajectory.

  let dt = camera.scene.x - splat.r0.w;
  let dt2 = dt * dt;
  let motion_linear = splat.r2.xyz;
  let motion_quadratic = vec3<f32>(splat.r2.w, splat.r3.x, splat.r3.y);
  let motion_cubic = vec3<f32>(splat.r3.z, splat.r3.w, splat.r4.x);
  let center = splat.r0.xyz
    + motion_linear * dt
    + motion_quadratic * dt2
    + motion_cubic * dt2 * dt;
Open the deployed source snapshot ↗
public/math/trace.mjsL42–58

The CPU teaching evaluator exposes every intermediate temporal value.

export function evaluateRecord(row, time, pixel) {
  const dt = time - row[3], rho = Math.max(Math.exp(row[4]), 1e-6);
  const temporal = Math.exp(-((dt / rho) ** 2));
  const mean = row.slice(0, 3).map((v, i) =>
    v + row[8 + i] * dt + row[11 + i] * dt ** 2 + row[14 + i] * dt ** 3);
  const u = row.slice(24, 28).map((v, i) => v + dt * row[28 + i]);
  const norm = Math.hypot(...u);
  if (!(norm > 0)) throw new Error('Undefined quaternion');
  const q = u.map(v => v / norm), [w, x, y, z] = q;
  const scales = row.slice(21, 24).map(Math.exp);
  // Columns of the active, right-handed rotation, exactly as splat.wgsl axes.
  const axes = [
    [1 - 2 * (y*y + z*z), 2 * (x*y + w*z), 2 * (x*z - w*y)],
    [2 * (x*y - w*z), 1 - 2 * (x*x + z*z), 2 * (y*z + w*x)],
    [2 * (x*z + w*y), 2 * (y*z - w*x), 1 - 2 * (x*x + y*y)],
  ].map((axis, i) => scaled(axis, scales[i]));
  const sigma = axes.map(outer).reduce((a, b) => a.map((row, i) => plus(row, b[i])));
Open the deployed source snapshot ↗

CHECK YOUR UNDERSTANDING

Can you predict the result?

A narrow temporal width mainly makes a primitive…

This is your own review mark. You can remove it at any time.

Go deeper with primary sources

Spacetime Gaussian Feature Splatting · paper ↗4D-GS · deformation-based representation ↗Native 4D Gaussian Splatting · joint spacetime representation ↗Full mathematical derivation ↗