DEV Community

cuculhart
cuculhart

Posted on

One canvas engine hosts CAD floor plans and wandering game units — position is data

A CAD floor plan of a building, and two game units wandering inside that same building. Two wildly different applications — running on the same engine.

I just open-sourced Kimetron Isle, a framework-agnostic 2D placement engine where position is data, not decoration. v1.0.0, MIT licensed.

The same engine, two faces

plan — a building sketch on a 10 mm grid. Walls are thickness×length rectangles that may not overlap each other; butt joints ("|-" T junctions) are legal because edge-touching doesn't count as an overlap. A door is the declared exception that may sit on a wall. Rulers, dimension lines, wall centerlines, and architectural areas (site / building / core / inner) are all derived — move a wall and the floor area changes.

game — two 300 mm units wander inside the building via engine.tryMove. Walls and rocks (locked, concave polygons) are impassable; the gate is the only way out. Units can't occupy the same space, and when two come within range an effect link appears — talk, attack, item exchange. The pond is a driver-side SVG pattern.

Same engine in both cases. The only difference is the policy each driver declares: "a door may overlap a wall", "rocks can't move", "units can't stack" — a few lines of driver code each.

Why build it

I built two production apps (Plesfeda, Sethera Piece) on React Flow, and learned that what a canvas library does for you stops at rendering, dragging, selection, and the viewport. Layout and domain semantics were always on my side — so the coupling was thin, and a replacement was feasible.

But a React Flow clone would be pointless. Every canvas/diagram library out there assumes screen coordinates, pixels, and a downward Y axis. In CAD, the coordinate itself is the design data — yet no engine treats it that way. That's the gap Kimetron Isle fills.

The world-coordinate model

  • Real units — mm / m / inch / px as first-class units. Define a world where 1 unit = 1 mm; zoom is a drawing scale, not a magnification
  • Selectable origin — bottom-left + Y-up (CAD/math) or top-left + Y-down (screen)
  • Built-in snapping — grid, vertices, edge midpoints, centers; drivers inject custom snaps via snapExtra
  • Collision & occupancy — solid objects can't overlap while edge-touching stays legal; canOverlap declares pair exceptions; collisionMode picks none/flag/reject
  • Rotation & polygons — rotation honored all the way down to OBB/SAT collision; kind:'polygon' hit-tests and collides correctly even when concave
  • Programmatic movement — objectsIn, canPlace, tryMove so AI-driven motion goes through the same collision policy as human gestures
  • Undo & serialization — immutable model ops, document-level undo/redo, JSON serialize

Architecture

Three layers: @kimetron/core (vanilla TypeScript — model, geometry, selection, drag, hit-testing, no DOM), @kimetron/dom (SVG/CSS renderer + pointer input), @kimetron/react (a thin adapter).

Semantics live on the user's side: Kimetron assigns meaning only up to geometry and interaction. That's exactly why a floor-plan editor and a wandering-unit demo can share one engine.

Honest status

v1.0.0 is a stability commitment — additive-only changes since 0.6.0. There are still gaps: associated dimensions, named layers, and block references are 1.x territory; Svelte/Vue adapters are "structurally possible" rather than built; collaboration and cloud storage are deliberately out of scope.

If you're building something where placement itself carries meaning — floor plans, equipment layouts, seating charts, panel assignments, game maps — I'd love to hear what you'd put on it.

Links

Top comments (0)