Skip to content

MattNotarangelo/CaveViewer

Repository files navigation

Cave Survey Viewer

A modern, web-native 3D viewer for cave survey data. Drag a survey file onto the page and get an interactive, depth-coloured 3D model you can orbit, pan, and zoom.

Everything runs client-side. Your survey files never leave your machine — nothing is uploaded. Cave survey data is private to its surveyors, so this is a feature, not a limitation. The app deploys as a pure static site.

Example cave render

Status

Phase Scope State
1 Survex .3d (v8): centreline render, orbit/pan/zoom, depth colouring, drag-and-drop, fit-to-view, length/bounds readout, north indicator ✅ done
1+ Preset plan/elevation views, orthographic toggle, scale bar, colour modes (elevation / distance-from-entrance / gradient / survey / single), leg-type visibility toggles, PNG export (ideas adopted from CaveView.js) ✅ done
2 Compass .plt (processed coordinates, LRUD, splays, multi-survey) ✅ done
3 Therion .lox + lit triangle-mesh passage walls (the modelled scrap surfaces); LRUD passage tubes reconstructed for .3d/.plt ✅ done
4 Interaction & UX: ViewCube navigation (drag to orbit, click a face to snap); click a station for its details; hover labels + station finder; measure tool (straight-line / horizontal / vertical / bearing); survey-tree show/hide; vertical exaggeration; entrance & fixed-point markers; light/dark theme; metric/imperial units; render-on-demand (idle GPU) ✅ done
next Colour by date; cross-sections from splays; clipping plane / depth cursor; depth fog planned

Architecture

The project is split into two cleanly separated parts. The parser is the durable asset; the renderer is the demo on top.

src/
  parser/          # No dependency on the renderer. Independently testable.
    types.ts       # The CaveModel contract (see below)
    byteCursor.ts  # Little-endian binary reader with bounds checking
    survex3d.ts    # Survex .3d v8 parser
    compassPlt.ts  # Compass .plt parser
    therionLox.ts  # Therion .lox parser (centreline + scrap wall meshes)
    caveStats.ts   # Derived stats (total length, depth range, ...)
    index.ts       # parseCaveFile(filename, buffer) dispatcher + exports
  viewer/          # Three.js. Consumes a CaveModel; knows nothing about files.
    Viewer.ts          # Scene, cameras, OrbitControls, picking, render-on-demand
    buildCenterline.ts # Fat-line centreline (colour + leg/survey visibility)
    buildLrudTubes.ts  # LRUD → passage tubes for .3d/.plt
    coloring.ts, colormap.ts, coords.ts, legend.ts, scaleBar.ts
    northIndicator.ts  # Compass needle
    viewCube.ts        # Autodesk-style navigation cube
    surveyTree.ts      # Survey-hierarchy build + leg-visibility logic (pure)
  ui/              # Vanilla DOM (framework-light by design)
    hud.ts, controls.ts, units.ts
    stationInfo.ts, stationSearch.ts, measurePanel.ts, surveyTreePanel.ts
  main.ts          # Wires parser → viewer → DOM

Every format parser converts its input into one normalized model, so the UI can be rewritten without touching parsing, and new formats reuse the whole renderer.

The CaveModel contract

interface CaveModel {
  metadata: {
    title: string;
    format: string;          // e.g. "survex-3d-v8"
    separator: string;       // survey hierarchy separator (default ".")
    crs?: string;            // coordinate reference system, if declared
    datestamp?: string;
    dateRange?: { from: string; to: string };   // ISO YYYY-MM-DD
    bounds: { min: Vec3; max: Vec3 };            // metres
    isExtendedElevation: boolean;
  };
  stations: { id, label, x, y, z, flags }[];     // x=East, y=North, z=Up (metres)
  legs:     { from, to, flags, survey?, date? }[]; // from/to index into stations
  walls?:   { positions: Float32Array; indices: Uint32Array };  // .lox triangle mesh
  lrud?:    { station, l, r, u, d, lastInPassage }[];           // passage cross-sections
}

Axes follow the surveying convention (x=East, y=North, z=Up), in metres. The parser stays axis-faithful; all axis remapping for rendering lives in viewer/coords.ts.

Supported formats & spec references

Format Type Spec / reference
Survex .3d (v8) binary Official 3d format spec; cross-checked against Survex's reference reader src/img.c (img_read_item_new, read_v8label)
Compass .plt text Cross-checked against Survex's reference Compass reader src/img.c; coordinates are North/East/Up in feet → metres
Therion .lox binary Reverse-engineered from Therion's reference reader src/common-utils/lxFile.{h,cxx} (chunked format; record fields follow each struct's Load(), not the .h declaration order)

The .3d parser implements the v8 layout exactly — byte offsets are taken from the spec and the reference C reader, not guessed. Files older than v8 are rejected with a clear message (re-save with a recent cavern, which writes v8 by default). The .plt parser reads Compass's processed plot coordinates, LRUD passage data, splay/duplicate/surface shot flags, and multi-survey sections. The .lox parser reads the centreline plus the modelled passage-wall triangle meshes ("scraps"), which the viewer renders as a lit, translucent surface — .lox is validated by a cross-format test against the same cave's .3d.

Develop, test, build

Requires Node 20+.

npm install
npm run dev          # Vite dev server with HMR
npm test             # parser test suite (vitest)
npm run typecheck    # tsc --noEmit
npm run build        # type-check then produce static site in dist/
npm run preview      # serve the production build locally

The "Load example cave" button loads public/system_migovec.lox — the Tolminski Migovec cave system (~47 km, 21k+ stations), surveyed by the JSPDT and Imperial College Caving Club and published openly by the Migovec Resurvey Project. It exercises the viewer on a real, large survey, including the modelled passage-wall mesh that .lox carries. See NOTICE for attribution.

Testing — parser correctness is the whole ballgame

A plausible render is not proof the parser is correct; the numbers must match.

  • Golden test (test/survex3d.golden.test.ts): parses a real cavern-written v8 file (dump3ddate.3d, vendored from Survex's own test suite) and asserts every leg coordinate, station label, and date against Survex's own dump3d reference output. This is the ground-truth oracle. See test/fixtures/survex/PROVENANCE.md.
  • Round-trip / edge cases (test/survex3d.encode.test.ts): a from-spec encoder exercises paths the golden fixture lacks — labelled LINEs, splay/surface/duplicate flags, XSECT (narrow + wide), anonymous stations, CRS/separator headers, ERROR and DATE records, the (del=0, add=0) label escape, and error handling.
  • Compass golden test (test/compassPlt.golden.test.ts): parses a real .plt (multisurvey.plt) and asserts stations, legs (incl. splays), and LRUD against Survex's own decode of the same file (multisurvey.dump) — an independent oracle.
  • Therion golden test (test/therionLox.golden.test.ts): cross-checks the .lox parse of a real cave against the same cave's .3d, so the reverse-engineered binary layout is anchored to reference-tool output.
  • Pure-logic unit tests cover the non-parser building blocks too — unit conversion/formatting (test/units.test.ts) and the survey-tree hierarchy + leg-visibility logic (test/surveyTree.test.ts).

Deploy — Cloudflare Pages

Pure static output, no server code or functions.

Setting Value
Build command npm run build
Build output directory dist
Node version 20

Auto-deploys on push to main. The build emits a relative base so it works from any path.

Adding a new format parser

  1. Create src/parser/<format>.ts exporting parse<Format>(buffer: ArrayBuffer): CaveModel. Depend only on types.ts and byteCursor.ts — never on the renderer.
  2. For binary formats, work against the official spec; for byte layouts, cross-check a reference implementation rather than guessing.
  3. Dispatch to it by extension in parseCaveFile (src/parser/index.ts).
  4. Commit a small fixture with known coordinates and a golden test that asserts the numbers — ideally validated against the format's own reference tooling.
  5. The renderer needs no changes: it consumes the normalized CaveModel.

Controls

Two drag schemes, toggled by the "Drag: Pan / Orbit" toolbar button (your choice is remembered):

  • Pan (default, Google Earth–style): left-drag pans · right-drag orbits (rotate + tilt) · scroll zooms
  • Orbit (3D-viewer / Aven-style): left-drag orbits · right-drag pans · scroll zooms

ViewCube (bottom-right): drag the cube to orbit, or click a face to snap to that view. Replaces dedicated cardinal-elevation buttons — the faces are labelled with the compass directions.

View controls (panel, top-right):

  • Quick views: Plan (top-down, North up — locked to orthographic) and 3D. P = plan.
  • Projection: toggle Perspective ⇄ Orthographic (true-scale); locked to orthographic in plan view.
  • Colour by: elevation, distance-from-entrance, gradient (steepness), survey/series, or single colour. The legend adapts to the mode.
  • Vertical exaggeration: a 1×–8× slider to stretch deep caves vertically (no effect in plan).
  • Show: toggle splay / surface / duplicate legs, and the passage-wall mesh (Walls — Therion .lox scrap meshes, or tubes reconstructed from LRUD cross-sections for .3d/.plt).
  • Surveys: a collapsible tree to show/hide individual survey series.
  • Find station…: type to locate a station; choosing one pans to it and selects it.

Selecting & measuring:

  • Hover a station to see its name; click it for a panel with name, position, elevation, and distance from the entrance. Entrances (green) and fixed points (amber) are marked.
  • Measure: toolbar toggle; click two stations for the straight-line / horizontal / vertical distance and compass bearing.

Toolbar (bottom):

  • Fit to view: the "Fit view" button or press F.
  • Save PNG: download the current view as an image.
  • Units: toggle metric ⇄ imperial. Theme: toggle dark ⇄ light. (Both remembered.)
  • Open a file: drag-and-drop a .3d / .plt / .lox anywhere, or use "Open file…".

License

This project is licensed under the MIT License (SPDX-License-Identifier: MIT) — see LICENSE. The source code and the built static site (dist/) are MIT and contain no third-party copyleft material.

Test vectors: test/fixtures/survex/ and test/fixtures/compass/ vendor small reference test vectors from the Survex project (GPL v2+), used only by the test suite to validate parser interoperability against genuine reference-tool output. They are not imported by the app and are not in the build output (mere aggregation, not a derivative work). See each directory's PROVENANCE.md and the top-level NOTICE for details.

About

Web Native 3D Cave Survey Viewer

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors