fix mechanism
This commit is contained in:
@@ -75,9 +75,35 @@ export const ItemTool: React.FC = () => {
|
||||
revalidate()
|
||||
}
|
||||
|
||||
// ---- Create initial draft ----
|
||||
/**
|
||||
* Create a draft from a transition result on the first valid move.
|
||||
* If placement is invalid at this position, the draft is immediately destroyed
|
||||
* so no item appears in the scene until the cursor reaches a valid spot.
|
||||
*/
|
||||
const ensureDraft = (result: TransitionResult) => {
|
||||
gridPosition.current.set(...result.gridPosition)
|
||||
cursorRef.current.position.set(...result.cursorPosition)
|
||||
cursorRef.current.rotation.y = result.cursorRotationY
|
||||
|
||||
draftNode.create(gridPosition.current, selectedItem)
|
||||
|
||||
const draft = draftNode.current
|
||||
if (draft) {
|
||||
Object.assign(draft, result.nodeUpdate)
|
||||
useScene.getState().updateNode(draft.id, result.nodeUpdate)
|
||||
}
|
||||
|
||||
if (!revalidate()) {
|
||||
draftNode.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Create initial draft (floor items only) ----
|
||||
// Wall/ceiling items are created on surface enter to avoid floating items.
|
||||
|
||||
if (!selectedItem.attachTo) {
|
||||
draftNode.create(gridPosition.current, selectedItem)
|
||||
}
|
||||
revalidate()
|
||||
|
||||
// ---- Floor Handlers ----
|
||||
@@ -113,10 +139,40 @@ export const ItemTool: React.FC = () => {
|
||||
|
||||
event.stopPropagation()
|
||||
applyTransition(result)
|
||||
|
||||
// Try to create draft immediately if placement is valid
|
||||
if (!draftNode.current) {
|
||||
ensureDraft(result)
|
||||
}
|
||||
}
|
||||
|
||||
const onWallMove = (event: WallEvent) => {
|
||||
const result = wallStrategy.move(getContext(), event)
|
||||
const ctx = getContext()
|
||||
|
||||
// If not yet on wall surface (e.g. entered via invalid top face),
|
||||
// promote this move to an enter when hitting a valid side face.
|
||||
if (ctx.state.surface !== 'wall') {
|
||||
const nodes = useScene.getState().nodes
|
||||
const enterResult = wallStrategy.enter(ctx, event, resolveLevelId, nodes)
|
||||
if (!enterResult) return
|
||||
|
||||
event.stopPropagation()
|
||||
applyTransition(enterResult)
|
||||
return
|
||||
}
|
||||
|
||||
// No draft yet (first move after enter) — create at current position if valid
|
||||
if (!draftNode.current) {
|
||||
const nodes = useScene.getState().nodes
|
||||
const setup = wallStrategy.enter(getContext(), event, resolveLevelId, nodes)
|
||||
if (!setup) return
|
||||
|
||||
event.stopPropagation()
|
||||
ensureDraft(setup)
|
||||
return
|
||||
}
|
||||
|
||||
const result = wallStrategy.move(ctx, event)
|
||||
if (!result) return
|
||||
|
||||
event.stopPropagation()
|
||||
@@ -161,17 +217,31 @@ export const ItemTool: React.FC = () => {
|
||||
if (result.dirtyNodeId) {
|
||||
useScene.getState().dirtyNodes.add(result.dirtyNodeId)
|
||||
}
|
||||
draftNode.create(gridPosition.current, selectedItem)
|
||||
|
||||
// Re-enter the wall — applyTransition creates the next draft at the correct position
|
||||
const nodes = useScene.getState().nodes
|
||||
const enterResult = wallStrategy.enter(getContext(), event, resolveLevelId, nodes)
|
||||
if (enterResult) {
|
||||
applyTransition(enterResult)
|
||||
} else {
|
||||
revalidate()
|
||||
}
|
||||
}
|
||||
|
||||
const onWallLeave = (event: WallEvent) => {
|
||||
const result = wallStrategy.leave(getContext())
|
||||
if (!result) return
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
// Wall/ceiling items: destroy draft so it doesn't float on the floor
|
||||
if (selectedItem.attachTo) {
|
||||
draftNode.destroy()
|
||||
Object.assign(placementState.current, result.stateUpdate)
|
||||
} else {
|
||||
applyTransition(result)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Ceiling Handlers ----
|
||||
|
||||
@@ -182,9 +252,25 @@ export const ItemTool: React.FC = () => {
|
||||
|
||||
event.stopPropagation()
|
||||
applyTransition(result)
|
||||
|
||||
// Try to create draft immediately if placement is valid
|
||||
if (!draftNode.current) {
|
||||
ensureDraft(result)
|
||||
}
|
||||
}
|
||||
|
||||
const onCeilingMove = (event: CeilingEvent) => {
|
||||
// No draft yet (first move after enter) — create at current position if valid
|
||||
if (!draftNode.current && placementState.current.surface === 'ceiling') {
|
||||
const nodes = useScene.getState().nodes
|
||||
const setup = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes)
|
||||
if (!setup) return
|
||||
|
||||
event.stopPropagation()
|
||||
ensureDraft(setup)
|
||||
return
|
||||
}
|
||||
|
||||
const result = ceilingStrategy.move(getContext(), event)
|
||||
if (!result) return
|
||||
|
||||
@@ -208,17 +294,31 @@ export const ItemTool: React.FC = () => {
|
||||
|
||||
event.stopPropagation()
|
||||
draftNode.commit(result.nodeUpdate)
|
||||
draftNode.create(gridPosition.current, selectedItem)
|
||||
|
||||
// Re-enter the ceiling — applyTransition creates the next draft at the correct position
|
||||
const nodes = useScene.getState().nodes
|
||||
const enterResult = ceilingStrategy.enter(getContext(), event, resolveLevelId, nodes)
|
||||
if (enterResult) {
|
||||
applyTransition(enterResult)
|
||||
} else {
|
||||
revalidate()
|
||||
}
|
||||
}
|
||||
|
||||
const onCeilingLeave = (event: CeilingEvent) => {
|
||||
const result = ceilingStrategy.leave(getContext())
|
||||
if (!result) return
|
||||
|
||||
event.stopPropagation()
|
||||
|
||||
// Wall/ceiling items: destroy draft so it doesn't float on the floor
|
||||
if (selectedItem.attachTo) {
|
||||
draftNode.destroy()
|
||||
Object.assign(placementState.current, result.stateUpdate)
|
||||
} else {
|
||||
applyTransition(result)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Keyboard rotation ----
|
||||
|
||||
@@ -287,6 +387,13 @@ export const ItemTool: React.FC = () => {
|
||||
if (draftNode.current && placementState.current.surface === 'floor') {
|
||||
const mesh = sceneRegistry.nodes.get(draftNode.current.id)
|
||||
if (mesh) {
|
||||
// If distance is large, snap immediately
|
||||
const distance = mesh.position.distanceToSquared(gridPosition.current)
|
||||
if (distance > 1) {
|
||||
mesh.position.copy(gridPosition.current)
|
||||
return
|
||||
}
|
||||
// Otherwise, lerp smoothly
|
||||
mesh.position.lerp(gridPosition.current, delta * 20)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export const wallStrategy = {
|
||||
resolveLevelId: LevelResolver,
|
||||
nodes: Record<string, AnyNode>,
|
||||
): TransitionResult | null {
|
||||
const attachTo = ctx.draftItem?.asset.attachTo
|
||||
const attachTo = ctx.asset.attachTo
|
||||
if (attachTo !== 'wall' && attachTo !== 'wall-side') return null
|
||||
if (!isValidWallSideFace(event.normal)) return null
|
||||
|
||||
@@ -121,7 +121,11 @@ export const wallStrategy = {
|
||||
},
|
||||
cursorRotationY: cursorRotation,
|
||||
gridPosition: [x, y, z],
|
||||
cursorPosition: [x, y, z],
|
||||
cursorPosition: [
|
||||
snapToHalf(event.position[0]),
|
||||
snapToHalf(event.position[1]),
|
||||
snapToHalf(event.position[2]),
|
||||
],
|
||||
stopPropagation: true,
|
||||
}
|
||||
},
|
||||
@@ -230,7 +234,7 @@ export const ceilingStrategy = {
|
||||
resolveLevelId: LevelResolver,
|
||||
nodes: Record<string, AnyNode>,
|
||||
): TransitionResult | null {
|
||||
if (ctx.draftItem?.asset.attachTo !== 'ceiling') return null
|
||||
if (ctx.asset.attachTo !== 'ceiling') return null
|
||||
|
||||
// Level guard
|
||||
const ceilingLevelId = resolveLevelId(event.node, nodes)
|
||||
|
||||
@@ -363,9 +363,9 @@ export const CATALOG_ITEMS: AssetInput[] = [
|
||||
0
|
||||
],
|
||||
"dimensions": [
|
||||
1.9999999999999991,
|
||||
2.0000000000000004,
|
||||
0.39999999999999936
|
||||
2,
|
||||
2,
|
||||
0.4
|
||||
],
|
||||
"attachTo": "wall"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user