Particles ocean

A deep-water surface driven by a real inverse FFT. A Phillips spectrum evolves in frequency space, Stockham passes produce a displacement field, and half a million particles ride the waves through an HDR bloom chain.

Open fullscreen
/**
 * Canonical parameter table copied from front/fft-ocean-1 DEFAULT_SETTINGS,
 * settings constants, uniform-packing, and bloom-pass. Keep gallery-only deltas
 * (resolution selector, deterministic GPU noise and orbit input) out of this table.
 */
export const OCEAN_TUNING = {
  simulation: {
    oceanSize: 200,
    worldSize: 400,
    gravity: 9.81,
    timeScale: 0.6,
    spectrumTimeScale: 0.5,
    windSpeed: 12.9,
    windAngle: 4.83,
    amplitude: 1.3,
    choppiness: 1.51,
    displacementScale: 0.005,
    foamThreshold: 0,
    phillipsShortWaveRatio: 0.001,
    oppositeWindDamping: 0.07,
  },
  particles: {
    pointSize: 0.75,
    fadeNear: 60,
    fadeFar: 250,
    fadePower: 3.2,
    oceanColor: [0.003035269835488375, 0.003035269835488375, 0.003035269835488375, 0] as const,
    neonColor: [1, 1, 1, 0] as const,
    foamColor: [1, 1, 1, 0] as const,
  },
  camera: {
    // Gallery reframe: the docs canvas is much taller than front's hero strip.
    // Raising and backing off the rig keeps the horizon in the upper third.
    eye: [0, 30, 90] as const,
    target: [0, 5, 55] as const,
    up: [0, 1, 0] as const,
    pitchDegrees: -10,
    fovDegrees: 90,
    near: 0.1,
    far: 2000,
  },
  bloom: {
    threshold: 0.3,
    smoothWidth: 0.01,
    strength: 0.08,
    radius: 0.46,
    levels: 5,
    kernelRadii: [6, 10, 14, 18, 22] as const,
    factors: [1, 0.8, 0.6, 0.4, 0.2] as const,
  },
  present: {
    exposure: 1,
    linearToSrgbExponent: 0.41666,
    clear: [0, 0, 0, 0] as const,
  },
} as const;
 
/** Matches front's `gaussianCoefficients`: sigma=radius/3, no normalization pass. */
export function gaussianCoefficients(kernelRadius: number): readonly number[] {
  return Array.from({ length: 24 }, (_, index) => index < kernelRadius
    ? (0.39894 * Math.exp((-0.5 * index * index) / ((kernelRadius / 3) ** 2))) / (kernelRadius / 3)
    : 0);
}