homogeneous-spaces

View on GitHub

Drawing geometrically-correct points, geodesics, curves, and surfaces in 2D and 3D homogeneous spaces — constant-curvature geometries, their Lorentzian cousins, and product spaces — through swappable coordinate models.

Readme

Drawing in 2D and 3D homogeneous spaces — constant-curvature geometries (Euclidean, spherical, hyperbolic), their Lorentzian cousins (Minkowski, de Sitter, anti–de Sitter), and products M×R — with geometrically-correct points, geodesics, curves, surfaces, and surfaces of revolution, viewed through swappable coordinate models, and animated. TypeScript + three.js + Vite.

Architecture

Five layers, each depending only on the ones above it:

core/        App harness: App, reactive Params (dependency DAG), TimelineManager, ParameterManager (lil-gui)
geometry/    Pure intrinsic math behind Geometry<P,I> (no rendering)
models/      Coordinate charts: project a point + report metric distortion (scaleAt / jacobianAt)
primitives/  Intrinsic, model-agnostic drawables (GeoPoint, Geodesic, GeoCurve, GeoSurface)
render/      Backends consuming (primitive, model): mesh (three.js)

Everything is generic over the canonical point type P (Vector3 in 2D = ambient R³, Vector4 in 3D = ambient R⁴). A geometry knows the intrinsic math; a model maps points into a picture and reports how the metric is distorted there — which is what makes a point the correct size and a tube a constant intrinsic width.

The framework is built on the metric as the one required primitive (Geometry.metric): frames, light cones, and a numerical geodesic engine all derive from it, so the classic “vector on a quadric with closed-form exp/log” is a fast-path optimization, not a requirement. Optional structure (symmetric-space transvections, rotation axes, surface-of-revolution coefficients, Lorentzian null frames) is exposed through duck-typed capabilities (geometry/capabilities.ts), queried with guards that throw clearly when a capability is absent. See CLAUDE.md for the developer guide.

Running

npm install
npm run dev <demo>     # run a demo (one folder per demo under demos/<name>/)
npm run build <demo>   # production build into dist/<demo>
npm run typecheck      # tsc --noEmit
npm run test           # vitest (behavior + numeric-engine validation)

Demos

# Constant curvature
npm run dev hyperbolic2     # H²: Poincaré disk / Klein disk / upper half-plane
npm run dev spherical2      # S²: globe / stereographic / Mercator
npm run dev euclidean2      # E²
npm run dev hyperbolic3     # H³: Poincaré ball / Klein ball / upper half-space
npm run dev spherical3      # S³ (stereographic to R³)
npm run dev euclidean3      # E³

# Products M × R
npm run dev h2xr            # H²×R   (stacked base model + fibre)
npm run dev s2xr            # S²×R

# Lorentzian (2D): light-cone glyphs, chart dropdowns
npm run dev minkowski       # R^{1,1}
npm run dev rindler         # the Rindler wedge
npm run dev desitter        # dS₂ (conformal / static / flat / Painlevé / closed)
npm run dev antidesitter    # AdS₂ (conformal / Poincaré)

# Lorentzian (3D) and Lorentzian products
npm run dev minkowski3      # R^{2,1}
npm run dev desitter3       # dS₃
npm run dev antidesitter3   # AdS₃
npm run dev h2xtime         # H²×ℝ_time (static spacetime), light cones over the disk
npm run dev s2xtime         # S²×ℝ_time (Einstein static universe)
npm run dev ds2xr           # dS₂×ℝ,  ads2xr — Lorentzian base × spacelike line

# Surfaces of revolution & embedding pictures
npm run dev revolution      # surface of revolution in each geometry (dropdown)
npm run dev pseudosphere    # the pseudosphere embedded across geometries; each shows the part that fits
npm run dev dshorizon       # dS₃ static chart (stops at horizon) vs global chart (crosses) vs S²×ℝ_time (stops)
npm run dev dshyperboloid   # dS₂-hyperboloid cross-section: static patch vs whole space, with the meridian
npm run dev hyperboloids    # embedding views of S²/H²/dS²/AdS², with a boost/rotation animating the slicing

# Animation
npm run dev animation       # pose/param tracks: spinning generator, gliding point, playback

Surfaces of revolution

Define a rotationally-symmetric surface intrinsically (a(u)²du² + b(u)²dθ²) once and embed it into any geometry that supplies the RevolutionMetric capability:

import { pseudosphere, embedRotational, MeshBackend } from './src';
import { DeSitter3 } from './src/geometry/DeSitter3';
const mesh = new MeshBackend();
mesh.surface(embedRotational(pseudosphere(), new DeSitter3(1)), model);

embeddableDomain(surface, geom) returns the portion that actually embeds. The mathematics — including the careful analysis of when an embedding edge is a genuine boundary versus a coordinate horizon (dS₃ crosses its cosmological horizon; S²×ℝ_time genuinely stops at its equator; AdS₃) — is written up in SURFACES_OF_REVOLUTION.md and desitter-pseudosphere-embedding.md, with figures generated by scripts/*-figure.mjs into figures/.

Status

Constant-curvature (2D/3D), products M×R, and Lorentzian (Minkowski/dS/AdS, 2D/3D

  • products) geometries; coordinate models for each; the three.js mesh backend (points, geodesics, curves, surfaces, light cones) at correct sizes; the reactive View model-switcher; a pose/parameter animation system; the surfaces-of-revolution system; and a vitest suite (geometry round-trips, induced-metric checks, capability guards, and validation of the numerical geodesic engine against the closed forms).

Next on the horizon: the BCV homogeneous spaces (Nil, SL₂~, Berger spheres) via NumericGeometry; a shader backend.

← All software