homogeneous-spaces
View on GitHubDrawing 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.
- Built with TypeScript / three.js
- Status Active
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
Viewmodel-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.