Drawing Architecture
This note defines the first clean drawing system for space.
Raster follow-up work is documented separately in Raster Drawing Implementation.
Internally the system is called drawing. The user-facing canvas feature label can be Draw.
Summary
The first drawing milestone should be:
- world-owned
- canvas-rendered
- graph-independent
- vector-only
- layer-based
- undoable from the start
Graph view and drawing can coexist visually in the same canvas, but only one canvas feature is interactive at a time.
User Flow
Canvas Feature Switching
Canvas interaction should have a second axis besides scene vs canvas.
scenevscanvasremains the top-level interaction surface- inside
canvas, the active feature is eithergraphordrawing
When drawing is active:
- graph remains visible
- graph is not interactive
- drawing owns canvas interactions
- right-drag pan and wheel zoom still work
HUD Layout
The HUD needs a dedicated left dock layer, separate from tiles and floating panels.
The intended order is:
- narrow canvas feature rail on the far left
- drawing sidebar immediately to its right when drawing is active
The drawing sidebar should contain:
Layerssection at the topToolssection belowTool Defaultssection below that
This should not be implemented as a floating dialog. It is persistent feature chrome for the active world.
Tool Behavior
The active drawing tool is persistent.
- entering drawing restores the last selected tool
- shape tools stay active until changed
Esccancels the active gesture only- leaving drawing happens through the canvas feature rail, not through
Esc
Initial tools:
SelectRectangleEllipseLinePenBrushMarkerEraser
Selection Rules
V1 selection is intentionally narrow.
- selection only works in the active layer
- click selects one object
Ctrl+click toggles object membership- click on empty space clears selection
- multi-select is supported
- marquee selection is deferred
- selected objects can be deleted
- selected objects cannot yet be moved, resized, or restyled
Layer Rules
Layers are visible from day one.
V1 layer operations:
- add
- rename
- delete
- reorder
- activate
V1 does not include:
- hide
- lock
- grouping
If drawing is entered with no layers, the system auto-creates Layer 1.
Deleting a layer is immediate. Undo is the safety mechanism.
Ownership And Persistence
The world owns drawing, not graph.
Recommended persisted shape:
world
canvas
active_feature
drawing
document
uidrawing.document is persistent content.
drawing.ui is persistent editor state for that world.
This keeps the drawing content independent from transient controller/runtime objects while still restoring the user’s editing context after reload.
Drawing Content vs UI State
Persist in drawing.document:
- layers
- vector objects
- object ids
- layer order
Persist in drawing.ui:
- active tool
- active layer
- tool defaults
- possibly selection, if restoring selection is desirable later
Do not store undo/redo stacks in world state.
Document Model
The first document should support typed layers even though V1 only exposes vector layers.
Document
{:version 1
:next-layer-id 1
:next-object-id 1
:layers [...]}Layer
{:id "layer-1"
:name "Layer 1"
:kind "vector"
:objects [...]}Common Object Fields
{:id "object-1"
:kind ...
:style {...}}Rectangle
{:id "object-1"
:kind "rectangle"
:x 10
:y 20
:w 80
:h 50
:style {...}}Ellipse
{:id "object-2"
:kind "ellipse"
:x 10
:y 20
:w 80
:h 50
:style {...}}Line
{:id "object-3"
:kind "line"
:x1 10
:y1 20
:x2 80
:y2 120
:style {...}}Stroke
{:id "object-4"
:kind "stroke"
:preset "pen"
:points [[10 20] [11 21] [13 24]]
:style {...}}pen, brush, and marker should share the same persistent stroke model.
They differ by:
- default style presets
- rendering semantics
They should not use three unrelated data models.
Style Model
V1 style controls are creation defaults only.
They are not an inspector for selected objects.
Recommended default fields:
- stroke color
- fill color
- thickness
- opacity
- filled?
- outline?
Typical behavior:
- tool defaults are edited in the sidebar
- new objects copy those defaults at creation time
- later object styling changes will operate on object-local style
Controller Model
Drawing needs a dedicated controller/runtime layer, not just raw document tables.
The controller should own:
- active?
- active layer id
- current tool
- current defaults
- current selection ids
- current hover target
- undo stack
- redo stack
It should also expose high-level operations:
- add object
- delete selected
- add layer
- rename layer
- delete layer
- reorder layer
- activate layer
- begin gesture
- cancel gesture
- undo
- redo
The controller is the main integration point for UI buttons, keybindings, and gesture states.
Input And State Model
Global app states should not explode into drawing variants.
Keep:
normalleadercamerafpc
Add drawing-specific temporary capture only for active gestures.
Recommended pattern:
- a
drawing-gesture-manager - one
drawing-gesture-state
This should mirror the existing terrain paint / rect-pick pattern:
- controller decides a gesture should begin
- gesture manager records previous state
- app enters the temporary drawing gesture state
- gesture consumes left-button drawing input
- gesture completion or cancel restores the previous state
The temporary gesture state should be used for:
- rectangle drag
- ellipse drag
- line drag
- freehand stroke
- eraser drag
It should not be used just because drawing is active.
Selection And Hit Testing
Do not build V1 drawing selection on top of the current ObjectSelector.
ObjectSelector is box-oriented and expects a single projected position per selectable. That is the wrong primitive for shapes and strokes.
Drawing needs its own active-layer hit testing:
- rectangle / ellipse hit test
- line hit test with tolerance
- stroke hit test against sampled segments
Selection tolerance should be computed in canvas world units from canvas.world-units-per-pixel, so hit behavior stays stable across zoom levels.
Eraser should use the same hit-test machinery but collect touched objects continuously during the drag.
V1 eraser semantics:
- erase whole touched objects
- no partial stroke cutting yet
Undo / Redo Model
Undo/redo must be designed in from the beginning.
It should be drawing-local, not shared with graph or other systems.
Use explicit commands with apply and revert.
Recommended command kinds:
add-objectdelete-objectsadd-layerrename-layerdelete-layerreorder-layer
Rules:
- one completed gesture = one history entry
- one layer operation = one history entry
- layer deletion must restore the layer, its contents, and its original order on undo
- active-layer changes are controller state, not history entries
Undo/redo stacks are runtime state only.
Rendering Model
Drawing should render directly into the canvas build context.
This allows:
- graph and drawing to coexist visually
- drawing to share the existing canvas camera and projection
- no special offscreen drawing surface for V1 vector work
Important Constraint
The current line renderer is not sufficient for thick drawing strokes.
V1 should not rely on plain GL lines for committed drawing geometry except possibly temporary debug previews.
Recommended render approach:
- rectangles and filled ellipses: quad / triangle geometry
- outlines: triangle ribbons or cached outline geometry
- lines: triangle ribbons
- strokes: triangle ribbons built from adjacent sampled points
- selection highlight: separate higher-depth overlay geometry
- live previews: same geometry path as committed objects when practical
This gives consistent thickness and makes future styling easier.
Canvas Feature Gating
Canvas needs feature-specific pointer targets.
There are two problems to solve:
- graph and drawing both live on the canvas
- only the active canvas feature should be interactive
Recommended approach:
- keep the real canvas render target unchanged
- introduce feature-specific pointer targets that delegate
screen-pos-rayto the canvas - objects registered by graph use the graph target
- objects registered by drawing use the drawing target
- pointer enablement checks decide which target is currently interactive
This keeps visual coexistence while giving clean interaction exclusivity.
Module Plan
Recommended new modules:
assets/lua/drawing/document.fnlassets/lua/drawing/defaults.fnlassets/lua/drawing/controller.fnlassets/lua/drawing/history.fnlassets/lua/drawing/commands.fnlassets/lua/drawing/hit-test.fnlassets/lua/drawing/render.fnlassets/lua/drawing/sidebar-view.fnlassets/lua/drawing/feature-rail-view.fnlassets/lua/drawing-gesture-state.fnlassets/lua/drawing-gesture-manager.fnl
Likely integration changes:
assets/lua/home-world.fnlassets/lua/main.fnlassets/lua/hud-layout.fnlassets/lua/hud-control-panel-layout.fnl
Implementation Order
Phase 1
- add drawing state defaults to world persistence
- add world runtime creation for drawing controller/runtime
- add canvas active feature state
- add left HUD dock support
- add feature rail with
GraphandDraw
Phase 2
- add drawing document model
- add layers UI
- add tool defaults UI
- persist active tool and defaults in world UI state
Phase 3
- add drawing-local history
- add select and delete
- add active-layer-only hit testing
- add selection highlight
Phase 4
- add rectangle, ellipse, and line creation with live preview
- add shift-constrained creation
- commit one history entry per completed gesture
Phase 5
- add pen, brush, and marker as stroke presets over shared stroke objects
- add eraser drag that deletes whole touched objects
Explicitly Deferred
These are out of scope for V1:
- raster layers
- hidden layers
- locked layers
- groups
- marquee selection
- move / resize editing
- selected-object style editing
- partial stroke erasing
- snapping
- graph integration of drawing content
- SVG import/export
Core Decisions
These decisions are now fixed for the first implementation:
- use the internal name
drawing - drawing is world-owned
- one drawing document per world
- typed layers from the start, vector only in V1
- visible layers from day one
- selection only in the active layer
- creation defaults only, no selected-object style editing in V1
- graph and drawing coexist visually but only one is interactive
- undo/redo is drawing-local
