Dev.to WebDev 🛠 Dev 👁 0 📖 8 min read

Walls Are Edges: Closing Loops Into Rooms Without Lying About Area

I'm Tomás Herrera. I spend most days on backend and infrastructure at ArchyBase — queues, contracts, the boring path that keeps geometry from becoming folklore. This post is about a sibling product our team also builds,

Walls Are Edges: Closing Loops Into Rooms Without Lying About Area

Walls Are Edges — OGP tech card about closing wall loops into rooms without lying about area

I'm Tomás Herrera. I spend most days on backend and infrastructure at ArchyBase — queues, contracts, the boring path that keeps geometry from becoming folklore. This post is about a sibling product our team also builds, FloorAI, and specifically about how an interactive editor should treat walls as topology rather than as decorative polylines. The concrete surface is the free Floor Plan Maker: draw with W, close a room with R, snap exact lengths in meters or feet-inches, watch room areas update, drop furniture/doors/windows, duplicate options, export when the model is honest.

I'm not going to invent a latency number or a user count. I'm going to talk about the data model that makes "this is a room of 18.6 m²" a derived fact instead of a sticker someone typed once.

The trap: walls as strokes

A lot of early editor prototypes store a floor plan like a drawing app:

Stroke {
  points: [(x,y)...]   // freehand or snapped
  thickness
  layer
}

That is fine for PNG export. It is a bad source of truth for:

  • exact segment lengths the user can edit (4.20 m vs a string that used to mean feet),
  • doors and windows that must live on a wall, not float near one,
  • room area that must change when you drag a corner,
  • multi-floor / duplicate-option trees that must not corrupt shared geometry,
  • DXF / SVG / PDF-with-scale exports that expect closed polylines and layers.

Strokes are a view. Topology is the model.

Walls are edges; corners are vertices

The minimum durable shape I want for an interactive plan looks closer to this:

Vertex {
  id
  x, y                 // canonical unit: meters in model space
  incident_wall_ids[]
}

Wall {
  id
  v_a, v_b             // ordered endpoints
  thickness?
  openings[]           // doors/windows as parametric offsets along the edge
}

RoomCandidate / Room {
  id
  cycle: WallId[]      // oriented walk that closes
  area_m2              // derived, never user-authored as primary
  label?
}

Hotkeys like W (wall) and R (room) map cleanly onto that model: W inserts or splits edges; R asks the topology layer to find or confirm a closed cycle and materialize a room. The UI can still look like "click, drag, type a length." Underneath, length is |v_b - v_a| in the canonical unit, then formatted for display as meters or feet-inches. Display strings are not the geometry contract.

That last sentence matters more than it sounds. If you store "12' 6\"" as the wall length and re-parse it later, rounding and locale bugs become structural. Store meters (or another single SI-like unit) in the model; format on the way out; parse on the way in with an explicit unit mode.

Closing a loop is a graph problem

When a user draws three walls of a rectangle and then the fourth, you do not "paint a fill." You need a closed walk:

  1. Snap the new endpoint onto an existing vertex within a tolerance (or create a vertex and merge later).
  2. Insert the wall edge.
  3. Run a local cycle search from the touched vertices (faces of a planar embedding, or a constrained walk along wall directions).
  4. If a simple cycle appears, attach a Room whose cycle owns those walls in order.
  5. Recompute area from the ordered vertex ring.

If the fourth wall stops 2 cm short of the corner because snap failed, you do not have a room — you have a dangling edge. Showing a pastel fill anyway is how you lie about area. Better UX: keep the incomplete chain visible, refuse R (or mark the candidate as open), and tell the user which gap is still open. Coral dangling edges beat fake closed polygons.

Floor plan as graph — vertices, wall edges, room loops, and shoelace area in a topology model

Area is derived: shoelace, not a text field

Once you have an ordered ring of vertices (x_i, y_i) in meters:

area = 1/2 * |Σ (x_i * y_{i+1} - x_{i+1} * y_i)|

with y_{n+1} = y_1. That is the shoelace formula. A few contracts I treat as non-negotiable:

  • Orientation. Store cycle winding consistently (e.g. CCW for interior rooms). If a boolean subtracts a courtyard, winding encodes the sign.
  • Shared walls. Two adjacent rooms share a wall id, not a duplicated segment with slightly different coordinates. Dragging the shared edge updates both areas from the same vertices.
  • Openings do not change floor area. A door on a wall is an attribute of the edge (offset, width, swing). It may affect circulation analysis later; it must not punch a hole in the room polygon unless you explicitly model that (you almost never should for furniture-layout plans).
  • Never persist area_m2 as editable primary state. Cache it, sure. Invalidate on any vertex move that touches the cycle. If export needs square feet, convert at the boundary.

Auto room areas in a maker tool are only trustworthy if they are this kind of derived quantity. A label someone typed in a property panel will drift the first time a corner moves.

Snap, split, and merge: the unglamorous correctness layer

Interactive editors die on vertex hygiene:

  • Snap to vertex / snap to edge. Creating a T-junction must split the existing wall into two colinear edges that share the new vertex. If you only visually snap and leave one long edge underneath, doors parametric-on-edge and cycle search both lie.
  • Merge colinear walls. After a delete, two edges that became colinear with degree-2 middle vertex should collapse — or you accumulate junk vertices that break "exact length" editing.
  • Tolerance policy. Screen-pixel snap and model-space snap are different. Decide a model epsilon (e.g. a few millimeters) independent of zoom, then a separate screen tolerance for hit-testing. Mixing them is how zoomed-out clicks invent 3 cm gaps that look closed.

None of this is glamorous. All of it decides whether "exact lengths" and "room areas auto" are real features or marketing copy.

Hosted geometry: doors, windows, furniture

A free maker that advertises large catalogs — thousands of furniture items, dozens of doors, many windows — still has a simple hosting rule:

Object Host Parameterization
Door / window Wall edge offset along edge, width, side/swing
Furniture Room (or floor slab) pose in room-local or world XY, optional snap to walls
Dimension label derived from edge never authoritative

Hit-testing then becomes staged: first spatial index over wall AABBs and furniture AABBs; then refine to edge parameter or furniture local bounds. You do not need a exotic index on day one, but you do need some index once the scene is dense — a flat array of 4000 furniture hits on every pointer move will feel wrong even if you never publish a benchmark.

The important architectural point: openings are not free polygons in the same bag as walls. If a door is just another stroke, moving the wall does not move the door, and DXF layers become archaeology.

Options and floors without corrupting shared geometry

Duplicate layout options and multi-floor plans tempt a shallow deepcopy of the whole document. That works until two options were supposed to share a core shell, or until an undo stack aliases a vertex id across forks.

A pattern that stays sane:

Document {
  vertex_pool: map id -> Vertex
  floors: map floor_id -> Floor
  options: map option_id -> OptionRef  // points at a floor graph revision
}

FloorGraphRevision {
  walls, rooms, placements
  parent_revision?
}

Duplicating an option clones the revision (copy-on-write if you are ambitious; deep copy of the graph if you are honest and small). It must mint new wall/room ids or carefully namespace them so edits in option B cannot mutate option A's cycles. Multi-floor is the same idea on the Z axis: each floor has its own graph; vertical alignment is a constraint between graphs, not one mashed coordinate space with overlapping ids.

If you skip revision identity, "duplicate" becomes "alias," and the first shared drag corrupts both.

Export is a consumer of topology, not a second editor

PNG/SVG at SD/HD/4K, PDF with paper size and scale, and DXF are different projections of the same graph:

  • Raster / SVG (visual): stroke walls, fill room cycles, draw furniture sprites, respect color vs B&W mode.
  • PDF with scale: same geometry plus a scale bar derived from model meters → paper units. If the cycle is wrong, the scale bar makes the lie official.
  • DXF: layers for walls, openings, furniture; polylines from room cycles; units declared once. Exporters should walk Wall/Room — not re-trace pixels from a canvas bitmap.

Trace-a-plan (upload underlay) fits as a background image with its own transform, not as walls. Vectorization into walls is a separate pipeline; until edges exist in the graph, you still do not have rooms.

Lock modes are just permissions on mutations: lock vertices / walls / placements so hit-testing still works but commits no-op. They do not change the topology contract.

A practical checklist before you trust "auto area"

When I review an editor path — ours or anyone's — I ask:

  1. Is there a single canonical unit in model space?
  2. Does inserting a T-junction split the wall edge in the data model?
  3. Can I point to the cycle (ordered wall ids) for every room that shows an area?
  4. Does dragging a shared vertex recompute both adjacent areas without a manual refresh?
  5. Are doors/windows parameterized on walls, so wall moves carry them?
  6. Does duplicate-option create a new graph revision instead of aliasing ids?
  7. Do DXF/SVG exporters read topology, or do they scrape the canvas?

If any answer is "we fake it in the renderer," area is a rumor.

Why this belongs next to a real maker

Product surfaces that expose templates (apartments, offices, restaurants, single rooms), catalogs of furniture/doors/windows, exact length entry, auto room areas, duplicate options, multi-floor, and serious export formats only stay coherent if the scene is a graph with closed cycles. The UI can stay approachable — keyboard W / R, snap, lock, trace underlay — while the model stays strict.

That is the systems habit I care about: make the interactive floor-plan editor's truth the same truth the exporter and the area label read. Everything else is chrome.

If you are building something in this shape, start with vertices, walls, and cycle detection. Add catalogs and pretty fills after the loops close without lying.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.