import { AbstractMesh, Matrix, Vector3, Scene, Engine } from '@babylonjs/core' import * as statusColors from '../../lib/statusColors' import { getSensorIdFromMesh } from './sensorHighlight' export interface SensorOverlayCircle { sensorId: string left: number top: number colorHex: string } const parseHexColor = (hex: string) => { let clean = hex.trim().replace('#', '') if (clean.length === 3) { clean = clean .split('') .map(c => c + c) .join('') } const num = parseInt(clean, 16) const r = (num >> 16) & 255 const g = (num >> 8) & 255 const b = num & 255 return { r, g, b } } export const hexWithAlpha = (hex: string, alpha: number) => { const { r, g, b } = parseHexColor(hex) return `rgba(${r}, ${g}, ${b}, ${alpha})` } export const computeSensorOverlayCircles = (params: { scene: Scene engine: Engine meshes: AbstractMesh[] sensorStatusMap?: Record }): SensorOverlayCircle[] => { const { scene, engine, meshes, sensorStatusMap } = params const camera = scene.activeCamera if (!camera) return [] const viewport = camera.viewport.toGlobal(engine.getRenderWidth(), engine.getRenderHeight()) const result: SensorOverlayCircle[] = [] for (const mesh of meshes) { const sensorId = getSensorIdFromMesh(mesh) if (!sensorId) continue const bbox = typeof mesh.getHierarchyBoundingVectors === 'function' ? mesh.getHierarchyBoundingVectors() : { min: mesh.getBoundingInfo().boundingBox.minimumWorld, max: mesh.getBoundingInfo().boundingBox.maximumWorld, } const center = bbox.min.add(bbox.max).scale(0.5) const projected = Vector3.Project(center, Matrix.Identity(), scene.getTransformMatrix(), viewport) if (!projected) continue const statusColor = sensorStatusMap?.[sensorId] || statusColors.STATUS_COLOR_NORMAL result.push({ sensorId, left: projected.x, top: projected.y, colorHex: statusColor, }) } return result }