Skip to content

Entity rotation #43

Open

Description

@lucasfelixs

Context

Entities are axis-aligned rectangles: Entity.bounds is { x, y, width, height }, with no rotation. Real floor plans need rotated items: desks at an angle, diagonal walls, a table turned 45°.

Rotation touches almost everything (types, hit testing, selection overlay, resize, snapping, the drag math), so this issue is design-first. Agree on the approach in the comments before writing code.


What changes (proposal)

Model. Add an optional rotation to the entity, in degrees, clockwise, around the center of bounds:

export interface Entity {
  id: EntityId;
  layer: LayerId;
  bounds: Bounds;        // unrotated box
  rotation?: number;     // degrees, default 0
  data?: unknown;
  selectable?: boolean;
}

bounds keeps meaning the unrotated box. That keeps existing scenes valid and makes rotation: 0 behave exactly like today.

Core

  • hitTestPoint: transform the point into the entity's local space (inverse rotation around the center), then use the existing AABB test.
  • sceneBounds / fitToScene: use the AABB of the rotated rectangle.
  • A helper such as rotatedAABB(bounds, rotation): Bounds in utils.ts.
  • applyResizeDelta: resize in local space so handles follow the rotated edges.

SVG and React

  • Render each entity inside <g transform="rotate(r cx cy)">, so drawEntity keeps drawing in unrotated coordinates.
  • Rotate the selection overlay and the resize handles with the entity.
  • Optionally, a rotation handle above the n handle, with snapping to 15° steps while Shift is held.

Open questions

  • Degrees or radians? Proposal: degrees (easier for app code and serialization).
  • Should rotation be a separate milestone step: first model, rendering and hit testing; then a rotation handle; then resize of rotated entities?
  • How does snap-to-grid apply to rotated entities: snap the center, or the AABB?

Affected files

File Change
packages/core/src/types.ts rotation?: number on Entity
packages/core/src/picking.ts Rotation-aware hit test
packages/core/src/store.ts sceneBounds uses rotated AABBs
packages/core/src/utils.ts Rotation helpers; applyResizeDelta in local space
packages/svg/src/renderer.ts, selection-overlay.ts, resize-handles.ts, handlers.ts Rotated rendering and interaction
packages/adapters/react/src/SpatialCanvas.tsx, internal/* Same for React

Invariants to preserve

  • rotation undefined or 0 behaves exactly as today. All existing tests pass unchanged.
  • drawEntity keeps receiving unrotated bounds; the renderer applies the transform.
  • No production dependencies.

Implementation checklist

  • Agree on the design in this issue
  • Model, helpers and hit testing in core, with tests (0°, 90°, 45°, points near the corners)
  • Rotated rendering, overlay and handles in SVG and React
  • Rotation handle (may be a follow-up issue)
  • Docs, example and changeset
  • Run pnpm typecheck && pnpm test && pnpm lint
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions