Add swing angle support for door rotation

This commit is contained in:
sudhir
2026-05-01 10:18:36 +05:30
parent 3df3d12f46
commit 915393d649
4 changed files with 95 additions and 31 deletions
@@ -2841,6 +2841,21 @@ function getOpeningCenterLine(polygon: Point2D[]) {
}
}
function isOpeningPlanFlipped(rotation: [number, number, number]) {
const normalized =
((((rotation[1] % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2)) + 1e-6) % (Math.PI * 2)
return normalized > Math.PI / 2 && normalized < (Math.PI * 3) / 2
}
function getFlippedHingesSide(hingesSide: DoorNode['hingesSide']) {
return hingesSide === 'left' ? 'right' : 'left'
}
function getFlippedSwingDirection(swingDirection: DoorNode['swingDirection']) {
return swingDirection === 'inward' ? 'outward' : 'inward'
}
function normalizeGridCoordinate(value: number): number {
return Number(value.toFixed(GRID_COORDINATE_PRECISION))
}
@@ -4301,8 +4316,14 @@ const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({
const px = -ny
const py = nx
const hingesSide = opening.hingesSide ?? 'left'
const swingDirection = opening.swingDirection ?? 'inward'
const isPlanFlipped = isOpeningPlanFlipped(opening.rotation)
const baseHingesSide = opening.hingesSide ?? 'left'
const baseSwingDirection = opening.swingDirection ?? 'inward'
const hingesSide = isPlanFlipped ? getFlippedHingesSide(baseHingesSide) : baseHingesSide
const swingDirection = isPlanFlipped
? getFlippedSwingDirection(baseSwingDirection)
: baseSwingDirection
const swingAngle = Math.max(0, Math.min(Math.PI / 2, opening.swingAngle ?? 0))
const width = opening.width
const sweepFlag =
hingesSide === 'left'
@@ -4345,9 +4366,16 @@ const FloorplanGeometryLayer = memo(function FloorplanGeometryLayer({
ny * hingeTangentSign * (doorCubeSize / 2),
}
const swingRadius = Math.hypot(arcEnd.x - leafStart.x, arcEnd.y - leafStart.y)
const closedLeafVector = {
x: arcEnd.x - leafStart.x,
y: arcEnd.y - leafStart.y,
}
const openAngle = swingAngle * swingSign * hingeTangentSign
const openCos = Math.cos(openAngle)
const openSin = Math.sin(openAngle)
const leafEnd = {
x: leafStart.x + px * swingSign * swingRadius,
y: leafStart.y + py * swingSign * swingRadius,
x: leafStart.x + closedLeafVector.x * openCos - closedLeafVector.y * openSin,
y: leafStart.y + closedLeafVector.x * openSin + closedLeafVector.y * openCos,
}
const doorBackgroundPoints = [
{
+17 -2
View File
@@ -5,6 +5,8 @@ import { runRedo, runUndo } from '../lib/history'
import { sfxEmitter } from '../lib/sfx-bus'
import useEditor from '../store/use-editor'
const DOOR_SWING_OPEN_ANGLE = Math.PI / 2
// Tools call this in their onCancel handler when they have an active mid-action to cancel,
// so that the global Escape handler knows not to also switch to select mode.
let _toolCancelConsumed = false
@@ -145,10 +147,19 @@ export const useKeyboard = ({
}
} else if ((e.key === 'r' || e.key === 'R') && !isVersionPreviewMode) {
// Rotate selected node clockwise if it supports rotation (items, roofs, etc.)
// Doors use R to toggle their leaf open/closed around the hinge.
const selectedNodeIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
if (selectedNodeIds.length === 1) {
const node = useScene.getState().nodes[selectedNodeIds[0]!]
if (node && 'rotation' in node) {
if (node?.type === 'door') {
e.preventDefault()
const currentSwingAngle = node.swingAngle ?? 0
useScene.getState().updateNode(node.id, {
swingAngle:
currentSwingAngle >= DOOR_SWING_OPEN_ANGLE / 2 ? 0 : DOOR_SWING_OPEN_ANGLE,
})
sfxEmitter.emit('sfx:item-rotate')
} else if (node && 'rotation' in node) {
e.preventDefault()
const ROTATION_STEP = Math.PI / 4
@@ -168,7 +179,11 @@ export const useKeyboard = ({
const selectedNodeIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
if (selectedNodeIds.length === 1) {
const node = useScene.getState().nodes[selectedNodeIds[0]!]
if (node && 'rotation' in node) {
if (node?.type === 'door') {
e.preventDefault()
useScene.getState().updateNode(node.id, { swingAngle: 0 })
sfxEmitter.emit('sfx:item-rotate')
} else if (node && 'rotation' in node) {
e.preventDefault()
const ROTATION_STEP = Math.PI / 4