From 49c4794ecbcf365defecac9b7999ff4a1c40b1e3 Mon Sep 17 00:00:00 2001 From: wass08 Date: Mon, 23 Feb 2026 12:41:55 +0900 Subject: [PATCH] fix item scale surface --- apps/editor/components/tools/item/move-tool.tsx | 4 +++- apps/editor/components/tools/item/placement-strategies.ts | 4 ++-- apps/editor/components/tools/item/use-draft-node.ts | 5 +++-- .../components/tools/item/use-placement-coordinator.tsx | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/editor/components/tools/item/move-tool.tsx b/apps/editor/components/tools/item/move-tool.tsx index 7d73c926..c3cc7c38 100644 --- a/apps/editor/components/tools/item/move-tool.tsx +++ b/apps/editor/components/tools/item/move-tool.tsx @@ -34,13 +34,15 @@ function MoveItemContent({ movingNode }: { movingNode: ItemNode }) { draftNode, // Duplicates start fresh in floor mode; wall/ceiling draft is created lazily by ensureDraft initialState: isNew ? { surface: 'floor', wallId: null, ceilingId: null, surfaceItemId: null } : getInitialState(movingNode), + // Preserve the original item's scale so Y-position calculations use the correct height + defaultScale: isNew ? movingNode.scale : undefined, initDraft: (gridPosition) => { if (isNew) { // Duplicate: use the same create() path as ItemTool so ghost rendering works correctly. // Floor items get a draft immediately; wall/ceiling items are created lazily on surface entry. gridPosition.copy(new Vector3(...movingNode.position)) if (!movingNode.asset.attachTo) { - draftNode.create(gridPosition, movingNode.asset, movingNode.rotation) + draftNode.create(gridPosition, movingNode.asset, movingNode.rotation, movingNode.scale) } } else { draftNode.adopt(movingNode) diff --git a/apps/editor/components/tools/item/placement-strategies.ts b/apps/editor/components/tools/item/placement-strategies.ts index 0747b33d..69daf861 100644 --- a/apps/editor/components/tools/item/placement-strategies.ts +++ b/apps/editor/components/tools/item/placement-strategies.ts @@ -411,7 +411,7 @@ export const itemSurfaceStrategy = { const x = snapToGrid(localPos.x, ourDims[0]) const z = snapToGrid(localPos.z, ourDims[2]) - const y = surfaceItem.asset.surface.height + const y = surfaceItem.asset.surface.height * surfaceItem.scale[1] const worldSnapped = surfaceMesh.localToWorld(new Vector3(x, y, z)) @@ -445,7 +445,7 @@ export const itemSurfaceStrategy = { const x = snapToGrid(localPos.x, ourDims[0]) const z = snapToGrid(localPos.z, ourDims[2]) - const y = surfaceItem.asset.surface.height + const y = surfaceItem.asset.surface.height * surfaceItem.scale[1] const worldSnapped = surfaceMesh.localToWorld(new Vector3(x, y, z)) diff --git a/apps/editor/components/tools/item/use-draft-node.ts b/apps/editor/components/tools/item/use-draft-node.ts index 07213229..4d52a944 100644 --- a/apps/editor/components/tools/item/use-draft-node.ts +++ b/apps/editor/components/tools/item/use-draft-node.ts @@ -18,7 +18,7 @@ export interface DraftNodeHandle { /** Whether the current draft was adopted (move mode) vs created (create mode) */ readonly isAdopted: boolean /** Create a new draft item at the given position. Returns the created node or null. */ - create: (gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number]) => ItemNode | null + create: (gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number], scale?: [number, number, number]) => ItemNode | null /** Take ownership of an existing scene node as the draft (for move mode). */ adopt: (node: ItemNode) => void /** Commit the current draft. Create mode: delete+recreate. Move mode: update in place. */ @@ -40,13 +40,14 @@ export function useDraftNode(): DraftNodeHandle { const adoptedRef = useRef(false) const originalStateRef = useRef(null) - const create = useCallback((gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number]): ItemNode | null => { + const create = useCallback((gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number], scale?: [number, number, number]): ItemNode | null => { const currentLevelId = useViewer.getState().selection.levelId if (!currentLevelId) return null const node = ItemNode.parse({ position: [gridPosition.x, gridPosition.y, gridPosition.z], rotation: rotation ?? [0, 0, 0], + scale: scale ?? [1, 1, 1], name: asset.name, asset, parentId: currentLevelId, diff --git a/apps/editor/components/tools/item/use-placement-coordinator.tsx b/apps/editor/components/tools/item/use-placement-coordinator.tsx index 93c0a8b8..dcd151e9 100644 --- a/apps/editor/components/tools/item/use-placement-coordinator.tsx +++ b/apps/editor/components/tools/item/use-placement-coordinator.tsx @@ -66,6 +66,8 @@ export interface PlacementCoordinatorConfig { onCommitted: () => boolean onCancel?: () => void initialState?: PlacementState + /** Scale to use when lazily creating a draft (e.g. for wall/ceiling duplicates). Defaults to [1,1,1]. */ + defaultScale?: [number, number, number] } export function usePlacementCoordinator(config: PlacementCoordinatorConfig): React.ReactNode { @@ -140,7 +142,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea cursorGroupRef.current.position.set(...result.cursorPosition) cursorGroupRef.current.rotation.y = result.cursorRotationY - draftNode.create(gridPosition.current, asset, [0, result.cursorRotationY, 0]) + draftNode.create(gridPosition.current, asset, [0, result.cursorRotationY, 0], configRef.current.defaultScale) const draft = draftNode.current if (draft) {