fix(editor): preserve grab offset when moving items (no teleport-to-cursor) (#416)
* fix(editor): preserve grab offset when moving items (no teleport-to-cursor) Grabbing a placed item to move it (move-cross handle or the Move affordance) snapped the item's origin to the cursor instead of keeping the offset between the grab point and the origin. - Floor (regression): the grab-offset rewrite patched event.localPosition, but floorStrategy.move reads event.position (the world cursor) for its world-grid snap on the default non-Shift path -- a read added after the offset fix landed. Now also correct event.position, derived from the corrected building-local point via buildingLocalToWorld. - Wall / ceiling / item-surface / shelf: the shared surface strategies snapped the origin straight to the cursor with no grab offset. Added per-surface grab anchors (start + (raw - anchor)) seeded on the first move per host and reset on leave/detach; host-resting items start in their surface, and the level-reparent effect skips intentionally-hosted drafts so the dragged mesh stays on its host. Rename preserveFloorDragOffset -> preserveDragOffset (it now governs all surfaces). 2D move paths already preserved the grab offset, so no sibling change is needed -- this brings 3D into parity. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(editor): tidy grab-offset floor/wall paths (quality pass) Behaviour-preserving readability pass before shipping: - Extract the floor grab-offset IIFE into a named `applyFloorGrabOffset` helper (parity with `resolveHostSurfaceWorld`); drop redundant tuple casts via the return annotation and tighten the comment. - Wall move: always set `position` (raw hit when the wall mesh is absent) instead of a conditional object spread. A reviewed dedup of the three per-surface anchors was deliberately NOT taken — the wall (X/Y) / ceiling (X/Z) / host-surface frames are heterogeneous enough that unifying them adds more indirection than it removes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8eab9979ab
commit
4b7e0eca76
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import type { ItemNode } from '@pascal-app/core'
|
||||
import { type AnyNodeId, type ItemNode, useScene } from '@pascal-app/core'
|
||||
import {
|
||||
type PlacementState,
|
||||
triggerSFX,
|
||||
@@ -67,6 +67,32 @@ function getInitialState(node: ItemNode): PlacementState {
|
||||
shelfId: null,
|
||||
}
|
||||
}
|
||||
// A floor item resting on a host surface (table / counter / shelf) starts in
|
||||
// that surface, not 'floor', so the first pointer move runs the surface move
|
||||
// handler — which preserves the grab offset — instead of a fresh `enter()`
|
||||
// that snaps the item's origin under the cursor. Without this the item
|
||||
// teleports the instant it's grabbed.
|
||||
const parent = node.parentId ? useScene.getState().nodes[node.parentId as AnyNodeId] : undefined
|
||||
if (parent?.type === 'item') {
|
||||
return {
|
||||
surface: 'item-surface',
|
||||
wallId: null,
|
||||
roofSegmentId: null,
|
||||
ceilingId: null,
|
||||
surfaceItemId: node.parentId,
|
||||
shelfId: null,
|
||||
}
|
||||
}
|
||||
if (parent?.type === 'shelf') {
|
||||
return {
|
||||
surface: 'shelf-surface',
|
||||
wallId: null,
|
||||
roofSegmentId: null,
|
||||
ceilingId: null,
|
||||
surfaceItemId: null,
|
||||
shelfId: node.parentId,
|
||||
}
|
||||
}
|
||||
return {
|
||||
surface: 'floor',
|
||||
wallId: null,
|
||||
@@ -102,7 +128,7 @@ export function MoveItemTool({ node }: { node: ItemNode }) {
|
||||
: getInitialState(node),
|
||||
// Preserve the original item's scale so Y-position calculations use the correct height.
|
||||
defaultScale: isNew ? node.scale : undefined,
|
||||
preserveFloorDragOffset: true,
|
||||
preserveDragOffset: true,
|
||||
initDraft: (gridPosition) => {
|
||||
if (isNew) {
|
||||
// Duplicate: floor items get a draft immediately; wall/ceiling
|
||||
|
||||
Reference in New Issue
Block a user