feat(nodes): mode-aware roof-segment edit + 2D rotation parity with 3D
Roof-segment edit/move ignored the active snap mode and showed no chip: - add snapProfile:'structural' so a body-move resolves the no-angle polygon context (grid/lines/off) like every other structural move; - resize uses getSegmentGridStep() (0 outside grid mode = the "smooth" resize that used to need a held Shift), dropping the captured gridSnapStep + Shift; - move drops its Shift bypass; - the affordance dispatcher opens a boundary reshape scope for the resize so the snapping chip shows and the context resolves. 2D rotation handles now match the 3D gizmo across all six rotate affordances (column / elevator / roof-segment / shelf / spawn / stair): a shared rotateAffordanceDelta snaps to the 15° step unless Shift (free), the dispatcher opens the same ROTATE_HANDLE_DRAG_LABEL handle-drag scope the 3D gizmo uses so the contextual HUD shows the "Shift = rotate freely" hint, and the live degree readout snaps to match the committed rotation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7512fccdaf
commit
84cdf4519d
@@ -0,0 +1,24 @@
|
||||
import { DEFAULT_ANGLE_STEP } from '@pascal-app/core'
|
||||
|
||||
/**
|
||||
* Shared rotation delta for the 2D corner rotate-arrow affordances (column /
|
||||
* elevator / roof-segment / shelf / spawn / stair — all structurally
|
||||
* identical). Measures the pointer's angular sweep from the grab bearing
|
||||
* around the node center, wrapped to [-π, π] so a drag crossing ±π keeps its
|
||||
* sign, then snaps it to the 15° increment unless `free` (the held Shift the
|
||||
* contextual HUD advertises). The 2D twin of the 3D gizmo's
|
||||
* `snapDirectRotationDelta`, so rotating a node reads the same in both views.
|
||||
*/
|
||||
export function rotateAffordanceDelta(args: {
|
||||
center: readonly [number, number]
|
||||
initialAngle: number
|
||||
planPoint: readonly [number, number]
|
||||
free: boolean
|
||||
}): number {
|
||||
const { center, initialAngle, planPoint, free } = args
|
||||
const currentAngle = Math.atan2(planPoint[1] - center[1], planPoint[0] - center[0])
|
||||
let delta = currentAngle - initialAngle
|
||||
while (delta > Math.PI) delta -= 2 * Math.PI
|
||||
while (delta < -Math.PI) delta += 2 * Math.PI
|
||||
return free ? delta : Math.round(delta / DEFAULT_ANGLE_STEP) * DEFAULT_ANGLE_STEP
|
||||
}
|
||||
Reference in New Issue
Block a user