Skip to content

maggiben/rings

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rings

A browser-based Three.js scene: a reflective sphere, a ring of procedural toruses on an elliptical path, a Preetham analytic sky dome, and real-time environment reflections. Originally part of globex; extracted here as a focused WebGL demo.

Live demo: rings-two.vercel.app

What you see

Element Role
Sky dome Preetham atmospheric scattering shader; sun position drives lighting
Reflective sphere (demo) Mirror material updated each frame from ping-pong cube cameras
Donut ring 24 toruses placed on an ellipse; each gets a unique procedural MeshStandardMaterial
Gloss floor Large plane with tiled metal texture, receives shadows
Magenta glow ball PointLight + small sphere orbiting the path on a 10s loop
Sun & frontera Spot lights from Scenario.json; sun tracks sky sun, casts shadows

Interaction: OrbitControls — drag to orbit, scroll to zoom, right-drag to pan (damped).

Debug: stats.js FPS panel (top-left) when running locally or in dev builds.

How it works

Boot sequence

  1. index.html loads src/Main.js.
  2. Main.js reads optional URL query params, builds a view object, and dynamically imports Rings.
  3. Rings constructs the scene, renderer, cameras, lights, actors, and starts the animation loop.

Scene graph

Scene
├── Sky (shader dome)
├── fixed/
│   └── floor
├── lights/          ← from Scenario.json
├── cameras/
│   ├── PerspectiveCamera (main)
│   └── cubeCamera_a / cubeCamera_b
└── actors/
    ├── glowBall (PointLight on path)
    ├── 24× donut (TorusGeometry)
    └── demo (reflective sphere)

Elliptical path

Ellipse extends THREE.Curve: points on (xRadius·cos(2πt), 0, yRadius·sin(2πt)) with default radius 42. Utilities in Utils.js place objects on the curve and align their orientation to the tangent.

Animation

Two motion layers run in parallel:

  1. Path tween (90s loop) — Moves the main camera and both cube cameras along the ellipse (animateObjectsAlongPath). The reflective sphere’s env-map sources follow this motion indirectly via cube camera updates.
  2. Glow ball tween (10s loop) — Moves the magenta PointLight around the same path.

Each frame:

  1. Hide demo, alternate updating cubeCamera_a / cubeCamera_b into the sphere’s envMap, show demo.
  2. tweenGroup.update(time).
  3. renderer.render(scene, camera).
  4. controls.update().

Materials & textures

  • Donuts: Canvas-generated textures via Textures.js (noise, cow patterns from tooloud, Perlin FBM) with random UV scale/offset per instance.
  • Floor: /images/metal2b.jpg from public/images/ (Phong, repeated).
  • Sphere: MeshBasicMaterial with dynamic envMap from cube render targets (512³).

Sky & lighting

SkyShader.js implements the Preetham analytic skylight model. After the sky mesh is added, sky.update() syncs shader uniforms; the named sun spotlight in Scenario.json is positioned from sky.getSunPosition() and targets the reflective sphere.

Multi-monitor / tiling (legacy)

The main camera uses setViewOffset(fullWidth, fullHeight, x, y, width, height) so one viewport can show a window into a larger virtual canvas — useful for tiled displays or video walls carried over from globex.

Project structure

rings/
├── index.html              # Full-viewport stage + stats.js CDN
├── public/images/          # Static textures (metal2b.jpg, uv.jpg)
├── src/
│   ├── Main.js             # Entry: URL params → Rings
│   ├── Rings/
│   │   ├── Rings.js        # Scene orchestration & render loop
│   │   ├── Actors.js       # Meshes: donut, floor, glowBall, …
│   │   ├── Materials.js    # Gloss, mirror, procedural donut mats
│   │   └── Scenario.json   # Light (and legacy camera) definitions
│   ├── Shaders/
│   │   └── SkyShader.js    # Preetham sky dome
│   └── common/             # Stage, Camera, Lights, Ellipse, Utils, …
├── tests/
│   └── webgl-smoke.mjs     # Playwright screenshot smoke test
└── vite.config.js          # `tween` → @tweenjs/tween.js compat alias

Requirements

  • Node.js 18+ (20+ recommended)
  • A browser with WebGL (Chrome, Firefox, Safari, Edge)

Run locally

npm install
npm run dev

Open http://localhost:5173/.

Production build

npm run build    # output → dist/
npm run preview  # serve dist/ locally

Tiling query parameters

Param Meaning Default
fullWidth Virtual canvas width window.innerWidth
fullHeight Virtual canvas height window.innerHeight
x Viewport offset X 0
y Viewport offset Y 0

Example (top-left quarter of a 4K canvas):

http://localhost:5173/?fullWidth=3840&fullHeight=2160&x=0&y=0

Test

Headless WebGL smoke test: loads the app in Chromium, captures a screenshot, and fails if too few non-black pixels (scene did not render).

Start the dev server first, then in another terminal:

npm run test:webgl

Optional environment variables:

Variable Default Purpose
WEBGL_TEST_URL http://localhost:5175/ Page URL (set to your dev server port, e.g. 5173)
WEBGL_MIN_NON_BLACK_RATIO 0.01 Minimum fraction of non-black pixels
WEBGL_TEST_TIMEOUT_MS 15000 Navigation timeout

Example:

WEBGL_TEST_URL=http://localhost:5173/ npm run test:webgl

Deploy

The app is a static Vite site. Vercel detects vite build and serves dist/ automatically.

npx vercel          # preview
npx vercel --prod   # production

Connect the GitHub repo in the Vercel project settings for deploy-on-push.

Tech stack

Layer Choice
Bundler Vite 7
3D three.js 0.180
Animation @tweenjs/tween.js
Controls OrbitControls (three/examples)
Procedural textures tooloud, custom Perlin (Noise.js)
Smoke test Playwright + pngjs

Legacy tween imports are aliased to @tweenjs/tween.js via src/common/tweenCompat.js.

Possible improvements

Ideas grounded in the current codebase — good starting points for contributions:

Performance & bundle

  • Code-split Rings.js (main chunk is ~760 KB minified) with dynamic imports or manual chunks.
  • Lower cube camera resolution on mobile / prefers-reduced-motion.
  • Pause cube env updates when the tab is hidden (document.visibilityState).

Rendering & assets

  • Expose Preetham sky parameters (turbidity, rayleigh, inclination, …) via URL or a small debug GUI.
  • Restore or document optional skybox paths in Materials.skyBox() (public/images/skybox/).
  • Fix animate() being invoked twice in Rings constructor (constructor + Main.js).

Code quality

  • Remove debug console.log in animateObjectsAlongPath.
  • Use Scenario.json camera templates or trim unused lightsX / cameras entries.
  • Drop redundant tween npm package if everything uses the Vite alias.
  • Align smoke test default port with Vite (5173).

Features

  • Re-enable path visualization (Actors.tube) behind a ?debug=1 flag.
  • Wire Materials.simplify wireframe mode for shader debugging.
  • TypeScript types for scene/scenario JSON.

Ops

  • GitHub → Vercel integration for continuous deploy.
  • CI workflow: npm run build + npm run test:webgl on pull requests.

Credits

License

MIT © 2026 Benjamin Maggi

About

WebGL Rings

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors