b3d-hud
A drop-in aircraft HUD: mounts the hud SVG as an additive, translucent
overlay centred over the scene and exposes setMeter / setHorizon / setTraces to
drive it each frame. The heavy lifting is the pure hud-math (radar
projection + horizon) and the hud driver; this component just handles mounting and
lifecycle. Wire it to a b3d-aircraft's state + the scene's targets.
In-scene / cockpit mode. attachInScene(parent, opts) rasterizes the SAME live
HUD SVG onto a plane in the 3D scene (via svg-texture) — so the HUD
shows in a 3D cockpit and in VR, where a DOM overlay is invisible. b3d-aircraft
mounts it on the canopy automatically (banks with the airframe, not head-locked) and
shows it in the cockpit view; setInSceneVisible(bool) toggles it.
Demo
Scrub the meters and attitude in the ⚙ panel; the radar traces orbit a fixed viewer, tracking inside the ring when in the field of view and pinning to the periphery when they swing out or behind.
import { b3d, b3dHud, b3dSkybox, label3d, slider3d, select3d } from 'tosijs-3d'
import { tosi } from 'tosijs'
const { s } = tosi({ s: { speed: 0.62, altitude: 0.4, health: 0.9, energy: 0.7, pitch: 0, roll: 0, warn: 'none' } })
const hud = b3dHud({})
// A warning maps to a text line + the arc side that flashes red.
const warnMap = {
'none': [],
'PULL UP': [{ text: 'PULL UP', side: 'bottom' }],
'MISSILE right': [{ text: 'MISSILE', side: 'right' }],
'MISSILE left': [{ text: 'MISSILE', side: 'left' }],
'STALL': [{ text: 'STALL', side: 'top' }],
}
const scene = b3d(
{
scenePanelOpen: true,
scenePanel: () => [
label3d({ text: 'HUD', bold: true }),
slider3d({ label: 'speed (red)', value: s.speed, min: 0, max: 1, step: 0.01 }),
slider3d({ label: 'altitude (blue)', value: s.altitude, min: 0, max: 1, step: 0.01 }),
slider3d({ label: 'health (green)', value: s.health, min: 0, max: 1, step: 0.01 }),
slider3d({ label: 'energy (yellow)', value: s.energy, min: 0, max: 1, step: 0.01 }),
slider3d({ label: 'pitch', value: s.pitch, min: -90, max: 90, step: 1 }),
slider3d({ label: 'roll', value: s.roll, min: -90, max: 90, step: 1 }),
select3d({ label: 'warning', value: s.warn, options: Object.keys(warnMap), onChange: (v) => hud.setWarnings(warnMap[v] || []) }),
],
},
b3dSkybox({ timeOfDay: 9 }),
hud,
)
preview.append(scene)
const apply = () => {
hud.setMeter('speed', s.speed.value)
hud.setMeter('altitude', s.altitude.value)
hud.setMeter('health', s.health.value)
hud.setMeter('energy', s.energy.value)
hud.setHorizon(s.pitch.value, s.roll.value, s.pitch.value)
hud.setWarnings(warnMap[s.warn.value] || [])
}
for (const k of ['speed', 'altitude', 'health', 'energy', 'pitch', 'roll', 'warn']) s[k].observe(apply)
apply()
// Radar traces orbiting a fixed viewer at the origin (facing +Z).
const viewer = { position: { x: 0, y: 0, z: 0 }, rotation: { x: 0, y: 0, z: 0, w: 1 } }
const kinds = ['hostile', 'friendly', 'neutral', 'waypoint']
let t = 0
setInterval(() => {
t += 0.03
const traces = kinds.map((kind, i) => ({
kind,
pos: { x: Math.sin(t + i * 1.6) * 34, y: Math.sin(t * 0.7 + i) * 12, z: Math.cos(t + i * 1.6) * 34 },
}))
// track inside the ring (radius 84), pin OUTSIDE the gauges (pinRadius 116)
hud.setTraces(traces, viewer, { fovH: Math.PI / 2, fovV: Math.PI / 2, radius: 84, pinRadius: 116 })
}, 32)
tosi-b3d { width: 100%; height: 100%; }
Attributes
| Attribute | Default | Description |
|---|---|---|
url |
'' |
Empty = the built-in code HUD (fully wired); set to a designer SVG to load one |
size |
70 |
HUD height as a % of the canvas's smaller dimension |
pxPerDeg |
8 |
Pitch-ladder pixels per degree |