117 lines
3.4 KiB
TypeScript
117 lines
3.4 KiB
TypeScript
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,
|
|
}
|
|
},
|
|
}
|
|
}
|