A framework-agnostic web-based 3D mineral crystal viewer. It generates idealized crystal geometry procedurally from crystallographic and morphology parameters — no prebuilt 3D meshes. Three.js is used only for rendering; all scientific calculations live in a renderer-neutral core.
Note: This project is a work in progress.
- All seven crystal systems (triclinic, monoclinic, orthorhombic, tetragonal, trigonal, hexagonal, cubic)
- Miller and Miller-Bravais indices
- Symmetry-equivalent faces
- Procedural crystal morphology with adjustable forms and morphology sliders
- Nine shipped minerals, each with at least two documented named habits
- Crystallographic axes and unit-cell display
- Morphology sliders and face inspection (picking, equivalent-face highlighting)
- Serializable and restorable viewer state
- Atomic structure visualization (CIF import, symmetry expansion, periodic bonds)
- CIF morphology from measured
_exptl_crystal_face_*planes, with a simplified d-spacing-based BFDH fallback when measurements are absent - Mineral appearance — physically based rendering with transmission, IOR, and colored absorption
- HDR image-based lighting from Radiance
.hdrand OpenEXR.exrpanoramas, including three-axis orientation, intensity, exposure, tone mapping, and background-only zoom - Scientific references and data provenance for all curated data
- Framework-independent core and a framework-agnostic Web Component
- Three.js used only for rendering, with no manually modeled crystal meshes
| Mineral | Crystal system | Habits | Variants | Appearance presets |
|---|---|---|---|---|
| Fluorite | Cubic | 3 | — | 3 |
| Quartz | Trigonal | 4 | 2 (left/right) | 5 |
| Calcite | Trigonal | 2 | — | — |
| Pyrite | Cubic | 2 | — | 2 |
| Anatase | Tetragonal | 2 | — | — |
| Albite | Triclinic | 2 | — | 1 |
| Gypsum | Monoclinic | 2 | — | 2 |
| Forsterite | Orthorhombic | 2 | — | 2 |
| Beryl | Hexagonal | 2 | — | 3 |
Records and provenance are version-controlled project data under crystal-data.
npm install
npm run build
npm test
npm run serve # serves demos at http://localhost:5173Open http://localhost:5173/packages/crystal-demo/ for the demo index, or go
straight to a demo:
- Basic Embedding — two independent viewer instances
- Fluorite — morphology sliders and habit selection
- Quartz — handedness, face inspection, Miller-Bravais
- Minerals — multi-mineral catalog
- Minerals with HDRI — local HDR/EXR environment lighting and presentation controls
- Atomic Structure — CIF import, bonds, lattice repetition
- CIF Morphology — measured face distances or theoretical BFDH-style fallback
- Appearance — PBR material controls
- Controls & Events — programmatic API and state save/restore
The demos are also hosted live on GitHub Pages: https://oyvindi.github.io/parametric-crystal-viewer/
crystal-data → crystal-core
crystal-three → crystal-core
crystal-viewer → crystal-core, crystal-data, crystal-three
crystal-demo → crystal-viewer
| Package | Owns |
|---|---|
crystal-core |
Renderer-neutral crystallographic calculations: lattice, symmetry, Miller indices, form and explicit-face half-space geometry, atomic expansion, periodic bonds. No runtime dependencies. |
crystal-data |
Mineral and habit schemas, curated records, provenance, CIF 1.1 structure and experimental-face import, and conversion into core inputs. |
crystal-three |
Conversion of core geometry into Three.js BufferGeometry, PBR materials, atomic rendering, and unit-cell overlays. |
crystal-viewer |
CrystalViewer class, <crystal-viewer> Web Component, CIF morphology orchestration, HDR environments, camera, picking, lifecycle, and state serialization. |
crystal-demo |
Plain-HTML reference demos using only the exported viewer boundary. |
Programmatic API. Exact types are in packages/crystal-viewer/src/index.ts and
emitted dist/*.d.ts; behavioral contracts are in viewer-api.md.
| Group | Methods |
|---|---|
| Loading | loadMineral(source), loadCif(text, options?), loadCifMorphology(text, options?), loadStructure(definition) |
| Morphology | setHabit(id), setVariant(id), setFormDevelopment(formId, value), setFormEnabled(formId, enabled), setMorphologyScale(scale), rotateModel(dx, dy, dz) |
| Inspection | selectFace(index), clearSelection(), highlightEquivalentFaces(index), getSelectedFace(), getAllFaces(), getEquivalentFaces(index), showFaceLabels(show) |
| Display | setShowAxes(show), setShowUnitCell(show), setShowBonds(show), setShowWireframe(show) |
| Atomic | setViewMode(mode), setLatticeRepetition(na, nb, nc), getStructureInfo() |
| Appearance | setAppearance(id), setAppearanceField(field, value) |
| Environment | loadEnvironment(data, format), loadHdrEnvironment(data), loadExrEnvironment(data), resetEnvironment(), setEnvironmentIntensity(value), setEnvironmentRotation(yaw, pitch, roll), setEnvironmentBackgroundVisible(show), setEnvironmentBackgroundZoom(zoom), setToneMapping(mode), setExposure(value) |
| State | getState(), setState(state) |
| Lifecycle | start(), stop(), disconnect(), reconnect(), dispose(), resetCamera(), resize(w, h), render() |
| Catalog | listMinerals(), getMineral(id) (re-exported from @crystal/data) |
The viewer is an EventTarget. Events it dispatches:
| Event | Triggered when |
|---|---|
mineral-loaded |
A mineral load commits successfully |
mineral-load-failed |
A mineral load fails; current state preserved |
load-superseded |
A newer load request supersedes an older one |
habit-changed |
The active habit changes |
variant-changed |
The active variant changes |
form-changed |
A form's development or enabled state changes |
geometry-changed |
Geometry regenerates successfully |
geometry-invalid |
An edit produces invalid geometry; last valid mesh retained |
face-selected |
A face is picked or selection is cleared |
view-mode-changed |
The view mode switches between morphology and atomic |
lattice-repetition-changed |
The atomic lattice repetition changes |
appearance-changed |
The appearance preset or a field value changes |
structure-loaded |
A structural definition loads for atomic view |
structure-load-failed |
A structural definition fails to load |
state-restored |
setState commits successfully |
state-rejected |
setState rejects malformed or incompatible state without mutation |
<script type="module">
import { defineCrystalViewerElement } from "@crystal/viewer/component";
defineCrystalViewerElement();
</script>
<crystal-viewer mineral="quartz" style="width:600px;height:400px"></crystal-viewer>The mineral attribute loads a bundled mineral on connection. getViewer()
returns the underlying CrystalViewer; dispose() releases resources permanently.
Multiple independent instances on one page are supported.
generateCrystal(crystallography, morphology) is the top-level pipeline: it
validates the unit cell, resolves symmetry (explicit operations or registry), and
generates convex crystal geometry via half-space intersection. validateCrystallography
checks inputs before generation. expandAtomicStructure and inferBonds support
atomic structure visualization. generateCrystalFromFaces builds a convex morphology
from explicit measured Miller planes and perpendicular distances without symmetry
expansion. See the package README
for the diagnostic code set.
createMineralCatalog(), listMinerals(), and getMineral(id) provide the
curated catalog. importCif(text, options?) parses CIF 1.1 into a structural
definition. validateMineral() enforces the schema and provenance contract. See
the package README. CIF imports preserve supported
experimental crystal-face measurements for morphology rendering.
createThreeGeometry(geometry) and createThreeGeometryWithPicking(geometry)
convert core geometry into Three.js BufferGeometry, recording the originating
core face index per triangle for picking. applyAppearance(material, params)
maps the seven V1 appearance fields to MeshPhysicalMaterial.
createAtomicStructure(...) builds instanced atom spheres, bond cylinders, and
unit-cell wireframes.
| Document | Contents |
|---|---|
| spec.md | Product scope, V1 checklist, capabilities, non-goals |
| scientific-model.md | Coordinates, symmetry, geometry, physical models, validation |
| data-model.md | Records, atomic expansion, bonds, CIF import, provenance |
| architecture.md | Platform, packages, boundaries, testing policy |
| viewer-api.md | Web Component, runtime behavior, events, serialization |
| plan.md | Milestones, dependencies, acceptance criteria |
| decisions/ | Technical decision records |
V1 (milestones M1–M8) is complete: geometry engine, nine minerals, atomic structure, stabilized viewer API, and appearance. Implemented post-V1 additions include CIF measured-face morphology, a simplified BFDH-style fallback, HDR/EXR environment lighting, and expanded model/environment orientation controls. Twinning (M9) and pressure/temperature effects (M10) remain later scope.