140 lines
3.9 KiB
TypeScript
140 lines
3.9 KiB
TypeScript
import { getScaledDimensions } from '../schema'
|
|
import type { AnyNode, AnyNodeId, ItemNode, LevelNode } from '../schema'
|
|
import useLiveTransforms from '../store/use-live-transforms'
|
|
import { getRotatedRectanglePolygon, rotatePlanVector } from './geometry'
|
|
import type { FloorplanItemEntry, FloorplanNodeTransform, LevelDescendantMap } from './types'
|
|
|
|
export function collectLevelDescendants(
|
|
levelNode: LevelNode,
|
|
nodes: Record<string, AnyNode>,
|
|
): AnyNode[] {
|
|
const descendants: AnyNode[] = []
|
|
const stack = [...levelNode.children].reverse() as AnyNodeId[]
|
|
|
|
while (stack.length > 0) {
|
|
const nodeId = stack.pop()
|
|
if (!nodeId) {
|
|
continue
|
|
}
|
|
|
|
const node = nodes[nodeId]
|
|
if (!node) {
|
|
continue
|
|
}
|
|
|
|
descendants.push(node)
|
|
|
|
if ('children' in node && Array.isArray(node.children) && node.children.length > 0) {
|
|
for (let index = node.children.length - 1; index >= 0; index -= 1) {
|
|
stack.push(node.children[index] as AnyNodeId)
|
|
}
|
|
}
|
|
}
|
|
|
|
return descendants
|
|
}
|
|
|
|
export function getItemFloorplanTransform(
|
|
item: ItemNode,
|
|
nodeById: LevelDescendantMap,
|
|
cache: Map<string, FloorplanNodeTransform | null>,
|
|
): FloorplanNodeTransform | null {
|
|
const cached = cache.get(item.id)
|
|
if (cached !== undefined) {
|
|
return cached
|
|
}
|
|
|
|
const localRotation = item.rotation[1] ?? 0
|
|
let result: FloorplanNodeTransform | null = null
|
|
const itemMetadata =
|
|
typeof item.metadata === 'object' && item.metadata !== null && !Array.isArray(item.metadata)
|
|
? (item.metadata as Record<string, unknown>)
|
|
: null
|
|
|
|
if (itemMetadata?.isTransient === true) {
|
|
const live = useLiveTransforms.getState().get(item.id)
|
|
if (live) {
|
|
result = {
|
|
position: {
|
|
x: live.position[0],
|
|
y: live.position[2],
|
|
},
|
|
rotation: live.rotation,
|
|
}
|
|
|
|
cache.set(item.id, result)
|
|
return result
|
|
}
|
|
}
|
|
|
|
if (item.parentId) {
|
|
const parentNode = nodeById.get(item.parentId as AnyNodeId)
|
|
|
|
if (parentNode?.type === 'wall') {
|
|
const wallRotation = -Math.atan2(
|
|
parentNode.end[1] - parentNode.start[1],
|
|
parentNode.end[0] - parentNode.start[0],
|
|
)
|
|
const wallLocalZ =
|
|
item.asset.attachTo === 'wall-side'
|
|
? ((parentNode.thickness ?? 0.1) / 2) * (item.side === 'back' ? -1 : 1)
|
|
: item.position[2]
|
|
const [offsetX, offsetY] = rotatePlanVector(item.position[0], wallLocalZ, wallRotation)
|
|
|
|
result = {
|
|
position: {
|
|
x: parentNode.start[0] + offsetX,
|
|
y: parentNode.start[1] + offsetY,
|
|
},
|
|
rotation: wallRotation + localRotation,
|
|
}
|
|
} else if (parentNode?.type === 'item') {
|
|
const parentTransform = getItemFloorplanTransform(parentNode, nodeById, cache)
|
|
if (parentTransform) {
|
|
const [offsetX, offsetY] = rotatePlanVector(
|
|
item.position[0],
|
|
item.position[2],
|
|
parentTransform.rotation,
|
|
)
|
|
result = {
|
|
position: {
|
|
x: parentTransform.position.x + offsetX,
|
|
y: parentTransform.position.y + offsetY,
|
|
},
|
|
rotation: parentTransform.rotation + localRotation,
|
|
}
|
|
}
|
|
} else {
|
|
result = {
|
|
position: { x: item.position[0], y: item.position[2] },
|
|
rotation: localRotation,
|
|
}
|
|
}
|
|
} else {
|
|
result = {
|
|
position: { x: item.position[0], y: item.position[2] },
|
|
rotation: localRotation,
|
|
}
|
|
}
|
|
|
|
cache.set(item.id, result)
|
|
return result
|
|
}
|
|
|
|
export function buildFloorplanItemEntry(
|
|
item: ItemNode,
|
|
nodeById: LevelDescendantMap,
|
|
cache: Map<string, FloorplanNodeTransform | null>,
|
|
): FloorplanItemEntry | null {
|
|
const transform = getItemFloorplanTransform(item, nodeById, cache)
|
|
if (!transform) {
|
|
return null
|
|
}
|
|
|
|
const [width, , depth] = getScaledDimensions(item)
|
|
return {
|
|
item,
|
|
polygon: getRotatedRectanglePolygon(transform.position, width, depth, transform.rotation),
|
|
}
|
|
}
|