Improve editor manipulation flows

This commit is contained in:
Aymeric Rabot
2026-06-08 01:07:53 -04:00
parent ab271df9b6
commit 8dc602caa9
57 changed files with 3442 additions and 735 deletions
@@ -10,17 +10,16 @@ import {
useLiveTransforms,
useScene,
} from '@pascal-app/core'
import { snapPointToGrid, type WallPlanPoint } from '@pascal-app/editor'
import { getSegmentGridStep, type WallPlanPoint } from '@pascal-app/editor'
import type * as THREE from 'three'
import { createFloorplanCursorResolver } from './floorplan-cursor'
/**
* Shared 2D floor-plan move for polygon-based kinds (slab / ceiling / zone).
*
* **Pivot semantics.** The move uses the polygon's **centroid** as the pivot:
* the centroid snaps to the (grid-snapped, then Figma-aligned) cursor — the
* same way a regular item's origin snaps to the cursor in both 3D and 2D.
* This replaces the old grab-relative delta ("drag from wherever you first
* touched"), so polygon kinds move consistently with every other item.
* Existing polygon kinds preserve the cursor grab offset; fresh catalog
* placement uses the polygon centroid as the cursor-following pivot. This
* matches the generic 3D move tool while keeping polygon geometry in vertices.
*
* **Why a delta in `useLiveTransforms`** (see `wiki/architecture/tools.md`):
* polygon kinds carry their position in their vertices, not a `position`
@@ -35,8 +34,6 @@ import type * as THREE from 'three'
* ceiling: `height 0.01`) so the 3D mesh doesn't teleport vertically in a
* split view during the drag.
*/
const GRID_STEP = 0.5
/** Figma-style alignment threshold (meters) — parity with the 3D move tools. */
const ALIGNMENT_THRESHOLD_M = 0.08
@@ -66,6 +63,7 @@ export function createPolygonCentroidMoveTarget(args: {
type: string
polygon: Array<[number, number]>
holes?: Array<Array<[number, number]>>
metadata?: unknown
}
nodes: Record<AnyNodeId, AnyNode>
/** 3D mesh Y the kind's system parks the group at on rebuild. */
@@ -80,6 +78,10 @@ export function createPolygonCentroidMoveTarget(args: {
hole.map(([x, z]) => [x, z] as [number, number]),
)
const originalCenter = polygonCentroid(originalPolygon)
const resolveCursor = createFloorplanCursorResolver({
original: originalCenter,
metadata: node.metadata,
})
// Alignment candidates gathered once — the scene is stable during the drag.
const candidates = collectAlignmentAnchors(nodes, id)
let lastDelta: [number, number] = [0, 0]
@@ -90,9 +92,9 @@ export function createPolygonCentroidMoveTarget(args: {
// Centroid → snapped cursor. Grid-snap the target centroid (Shift
// drops the grid snap), then layer Figma alignment on the translated
// polygon's vertices and fold its snap into the delta. Alt bypasses.
const target: WallPlanPoint = modifiers.shiftKey
? ([planPoint[0], planPoint[1]] as WallPlanPoint)
: snapPointToGrid([planPoint[0], planPoint[1]] as WallPlanPoint, GRID_STEP)
const step = getSegmentGridStep()
const snap = (value: number) => (modifiers.shiftKey ? value : Math.round(value / step) * step)
const target = resolveCursor(planPoint, { snap }) as WallPlanPoint
let dx = target[0] - originalCenter[0]
let dz = target[1] - originalCenter[1]