Fix relative move drag offsets

This commit is contained in:
Aymeric Rabot
2026-06-07 18:18:46 -04:00
parent b2e7a090d6
commit be0f491bbd
21 changed files with 712 additions and 623 deletions
@@ -0,0 +1,116 @@
import {
type AnyNodeId,
type RoofEvent,
type RoofNode,
type RoofSegmentNode,
sceneRegistry,
} from '@pascal-app/core'
import { useViewer } from '@pascal-app/viewer'
import * as THREE from 'three'
import { type RoofSegmentHit, resolveRoofSegmentHit } from './roof-segment-hit'
import { getSurfaceY } from './roof-surface'
export type RelativeRoofDragTarget = {
segment: RoofSegmentNode
localX: number
localY: number
localZ: number
hit: RoofSegmentHit
}
type RelativeRoofDragState = {
segmentId: string
anchor: [number, number]
start: [number, number, number]
current: [number, number, number]
surfaceOffsetY: number
}
export function roofSegmentLocalToBuildingLocal(
segmentId: string,
position: [number, number, number],
): [number, number, number] {
const segmentObj = sceneRegistry.nodes.get(segmentId as AnyNodeId)
if (!segmentObj) return position
const point = segmentObj.localToWorld(new THREE.Vector3(...position))
const buildingId = useViewer.getState().selection.buildingId
const buildingObj = buildingId ? sceneRegistry.nodes.get(buildingId as AnyNodeId) : null
if (buildingObj) buildingObj.worldToLocal(point)
return [point.x, point.y, point.z]
}
export function createRelativeRoofDrag(original: {
position: [number, number, number]
roofSegmentId?: string
}): {
resolve: (event: RoofEvent) => RelativeRoofDragTarget | null
} {
let state: RelativeRoofDragState | null = null
const getPositionInSegment = (
position: [number, number, number],
fromSegmentId: string | undefined,
segment: RoofSegmentNode,
): [number, number, number] => {
if (fromSegmentId === segment.id) return position
const fromSegmentObj = fromSegmentId
? sceneRegistry.nodes.get(fromSegmentId as AnyNodeId)
: null
const targetSegmentObj = sceneRegistry.nodes.get(segment.id as AnyNodeId)
if (!(fromSegmentObj && targetSegmentObj)) return position
const point = fromSegmentObj.localToWorld(new THREE.Vector3(...position))
targetSegmentObj.worldToLocal(point)
return [point.x, point.y, point.z]
}
const getStartPositionForSegment = (
segment: RoofSegmentNode,
previousState: RelativeRoofDragState | null,
): [number, number, number] => {
if (previousState) {
return getPositionInSegment(previousState.current, previousState.segmentId, segment)
}
if (original.roofSegmentId === segment.id) return original.position
return getPositionInSegment(original.position, original.roofSegmentId, segment)
}
return {
resolve(event) {
const hit = resolveRoofSegmentHit(
event.node as RoofNode,
event.position[0],
event.position[1],
event.position[2],
)
if (!hit) return null
if (!state || state.segmentId !== hit.segment.id) {
const start = getStartPositionForSegment(hit.segment, state)
state = {
segmentId: hit.segment.id,
anchor: [hit.localX, hit.localZ],
start,
current: start,
surfaceOffsetY: start[1] - getSurfaceY(start[0], start[2], hit.segment),
}
}
const localX = state.start[0] + (hit.localX - state.anchor[0])
const localZ = state.start[2] + (hit.localZ - state.anchor[1])
const localY = getSurfaceY(localX, localZ, hit.segment) + state.surfaceOffsetY
state.current = [localX, localY, localZ]
return {
segment: hit.segment,
localX,
localY,
localZ,
hit,
}
},
}
}