Files
grok-icon-study/src/ui/pose/space.ts
T

100 lines
2.7 KiB
TypeScript

export type LocalPoint = readonly [number, number]
export type Frame = {
x: number
y: number
w: number
h: number
}
export const VIEW = 300
export const HALF = VIEW / 2
export const HEAD_RE = 114.2705
export const localHeadRadius = (svg: SVGSVGElement | null, radius = HEAD_RE) => {
if (!svg) return 95
const box = svg.viewBox.baseVal
if (!box.width) return 95
return (radius / box.width) * VIEW
}
export const toLocal = (
svg: SVGSVGElement | null,
clientX: number,
clientY: number
): LocalPoint | null => {
if (!svg) return null
const rectangle = svg.getBoundingClientRect()
if (rectangle.width < 1 || rectangle.height < 1) return null
return [
((clientX - rectangle.left) / rectangle.width) * VIEW - HALF,
((clientY - rectangle.top) / rectangle.height) * VIEW - HALF
]
}
export const overlayToHost = (
host: SVGSVGElement | null,
x: number,
y: number
): LocalPoint | null => {
if (!host) return null
const box = host.viewBox.baseVal
const w = box.width || VIEW
const h = box.height || VIEW
return [box.x + ((x + HALF) / VIEW) * w, box.y + ((y + HALF) / VIEW) * h]
}
export const hostToLocal = (
host: SVGSVGElement | null,
x: number,
y: number
): LocalPoint | null => {
if (!host) return null
const matrix = host.getScreenCTM()
if (!matrix) return toLocal(host, x, y)
const point = host.createSVGPoint()
point.x = x
point.y = y
const screen = point.matrixTransform(matrix)
return toLocal(host, screen.x, screen.y)
}
export const mapElementPoint = (
el: SVGGraphicsElement,
x: number,
y: number,
host: SVGSVGElement | null
): LocalPoint | null => {
const svg = el.ownerSVGElement
const screenMatrix = el.getScreenCTM()
const localMatrix = el.getCTM()
const hostMatrix = host?.getScreenCTM()
if (!svg || (!screenMatrix && (!localMatrix || !hostMatrix))) return null
const point = svg.createSVGPoint()
point.x = x
point.y = y
const screen = screenMatrix
? point.matrixTransform(screenMatrix)
: point.matrixTransform(localMatrix!).matrixTransform(hostMatrix!)
return toLocal(host ?? svg, screen.x, screen.y)
}
export const frameOf = (
el: SVGGraphicsElement | null | undefined,
overlay: SVGSVGElement | null
): Frame | null => {
if (!el || !overlay) return null
if (el.style.display === 'none') return null
const box = el.getBoundingClientRect()
const host = overlay.getBoundingClientRect()
if (box.width < 0.5 || box.height < 0.5 || host.width < 1) return null
const sx = VIEW / host.width
const sy = VIEW / host.height
return {
x: (box.left + box.width / 2 - host.left) * sx - HALF,
y: (box.top + box.height / 2 - host.top) * sy - HALF,
w: box.width * sx,
h: box.height * sy
}
}