Smiley Spheres

A Three.js scene with generated sphere textures, palette controls, pointer interaction, and benchmarked collision choices.

Built as a working visual experiment, then tightened with runtime texture generation, palette tooling, and a benchmarked collision path that keeps brute force for small scenes and switches to a uniform grid once the scene gets dense.

Skip to the collision benchmarks
  • Three.js
  • React
  • TypeScript
  • Canvas API
  • HSL palettes
  • Deno.bench()
  • Collision benchmarks

The expressions are drawn at runtime. No image files are needed for the faces.

The scene starts from one color and walks hue, saturation, and lightness by fixed steps.

Generator model

The controls map to four inputs before colors wrap across the spheres.

Hue step
Rotates around the color wheel. 5-10 degree steps stay close; 60-120 degree steps separate colors.
Saturation step
Changes color intensity per generated swatch.
Lightness step
Moves each color toward brighter or darker values.
Clamping
Saturation and lightness stay inside the 0-1 range, then sphere colors wrap by index.

Interactive palette generator

Use presets or tune the HSL steps directly while the preview keeps the sphere layout running.

Palette controls

Start with presets or tune the generator directly.

Generated palette

5
Preview ready

Brute force wins at tiny counts. Uniform grid wins once the scene gets dense enough to make setup cost worth paying.

Decision

Benchmark data kept the production path simple: brute force for small scenes, uniform grid after the crossover point.

Try the solver

Switch algorithms or body counts to compare the same scene with live runtime metrics.

Algorithm playground

Run the same scene through each broad-phase mode and watch the runtime metrics while it moves.

AlgorithmOr compare manually
48
121000
0.95
0.251.2
Auto → Brute forceFPS -- · Bodies 48 · Steps --

Evidence

The cards show the broad-phase structure; the table shows where the fastest path changes.

Every pairO(N²)

Brute force

Checks every pair. It wins around 80 spheres and below because it has almost no setup cost.

Sorted intervalsO(N log N)

Sweep and prune

Sorts bodies by X axis, then only checks overlapping intervals.

Hash bucketsO(N) avg

Spatial hash

Buckets bodies into map cells. The query path was improved, but hashing still costs more here.

Flat gridO(N) avg

Uniform grid

Uses a flat pre-allocated array and wins once the scene gets dense enough.

Recursive splitO(N log N)

Quadtree

Scales better than brute force at high counts, but tree traversal trails the flat grid.

Apple M4, Deno 2.7.4, April 2026. Bold = fastest at that N.
N Brute ForceSweep & PruneSpatial HashUniform GridQuadtree
20 222 ns1.2 µs2.4 µs2.3 µs1.8 µs
50 1.5 µs3.6 µs10.8 µs3.2 µs6.4 µs
75 3.5 µs5.7 µs16.3 µs4.1 µs10.1 µs
100 6.0 µs8.7 µs24.6 µs4.3 µs12.9 µs
500 127.3 µs75.2 µs135.1 µs5.5 µs47.6 µs
1,000 501.5 µs192.7 µs333.4 µs7.2 µs87.0 µs
5,000 18.4 ms1.8 ms16.0 ms16.7 µs393.4 µs
10,000 69.3 ms6.3 ms182.0 ms31.7 µs855.1 µs

The final implementation keeps the simple path for small scenes and switches only when the data says the setup cost pays off.

  1. v1.4

    Study polish

    The page moved into the current Design Lab study format with clearer texture, palette, and collision evidence.

    Changed
    • Reworked the texture section into the generated map, expression variants, and sphere preview.
    • Added palette presets and direct controls so the generated colors can be tuned without restarting the scene.
    • Redesigned the broad-phase collision diagrams with labelled structures, highlighted comparisons, and calmer motion.
    • Polished interaction states across the texture carousel and controls, and aligned demo widths with the reading column.
    • Tightened the release log, references, spacing, and responsive layout.
  2. v1.3

    Runtime polish

    Simulation timing, render cost, and background work were tightened so the scene behaves better inside a long study page.

    Changed
    • Moved physics updates to fixed substeps so sphere motion stays steadier between render frames.
    • Paused the animation loop when the scene is offscreen or the document is hidden.
    • Capped renderer pixel ratio to reduce unnecessary GPU work on high-DPI displays.
    • Reused generated face textures when spheres share a color and expression.
  3. v1.2

    Spatial hash performance

    The spatial hash query path stopped allocating a fresh result array for every lookup and reused caller-owned storage instead.

    Changed
    • SpatialHashMap.query() appends into a caller-owned array instead of returning a new one.
    • Spatial hash at N=10,000 improved from 189.3 ms to 182.0 ms, with less allocation pressure in the hot path.
    • Benchmarks were re-run on Deno 2.7.4; uniform grid at N=10,000 improved from 60.2 µs to 31.7 µs and remained the fastest dense-scene path.
  4. v1.1

    Raycaster performance

    O(1) body lookup on click replaced the linear array scan with a direct mesh.userData reference.

    Changed
    • Cached the body reference on mesh.userData at sphere creation: O(1) lookup instead of O(N) array.find().
    • ~2x faster at N=10, ~12x at N=100, and ~125x at N=1,000, measured with Deno.bench().
    • Added raycaster.bench.ts to benchmark lookup strategies side by side.
    • Added deno task bench to run all benchmarks in one command.
  5. v1.0

    Initial release

    First working 3D scene with physics-based collision, procedural textures, and benchmarked algorithms.

    Included
    • Three.js 3D scene with 30 physics-based bouncing spheres.
    • Five collision algorithms benchmarked with Deno.bench(): brute force, sweep and prune, spatial hash, uniform grid, and quadtree.
    • Procedural texture generation with Canvas API: smiley, neutral, and sad faces with automatic contrast.
    • HSL-based color palette generator with an interactive playground.
    • Click-to-impulse interaction through the Three.js raycaster.

Source material and tools that shaped the study.

  1. SuperHiOriginal interaction inspiration.
  2. Three.js3D rendering library.
  3. Deno.bench()Built-in benchmark tool used for the collision data.
  4. UI-colorsThesis-related palette exploration.
  5. Soft-UINeumorphism color generator.
  6. color-processing-librarynpm color processing package.