Docs: https://scala-tessella.github.io/dcel/
DCEL utilities for representing, building, editing and analysing edge-to-edge tessellations of unit-side polygons in Scala 3.
Tessellations are modelled as a Doubly Connected Edge List (DCEL): a classical planar-subdivision representation in which every edge is split into two oppositely oriented half-edges, and each half-edge knows its origin vertex, its incident face, its twin, its predecessor, and its successor. On top of this base representation, the library provides builders, validation, topology/geometry operations, symmetry/uniformity/lattice analysis, and import/export.
The primary public type is Tiling — a tessellation certified to satisfy
the full DCEL invariants. Constructors return it, all mutating operations live
on it, and holding one is a compile-time proof of validity (see
ADR-0017). The raw TilingDCEL structure stays
available for uncertified data and read-only queries.
Status: early development (pre-1.0). The API may still change between minor versions; 0.2.0 introduced breaking changes over 0.1.x (see the changelog).
The Tessella web and desktop editor, see https://tessell.art/editor (open here the web editor), is built on the dcel library.
Add the dependency to your build.sbt:
libraryDependencies += "io.github.scala-tessella" %% "dcel" % "0.2.0"
// Use %%% instead of %% for Scala.jsPublished for Scala 3 on the JVM and Scala.js. Scala Native is scaffolded but blocked: Spire publishes no artifact for the Scala Native 0.5 ecosystem (see ADR-0016).
- A certified tiling type.
Tilingproves its value passed full validation: get one from any constructor, fromTiling.empty(the blank canvas), or by certifying a rawTilingDCELwithTiling.from. Every mutating operation takes and returnsTiling, so validity is preserved by the type system rather than by convention. - Construct tilings from regular or simple unit-side polygons
(
TilingDCEL.createRegularPolygon,TilingDCEL.createSimplePolygon). - Build lattices quickly: triangle, rhombus/square, and hexagon nets, rings,
and holed triangle nets (
TilingBuilder.createTriangleNet,createRhombusNet,createHexagonNet,createRing,createHoledTriangleNet). - Grow and shrink tessellations with validation-aware APIs:
maybeAddRegularPolygon*,maybeAddSimplePolygon*,maybeDeleteVertex,maybeDeleteEdge,maybeDeleteFace,fanAt,doubleArea. - Multiply by isometries: weld a transformed copy of the whole tiling onto
itself —
maybeAddTranslatedCopy,maybeAddRotatedCopy,maybeAddMirroredCopy,maybeAddGlideReflectedCopy(ormaybeAddCopywith anIsometry) — plus full rings and strips:fanAround,repeatAlong,repeatGrid. - Detect periodicity:
translationLatticerecovers the primitive translation basis of a periodic patch, andlargestContainedParallelogonreturns the corner vertices of the largest translational unit the patch contains — the infinite tessellation it tends to. - Analyse topology, symmetry and uniformity: boundary, inner vertices,
vertex angles,
uniformityTree,scanUniformityTree,gonalityTrees, boundary-equivalence grouping, rotational/reflectional symmetry. - Validate completeness, topology, geometry, and spatial consistency in
one call (
TilingValidation.validate). Failures are surfaced as a sealedTilingErrorADT (IncompleteError,TopologyError,GeometryError,SpatialError,ValidationError,NotFoundError). - Export to SVG (with optional uniformity colouring, arrows, labels) and
DOT, animated uniformity refinement (
SvgAnimation), single-polygon and parallelogon-tiling renders (SimplePolygonSVG), plus round-trip serialisation through SVG metadata (toMetadata/TilingSVG.fromMetadata).
| Item | Version / Status |
|---|---|
| Scala | 3.8.4 |
| Published artifact | "io.github.scala-tessella" %% "dcel" % "0.2.0" (Maven Central) |
| SBT | 1.12.11 |
| Platforms | JVM, Scala.js (CommonJS on Node.js) |
| Scala Native | scaffolded; blocked — no Spire artifact for Native 0.5 (ADR-0016) |
| Core deps | ring-seq 0.8.0, iron 3.3.1, spire 0.18.0 |
| Test deps | ScalaTest 3.2.20, ScalaCheck 1.19.0 |
dcel/
├── build.sbt # Cross-project (JVM + JS) build and compiler flags
├── project/plugins.sbt # sbt-scalajs, sbt-scalafmt, sbt-scalafix, sbt-ci-release, …
├── adr/ # Architecture Decision Records (design rationale)
├── shared/src/main/scala/io/github/scala_tessella/dcel/
│ ├── Tiling.scala # The certified tiling type: trust boundary + all mutators
│ ├── TilingDCEL.scala # Raw DCEL case class; read-only queries
│ ├── TilingBuilder.scala # Polygon/net/ring constructors (validation facade)
│ ├── TilingNetBuilder.scala # Lattice-construction plumbing (package-private)
│ ├── TilingAddition.scala # Polygon-by-polygon growth pipeline
│ ├── TilingGrowthWiring.scala # Low-level DCEL surgery behind growth (package-private)
│ ├── TilingDeletion.scala # Removal primitives
│ ├── TilingMerge.scala # Tiling union: coincidence matching, enclosed regions
│ ├── TilingMultiplication.scala # Copy-based growth: isometric copies, fans, repeats
│ ├── Isometry.scala # Translation / Rotation / Reflection / GlideReflection
│ ├── TilingValidation.scala # Completeness, topology, geometry, spatial checks
│ ├── TilingEquivalency.scala # Deep copy, boundary signature & equivalence grouping
│ ├── TilingUniformity.scala # Uniformity/gonality trees
│ ├── TilingSymmetry.scala # Rotational/reflectional symmetry queries
│ ├── TilingLattice.scala # Translation-lattice detection, largest parallelogon
│ ├── TilingError.scala # Sealed error ADT + helpers
│ ├── Tree.scala # Algebraic Leaf/Branch tree used for uniformity
│ ├── geometry/ # BigDecimal-based primitives (see below)
│ ├── structure/ # DCEL entities: Vertex, HalfEdge, Face, IDs, Prefixable
│ └── conversion/ # SVG/DOT exporters, animation, SVG-metadata round-trip
├── shared/src/test/scala/… # Unit + ScalaCheck property tests
├── shared/src/test/resources/ # SVG fixtures used by tests
├── benchmarks/ # JMH suites + RUNBOOK.md (opt-in, unpublished)
├── generator/ # Experimental n-uniform tiling search (opt-in, unpublished)
└── README.md
Two opt-in sbt subprojects live in this repo but are not part of the published
dcel_3 artifact:
benchmarks/— JMH micro-benchmarks for the construction, validation, uniformity, and copy-operation hot paths, with committed JSON baselines for before/after comparison. Seebenchmarks/RUNBOOK.md; run withsbt 'benchmarks/Jmh/run …'.generator/— research-grade enumerator of n-uniform n-archimedean tilings (TilingGenerator.findTilings,expandRotationally,expandRotationallyMore). API and search heuristics are still in flux; several exploratory tests are disabled. Run withsbt generator/test. When the surface stabilises, this may be promoted to its own published artifact.
| Package | Role |
|---|---|
io.github.scala_tessella.dcel |
Core: Tiling (certified type + mutators), TilingDCEL (raw structure + queries), builders, validation, analysis, errors |
io.github.scala_tessella.dcel.geometry |
Deterministic BigDecimal/Spire primitives: BigPoint, BigLineSegment, BigBox, BigRadian, AngleDegree, RegularPolygon, SimplePolygon, Orientation, SpatialGrid, IntersectionDetection |
io.github.scala_tessella.dcel.structure |
Vertex, HalfEdge, Face + opaque VertexId/FaceId, Prefixable |
io.github.scala_tessella.dcel.conversion |
TilingSVG (with SvgOptions), SimplePolygonSVG, SvgAnimation, TilingDOT, SVG-metadata round-trip |
Only the root package depends on the other three; conversion depends on
everything, structure depends on geometry, geometry has no internal deps.
- JDK 17 or later (CI runs on Temurin 17).
- sbt (
1.12.11used by this project; launcher will pick up the version). - Node.js on
PATHfor the Scala.js test runner.
# compile all modules
sbt compile
# run all tests
sbt test
# run target-specific tests
sbt dcelJVM/test
sbt dcelJS/test
# format + scalafix + tests (alias defined in build.sbt)
sbt qaimport io.github.scala_tessella.dcel.{Tiling, TilingDCEL}
import io.github.scala_tessella.dcel.geometry.RegularPolygon
import io.github.scala_tessella.dcel.structure.VertexId
val base: Tiling = TilingDCEL.createRegularPolygon(RegularPolygon(6))
val grownEither = // Either[TilingError, Tiling]
base.maybeAddRegularPolygonToBoundary(
onEdgeStartingWith = VertexId(1),
polygon = RegularPolygon(3)
)
val svgEither = grownEither.map(_.toSVG(scale = 80.0))Every mutating operation lives on the certified Tiling type, returns an
Either[TilingError, Tiling], and operates on an internal deep copy — the
input value is never modified. Start an editor session from Tiling.empty,
or certify a hand-assembled TilingDCEL with Tiling.from (which runs the
full validation).
import io.github.scala_tessella.dcel.TilingBuilder
val rhombusNetEither = TilingBuilder.createRhombusNet(width = 3, height = 3)
val holedTriangleEither =
TilingBuilder.createHoledTriangleNet(10, 10): (x, y) =>
x > 2 && x < 7 && y > 2 && y < 7// Mirror a pentagon across one of its edges, then fan the cluster:
// the six-pentagon Dürer cluster in two calls.
val pentagon = TilingDCEL.createRegularPolygon(RegularPolygon(5))
val coords = pentagon.coordinates
val duerer =
for
two <- pentagon.maybeAddMirroredCopy(coords(VertexId(2)), coords(VertexId(3)))
all <- two.fanAround(pentagon.coordinates.values.toList.centroid, 5)
yield allimport io.github.scala_tessella.dcel.TilingLattice.{largestContainedParallelogon, translationLattice}
val netEither = TilingBuilder.createHexagonNet(6, 6)
val basis = netEither.map(_.translationLattice()) // primitive lattice basis
val corners = netEither.map(_.largestContainedParallelogon()) // ordered corner verticesThe uniformity operations live as extension methods in TilingUniformity:
import io.github.scala_tessella.dcel.TilingUniformity.*
val tree = base.uniformityTree
val uncompressed = base.uniformityTreeUncompressed()
val scan = base.scanUniformityTreeimport io.github.scala_tessella.dcel.conversion.TilingSVG.SvgOptions
val dot: String = base.toDOT
val svg: String = base.toSVG(SvgOptions(scale = 80.0, showUniformity = true))import io.github.scala_tessella.dcel.conversion.TilingSVG.{fromMetadata, toMetadata}
val metadata = base.toMetadata // <tessella:tessella-dcel> XML element
val reconstructed = fromMetadata(metadata) // Either[TilingError, Tiling]- Public fallible APIs generally return
Either[TilingError, A]. TilingErroris a sealed trait withValidationError,IncompleteError,TopologyError,GeometryError,SpatialError, andNotFoundErroralternatives, plus aTilingError.combineErrorshelper.- Validity lives in the type system: a
Tilingis proof its value passedTilingValidation.validate, so queries that depend on well-formedness (e.g.boundarySimplePolygon,gonalityTrees) are total onTilingand simply absent from the rawTilingDCEL. No publicUnsafemethods remain — the safe/Unsafepairing of ADR-0003 is now an internal convention only. - IDs are opaque types (
VertexId,FaceId) and format themselves through thePrefixabletrait (e.g.v1,f7). - Design rationale is recorded as Architecture Decision Records under
adr/.
- Formatting:
scalafmtvia SBT plugin;scalafmtOnCompileis enabled. - Lint / refactoring:
scalafixwith SemanticDB. - Compiler hygiene:
-deprecation -feature -unchecked -Wvalue-discard -Wnonunit-statement -Wunused:imports, elevated to-WerrorinCompileonly; tests are compiled with warnings, not fatal. - Cross-building: JVM and Scala.js share all sources via
crossType(Full). A Scala Native project is scaffolded innative/and will be enabled as soon as Spire ships an artifact for the Scala Native 0.5 ecosystem (ADR-0016).
Pure unit tests plus ScalaCheck property-based tests (see
PropertyBasedDCELSpec.scala), including the ADR-0017 certification property
(every tiling reachable through the public API must re-certify via
Tiling.from) and a merge-engine property over random isometric-copy
sequences. SVG fixtures under shared/src/test/resources/ are used as
reference snapshots for the builders and the SVG exporter.
Dual-licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHEor https://www.apache.org/licenses/LICENSE-2.0) - MIT license (
LICENSE-MITor https://opensource.org/licenses/MIT)
at your option.