/** * Render-only projection of already-resolved opening tunnel geometry. * * Geometry association, cache invalidation and room-fill semantics stay with * their existing sources of truth. This module only maps immutable inputs to * the established SVG contract, which makes it the first HP-ARCH-01 render * seam without introducing a second model or a second state owner. */ import { nothing, svg, type TemplateResult } from 'lit'; import type { ResolvedRoomFill } from '../logic'; import type { OpeningCfg } from '../types'; import type { OpeningTunnelGeometry } from '../wall-thickness'; export type RenderOpening = OpeningCfg & { rx: number; ry: number; rlen: number }; export interface OpeningTunnelRenderInput { openings: readonly RenderOpening[]; geometries: readonly (OpeningTunnelGeometry | null)[]; fillsByRoomId: ReadonlyMap; /** Makes gradients unique when data-fill and Glow-base layers coexist. */ idPrefix?: string; groupClass?: string; dataLayer?: string; } /** Preserve the existing `data-hp`, gradient-id and hard centreline contract. */ export function renderOpeningTunnelFills({ openings, geometries, fillsByRoomId, idPrefix = 'data', groupClass = 'opening-tunnels', dataLayer = 'data', }: OpeningTunnelRenderInput): TemplateResult { const parts = openings.map((opening, index) => { const geometry = geometries[index]; if (!geometry) return nothing; const negative = geometry.faces.find((face) => face.side === -1); const positive = geometry.faces.find((face) => face.side === 1); if (!negative || !positive) return nothing; const negFill = fillsByRoomId.get(negative.roomId) || null; const posFill = fillsByRoomId.get(positive.roomId) || null; if (!negFill && !posFill) return nothing; const d = `${negative.d} ${positive.d}`; const same = !!negFill && !!posFill && negFill.color === posFill.color && negFill.opacity === posFill.opacity; const transform = `translate(${opening.rx} ${opening.ry}) rotate(${opening.angle})`; if (same) { return svg``; } const span = geometry.maxY - geometry.minY; if (!(span > 0)) return nothing; const axis = Math.max(0, Math.min(1, -geometry.minY / span)); const axisOffset = `${(axis * 100).toFixed(6)}%`; const nf = negFill || { color: '#000000', opacity: 0, mode: 'none' as const }; const pf = posFill || { color: '#000000', opacity: 0, mode: 'none' as const }; const safePrefix = idPrefix.replace(/[^a-zA-Z0-9_-]/g, '-'); const gradientId = `hp-opening-tunnel-${safePrefix}-${index}`; return svg``; }); return svg``; }