1use core::f32::consts::PI;
13use crate::holo;
14
15pub type Rgba = u32;
17
18#[derive(Default, Clone)]
21pub struct Draw {
22 pub fills: Vec<(Rgba, Vec<[f32; 2]>)>,
23 pub strokes: Vec<(Rgba, Vec<[f32; 2]>)>,
24}
25
26impl Draw {
27 fn new() -> Self { Self::default() }
28 fn fill(&mut self, c: Rgba, poly: Vec<[f32; 2]>) { self.fills.push((c, poly)); }
29 fn stroke(&mut self, c: Rgba, pl: Vec<[f32; 2]>) { self.strokes.push((c, pl)); }
30 fn segs(&mut self, c: Rgba, segs: &[[f32; 4]]) {
32 for s in segs { self.strokes.push((c, vec![[s[0], s[1]], [s[2], s[3]]])); }
33 }
34 fn rect_outline(&mut self, c: Rgba, x: f32, y: f32, w: f32, h: f32) {
35 self.stroke(c, vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]]);
36 }
37 fn rect_fill(&mut self, c: Rgba, x: f32, y: f32, w: f32, h: f32) {
38 self.fill(c, vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]]);
39 }
40}
41
42#[inline] fn clamp01(v: f32) -> f32 { v.max(0.0).min(1.0) }
45
46pub fn mix(a: Rgba, b: Rgba, t: f32) -> Rgba {
48 let t = clamp01(t);
49 let lerp = |x: u32, y: u32| (x as f32 + (y as f32 - x as f32) * t) as u32 & 0xFF;
50 let (ar, ag, ab) = (a >> 16 & 0xFF, a >> 8 & 0xFF, a & 0xFF);
51 let (br, bg, bb) = (b >> 16 & 0xFF, b >> 8 & 0xFF, b & 0xFF);
52 (lerp(ar, br) << 16) | (lerp(ag, bg) << 8) | lerp(ab, bb)
53}
54
55pub fn shade(c: Rgba, k: f32) -> Rgba {
57 let f = |x: u32| ((x as f32 * k).max(0.0).min(255.0)) as u32;
58 (f(c >> 16 & 0xFF) << 16) | (f(c >> 8 & 0xFF) << 8) | f(c & 0xFF)
59}
60
61fn arc(cx: f32, cy: f32, r: f32, a0: f32, a1: f32, n: usize) -> Vec<[f32; 2]> {
63 let n = n.max(1);
64 (0..=n).map(|i| {
65 let a = a0 + (a1 - a0) * i as f32 / n as f32;
66 [cx + a.cos() * r, cy + a.sin() * r]
67 }).collect()
68}
69
70fn bevel_poly(x: f32, y: f32, w: f32, h: f32, b: f32) -> Vec<[f32; 2]> {
72 let b = b.min(w * 0.5).min(h * 0.5);
73 let (x1, y1) = (x + w, y + h);
74 vec![[x + b, y], [x1 - b, y], [x1, y + b], [x1, y1 - b],
75 [x1 - b, y1], [x + b, y1], [x, y1 - b], [x, y + b], [x + b, y]]
76}
77
78pub fn radar(cx: f32, cy: f32, r: f32, sweep: f32, primary: Rgba, accent: Rgba, track: Rgba) -> Draw {
85 let mut d = Draw::new();
86 for k in 1..=3 { d.stroke(track, arc(cx, cy, r * k as f32 / 3.0, 0.0, PI * 2.0, 48)); }
87 d.stroke(track, vec![[cx - r, cy], [cx + r, cy]]);
88 d.stroke(track, vec![[cx, cy - r], [cx, cy + r]]);
89 let sw = 0.32;
91 let mut wedge = vec![[cx, cy]];
92 wedge.extend(arc(cx, cy, r, sweep - sw, sweep, 10));
93 wedge.push([cx, cy]);
94 d.fill(shade(accent, 0.5), wedge);
95 d.stroke(accent, vec![[cx, cy], [cx + sweep.cos() * r, cy + sweep.sin() * r]]);
96 let pr = r * 0.62;
98 let pa = sweep - 0.15;
99 let (bx, by) = (cx + pa.cos() * pr, cy + pa.sin() * pr);
100 d.fill(primary, arc(bx, by, 3.5, 0.0, PI * 2.0, 10));
101 d
102}
103
104pub fn compass(x: f32, y: f32, w: f32, h: f32, heading: f32, primary: Rgba, track: Rgba) -> Draw {
107 let mut d = Draw::new();
108 d.rect_outline(track, x, y, w, h);
109 let cx = x + w * 0.5;
110 let deg_per_px = 90.0 / w; let start = (heading - w * 0.5 * deg_per_px).floor() as i32;
112 let end = (heading + w * 0.5 * deg_per_px).ceil() as i32;
113 let mut deg = start - (start.rem_euclid(15));
114 while deg <= end {
115 let px = cx + (deg as f32 - heading) / deg_per_px;
116 if px >= x && px <= x + w {
117 let card = deg.rem_euclid(90) == 0;
118 let th = if card { h * 0.6 } else { h * 0.3 };
119 d.stroke(if card { primary } else { track }, vec![[px, y + h], [px, y + h - th]]);
120 }
121 deg += 15;
122 }
123 d.stroke(primary, vec![[cx, y - 4.0], [cx - 5.0, y - 12.0], [cx + 5.0, y - 12.0], [cx, y - 4.0]]);
125 d
126}
127
128pub fn reticle(cx: f32, cy: f32, r: f32, spread: f32, primary: Rgba) -> Draw {
130 let mut d = Draw::new();
131 let g = 0.25 + spread * 0.5;
132 for q in 0..4 {
133 let base = q as f32 * PI * 0.5 + PI * 0.25;
134 d.stroke(primary, arc(cx, cy, r, base + g * 0.5, base + PI * 0.5 - g * 0.5, 10));
135 }
136 let inner = r * 0.45 + spread * r * 0.4;
137 for k in 0..4 {
138 let a = k as f32 * PI * 0.5;
139 d.stroke(primary, vec![[cx + a.cos() * inner, cy + a.sin() * inner],
140 [cx + a.cos() * (inner + 8.0), cy + a.sin() * (inner + 8.0)]]);
141 }
142 d.fill(primary, arc(cx, cy, 1.8, 0.0, PI * 2.0, 8));
143 d
144}
145
146pub fn target(x: f32, y: f32, w: f32, h: f32, lock: f32, primary: Rgba, accent: Rgba) -> Draw {
148 let mut d = Draw::new();
149 let off = (1.0 - clamp01(lock)) * 14.0;
150 let col = mix(primary, accent, clamp01(lock));
151 let l = (w.min(h)) * 0.28;
152 let (xx, yy, ww, hh) = (x - off, y - off, w + off * 2.0, h + off * 2.0);
153 for seg in holo::corner_brackets(xx, yy, ww, hh, l) {
154 d.stroke(col, vec![[seg[0], seg[1]], [seg[2], seg[3]]]);
155 }
156 if lock > 0.5 { d.stroke(col, vec![[x + w * 0.5, y - off - 6.0], [x + w * 0.5, y]]); }
157 d
158}
159
160pub fn panel(x: f32, y: f32, w: f32, h: f32, bevel: f32, primary: Rgba, bg: Rgba) -> Draw {
162 let mut d = Draw::new();
163 d.fill(bg, bevel_poly(x, y, w, h, bevel));
164 d.stroke(primary, bevel_poly(x, y, w, h, bevel));
165 let hb = 16.0_f32.min(h * 0.25);
167 d.stroke(primary, vec![[x + bevel, y + hb], [x + w - bevel, y + hb]]);
168 d.fill(shade(primary, 0.35), vec![[x + bevel, y], [x + w * 0.4, y], [x + w * 0.4 - 6.0, y + hb], [x + bevel, y + hb]]);
169 d
170}
171
172pub fn scanlines(x: f32, y: f32, w: f32, h: f32, density: usize, line: Rgba) -> Draw {
174 let mut d = Draw::new();
175 let n = density.max(1);
176 for i in 0..n {
177 let py = y + h * i as f32 / n as f32;
178 d.stroke(line, vec![[x, py], [x + w, py]]);
179 }
180 d
181}
182
183pub fn bar(x: f32, y: f32, w: f32, h: f32, frac: f32, fill: Rgba, track: Rgba) -> Draw {
189 let mut d = Draw::new();
190 d.rect_outline(track, x, y, w, h);
191 let fw = w * clamp01(frac);
192 if fw > 0.5 { d.rect_fill(fill, x + 1.0, y + 1.0, (fw - 2.0).max(0.0), h - 2.0); }
193 d
194}
195
196pub fn segbar(x: f32, y: f32, w: f32, h: f32, frac: f32, segs: usize, fill: Rgba, track: Rgba) -> Draw {
198 let mut d = Draw::new();
199 let n = segs.max(1);
200 let gap = 2.0;
201 let cw = (w - gap * (n as f32 - 1.0)) / n as f32;
202 let lit = (clamp01(frac) * n as f32).round() as usize;
203 for i in 0..n {
204 let cx = x + i as f32 * (cw + gap);
205 if i < lit { d.rect_fill(fill, cx, y, cw, h); }
206 else { d.rect_outline(track, cx, y, cw, h); }
207 }
208 d
209}
210
211pub fn gauge(cx: f32, cy: f32, r: f32, frac: f32, needle: Rgba, accent: Rgba, track: Rgba) -> Draw {
213 let mut d = Draw::new();
214 let a0 = PI * 0.75; let a1 = PI * 0.25 + PI * 2.0; let span = a1 - a0;
217 d.stroke(track, arc(cx, cy, r, a0, a1, 60));
218 d.stroke(accent, arc(cx, cy, r, a0, a0 + span * clamp01(frac), 60));
220 for i in 0..=10 {
222 let a = a0 + span * i as f32 / 10.0;
223 let (c, s) = (a.cos(), a.sin());
224 d.stroke(track, vec![[cx + c * r, cy + s * r], [cx + c * (r - 7.0), cy + s * (r - 7.0)]]);
225 }
226 let a = a0 + span * clamp01(frac);
228 d.stroke(needle, vec![[cx, cy], [cx + a.cos() * (r - 4.0), cy + a.sin() * (r - 4.0)]]);
229 d.fill(needle, arc(cx, cy, 3.0, 0.0, PI * 2.0, 8));
230 d
231}
232
233pub fn ring(cx: f32, cy: f32, r: f32, frac: f32, fill: Rgba, track: Rgba) -> Draw {
235 let mut d = Draw::new();
236 let a0 = PI * 0.5 + 0.4;
237 let a1 = PI * 0.5 - 0.4 + PI * 2.0;
238 let span = a1 - a0;
239 d.stroke(track, arc(cx, cy, r, a0, a1, 64));
240 d.stroke(fill, arc(cx, cy, r, a0, a0 + span * clamp01(frac), 64));
241 d
242}
243
244pub fn vu(x: f32, y: f32, w: f32, h: f32, levels: &[f32], fill: Rgba, peak: Rgba) -> Draw {
246 let mut d = Draw::new();
247 let n = levels.len().max(1);
248 let gap = 2.0;
249 let bw = (w - gap * (n as f32 - 1.0)) / n as f32;
250 for (i, &lv) in levels.iter().enumerate() {
251 let bx = x + i as f32 * (bw + gap);
252 let bh = h * clamp01(lv);
253 let c = mix(fill, peak, clamp01(lv));
254 d.rect_fill(c, bx, y + h - bh, bw, bh);
255 }
256 d
257}
258
259pub fn spark(x: f32, y: f32, w: f32, h: f32, vals: &[f32], line: Rgba) -> Draw {
261 let mut d = Draw::new();
262 if vals.len() < 2 { return d; }
263 let (mut lo, mut hi) = (f32::MAX, f32::MIN);
264 for &v in vals { lo = lo.min(v); hi = hi.max(v); }
265 let rng = (hi - lo).max(1e-6);
266 let pl: Vec<[f32; 2]> = vals.iter().enumerate().map(|(i, &v)| {
267 [x + w * i as f32 / (vals.len() as f32 - 1.0), y + h - (v - lo) / rng * h]
268 }).collect();
269 d.stroke(line, pl);
270 d
271}
272
273pub fn battery(x: f32, y: f32, w: f32, h: f32, frac: f32, fill: Rgba, track: Rgba, warn: Rgba) -> Draw {
275 let mut d = Draw::new();
276 let bw = w - 4.0;
277 d.rect_outline(track, x, y, bw, h);
278 d.rect_fill(track, x + bw, y + h * 0.3, 4.0, h * 0.4); let f = clamp01(frac);
280 let c = if f < 0.25 { warn } else { fill };
281 if f > 0.0 { d.rect_fill(c, x + 2.0, y + 2.0, (bw - 4.0) * f, h - 4.0); }
282 d
283}
284
285pub fn button(x: f32, y: f32, w: f32, h: f32, hover: bool, active: bool, primary: Rgba, bg: Rgba) -> Draw {
291 let mut d = Draw::new();
292 let glow = if active { 0.55 } else if hover { 0.3 } else { 0.12 };
293 d.fill(shade(primary, glow), bevel_poly(x, y, w, h, 8.0));
294 d.stroke(if hover { mix(primary, 0xFFFFFF, 0.4) } else { primary }, bevel_poly(x, y, w, h, 8.0));
295 let _ = bg;
296 d
297}
298
299pub fn toggle(x: f32, y: f32, w: f32, h: f32, on: bool, on_col: Rgba, track: Rgba) -> Draw {
301 let mut d = Draw::new();
302 let r = h * 0.5;
303 let rail = if on { on_col } else { track };
304 let mut p = arc(x + r, y + r, r, PI * 0.5, PI * 1.5, 12);
306 p.extend(arc(x + w - r, y + r, r, PI * 1.5, PI * 2.5, 12));
307 p.push(p[0]);
308 d.stroke(rail, p);
309 let kx = if on { x + w - r } else { x + r };
310 d.fill(rail, arc(kx, y + r, r - 2.0, 0.0, PI * 2.0, 16));
311 d
312}
313
314pub fn slider(x: f32, y: f32, w: f32, frac: f32, hover: bool, fill: Rgba, track: Rgba) -> Draw {
316 let mut d = Draw::new();
317 d.stroke(track, vec![[x, y], [x + w, y]]);
318 let kx = x + w * clamp01(frac);
319 d.stroke(fill, vec![[x, y], [kx, y]]);
320 let kr = if hover { 8.0 } else { 6.0 };
321 d.fill(fill, arc(kx, y, kr, 0.0, PI * 2.0, 16));
322 d.stroke(mix(fill, 0xFFFFFF, 0.5), arc(kx, y, kr, 0.0, PI * 2.0, 16));
323 d
324}
325
326pub fn checkbox(x: f32, y: f32, s: f32, checked: bool, hover: bool, primary: Rgba, track: Rgba) -> Draw {
328 let mut d = Draw::new();
329 d.rect_outline(if hover { mix(primary, 0xFFFFFF, 0.4) } else { track }, x, y, s, s);
330 if checked {
331 d.stroke(primary, vec![[x + s * 0.22, y + s * 0.55], [x + s * 0.42, y + s * 0.75], [x + s * 0.8, y + s * 0.25]]);
332 }
333 d
334}
335
336pub fn tabs(x: f32, y: f32, w: f32, h: f32, count: usize, active: usize, hover: i32, primary: Rgba, track: Rgba) -> Draw {
338 let mut d = Draw::new();
339 let n = count.max(1);
340 let tw = w / n as f32;
341 for i in 0..n {
342 let tx = x + i as f32 * tw;
343 if i == active {
344 d.fill(shade(primary, 0.3), vec![[tx, y], [tx + tw, y], [tx + tw, y + h], [tx, y + h], [tx, y]]);
345 d.stroke(primary, vec![[tx, y + h], [tx, y], [tx + tw, y], [tx + tw, y + h]]);
346 } else {
347 let c = if hover == i as i32 { mix(track, primary, 0.5) } else { track };
348 d.stroke(c, vec![[tx, y + h], [tx + tw, y + h]]);
349 }
350 }
351 d
352}
353
354pub fn progress(x: f32, y: f32, w: f32, h: f32, frac: f32, fill: Rgba, track: Rgba) -> Draw {
356 let mut d = Draw::new();
357 d.rect_outline(track, x, y, w, h);
358 let fw = (w - 2.0) * clamp01(frac);
359 if fw > 0.5 { d.rect_fill(fill, x + 1.0, y + 1.0, fw, h - 2.0); }
360 d
361}
362
363pub fn tooltip(x: f32, y: f32, w: f32, h: f32, primary: Rgba, bg: Rgba) -> Draw {
365 let mut d = Draw::new();
366 d.fill(bg, bevel_poly(x, y, w, h, 6.0));
367 d.stroke(primary, bevel_poly(x, y, w, h, 6.0));
368 d.fill(bg, vec![[x + 12.0, y + h], [x + 22.0, y + h], [x + 14.0, y + h + 8.0]]);
369 d.stroke(primary, vec![[x + 12.0, y + h], [x + 14.0, y + h + 8.0], [x + 22.0, y + h]]);
370 d
371}
372
373pub fn stepper(x: f32, y: f32, w: f32, h: f32, hover_minus: bool, hover_plus: bool, primary: Rgba, track: Rgba) -> Draw {
375 let mut d = Draw::new();
376 let bw = h;
377 d.fill(shade(primary, if hover_minus { 0.4 } else { 0.12 }), bevel_poly(x, y, bw, h, 5.0));
378 d.stroke(primary, bevel_poly(x, y, bw, h, 5.0));
379 d.stroke(primary, vec![[x + bw * 0.3, y + h * 0.5], [x + bw * 0.7, y + h * 0.5]]);
380 let px = x + w - bw;
381 d.fill(shade(primary, if hover_plus { 0.4 } else { 0.12 }), bevel_poly(px, y, bw, h, 5.0));
382 d.stroke(primary, bevel_poly(px, y, bw, h, 5.0));
383 d.stroke(primary, vec![[px + bw * 0.3, y + h * 0.5], [px + bw * 0.7, y + h * 0.5]]);
384 d.stroke(primary, vec![[px + bw * 0.5, y + h * 0.3], [px + bw * 0.5, y + h * 0.7]]);
385 d.rect_outline(track, x + bw + 2.0, y, w - bw * 2.0 - 4.0, h);
386 d
387}
388
389pub fn healthbar(x: f32, y: f32, w: f32, h: f32, frac: f32, pulse: f32, full: Rgba, low: Rgba, track: Rgba) -> Draw {
396 let mut d = Draw::new();
397 d.rect_outline(track, x, y, w, h);
398 let f = clamp01(frac);
399 let mut c = mix(low, full, f);
400 if f < 0.3 { c = mix(c, 0xFFFFFF, pulse * 0.6); }
401 if f > 0.0 { d.rect_fill(c, x + 1.0, y + 1.0, (w - 2.0) * f, h - 2.0); }
402 for i in 1..10 { let nx = x + w * i as f32 / 10.0; d.stroke(track, vec![[nx, y], [nx, y + h]]); }
403 d
404}
405
406pub fn cooldown(cx: f32, cy: f32, r: f32, frac: f32, fill: Rgba, track: Rgba) -> Draw {
408 let mut d = Draw::new();
409 d.stroke(track, arc(cx, cy, r, 0.0, PI * 2.0, 48));
410 let a0 = -PI * 0.5;
411 let mut wedge = vec![[cx, cy]];
412 wedge.extend(arc(cx, cy, r, a0, a0 + PI * 2.0 * clamp01(frac), 48));
413 wedge.push([cx, cy]);
414 d.fill(shade(fill, 0.55), wedge);
415 d
416}
417
418pub fn counter(x: f32, y: f32, dw: f32, dh: f32, value: i64, digits: usize, on: Rgba, off: Rgba) -> Draw {
420 let mut d = Draw::new();
421 let segmap: [u8; 10] = [0b1111110, 0b0110000, 0b1101101, 0b1111001, 0b0110011,
422 0b1011011, 0b1011111, 0b1110000, 0b1111111, 0b1111011];
423 let gap = dw * 0.35;
424 let v = value.unsigned_abs();
425 for i in 0..digits {
426 let digit = ((v / 10u64.pow((digits - 1 - i) as u32)) % 10) as usize;
427 let mask = segmap[digit];
428 let dx = x + i as f32 * (dw + gap);
429 seven_seg(&mut d, dx, y, dw, dh, mask, on, off);
430 }
431 d
432}
433
434fn seven_seg(d: &mut Draw, x: f32, y: f32, w: f32, h: f32, mask: u8, on: Rgba, off: Rgba) {
435 let t = w * 0.12; let mid = y + h * 0.5;
437 let bars = [
439 (6, [x + t, y, w - 2.0 * t, t]), (5, [x + w - t, y + t, t, h * 0.5 - 1.5 * t]), (4, [x + w - t, mid + 0.5 * t, t, h * 0.5 - 1.5 * t]), (3, [x + t, y + h - t, w - 2.0 * t, t]), (2, [x, mid + 0.5 * t, t, h * 0.5 - 1.5 * t]), (1, [x, y + t, t, h * 0.5 - 1.5 * t]), (0, [x + t, mid - 0.5 * t, w - 2.0 * t, t]), ];
447 for (bit, r) in bars {
448 let c = if mask & (1 << bit) != 0 { on } else { off };
449 d.rect_fill(c, r[0], r[1], r[2], r[3]);
450 }
451}
452
453pub fn minimap(x: f32, y: f32, w: f32, h: f32, primary: Rgba, bg: Rgba) -> Draw {
455 let mut d = Draw::new();
456 d.fill(bg, vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h], [x, y]]);
457 for seg in holo::corner_brackets(x, y, w, h, 14.0) {
458 d.stroke(primary, vec![[seg[0], seg[1]], [seg[2], seg[3]]]);
459 }
460 d
461}
462
463pub fn dpad(cx: f32, cy: f32, r: f32, dir: i32, primary: Rgba, track: Rgba) -> Draw {
465 let mut d = Draw::new();
466 let arm = r * 0.45;
467 let dirs = [(0.0, -1.0), (1.0, 0.0), (0.0, 1.0), (-1.0, 0.0)];
468 d.stroke(track, arc(cx, cy, r, 0.0, PI * 2.0, 32));
469 for (i, (dx, dy)) in dirs.iter().enumerate() {
470 let on = dir == i as i32 + 1;
471 let c = if on { primary } else { track };
472 let bx = cx + dx * r * 0.55;
473 let by = cy + dy * r * 0.55;
474 let tri = vec![
475 [bx + dx * arm + dy * arm * 0.4, by + dy * arm + dx * arm * 0.4],
476 [bx + dx * arm - dy * arm * 0.4, by + dy * arm - dx * arm * 0.4],
477 [bx - dx * arm * 0.3, by - dy * arm * 0.3],
478 [bx + dx * arm + dy * arm * 0.4, by + dy * arm + dx * arm * 0.4],
479 ];
480 if on { d.fill(c, tri.clone()); }
481 d.stroke(c, tri);
482 }
483 d
484}
485
486pub fn slotgrid(x: f32, y: f32, cols: usize, rows: usize, cell: f32, sel: i32, primary: Rgba, track: Rgba) -> Draw {
488 let mut d = Draw::new();
489 let gap = 4.0;
490 for r in 0..rows {
491 for c in 0..cols {
492 let idx = (r * cols + c) as i32;
493 let cx = x + c as f32 * (cell + gap);
494 let cy = y + r as f32 * (cell + gap);
495 if idx == sel {
496 d.fill(shade(primary, 0.3), vec![[cx, cy], [cx + cell, cy], [cx + cell, cy + cell], [cx, cy + cell], [cx, cy]]);
497 d.rect_outline(primary, cx, cy, cell, cell);
498 } else {
499 d.rect_outline(track, cx, cy, cell, cell);
500 }
501 }
502 }
503 d
504}
505
506pub fn vignette(w: f32, h: f32, intensity: f32, col: Rgba) -> Draw {
508 let mut d = Draw::new();
509 let t = clamp01(intensity);
510 let layers = (1.0 + t * 10.0) as usize;
511 for i in 0..layers {
512 let inset = i as f32 * 3.0;
513 let c = shade(col, t * (1.0 - i as f32 / layers as f32));
514 d.rect_outline(c, inset, inset, (w - inset * 2.0).max(0.0), (h - inset * 2.0).max(0.0));
515 }
516 d
517}
518
519#[inline]
524fn proj3(p: [f32; 3], spin: f32, tilt: f32, cx: f32, cy: f32, scale: f32) -> [f32; 2] {
525 let (cs, sn) = (spin.cos(), spin.sin());
527 let (ct, st) = (tilt.cos(), tilt.sin());
528 let x1 = p[0] * cs + p[2] * sn;
529 let z1 = -p[0] * sn + p[2] * cs;
530 let y2 = p[1] * ct - z1 * st;
531 let z2 = p[1] * st + z1 * ct;
532 let f = 3.0 / (3.0 + z2);
533 [cx + x1 * scale * f, cy + y2 * scale * f]
534}
535
536pub fn gauge3d(cx: f32, cy: f32, r: f32, frac: f32, spin: f32, fill: Rgba, track: Rgba) -> Draw {
539 let mut d = Draw::new();
540 let n = 48;
541 let lit = (clamp01(frac) * n as f32) as usize;
542 let mut prev = proj3([1.0, 0.0, 0.0], spin, 0.9, cx, cy, r);
543 for i in 1..=n {
544 let a = i as f32 / n as f32 * PI * 2.0;
545 let p = proj3([a.cos(), 0.0, a.sin()], spin, 0.9, cx, cy, r);
546 let c = if i <= lit { fill } else { track };
547 d.stroke(c, vec![prev, p]);
548 prev = p;
549 }
550 for k in 0..8 {
552 let a = k as f32 / 8.0 * PI * 2.0;
553 let o = proj3([a.cos(), 0.0, a.sin()], spin, 0.9, cx, cy, r);
554 let inn = proj3([a.cos() * 0.7, 0.0, a.sin() * 0.7], spin, 0.9, cx, cy, r);
555 d.stroke(shade(track, 0.7), vec![inn, o]);
556 }
557 d
558}
559
560pub fn panel3d(x: f32, y: f32, w: f32, h: f32, depth: f32, primary: Rgba, bg: Rgba) -> Draw {
562 let mut d = Draw::new();
563 let dx = depth * 0.7;
564 let dy = -depth * 0.7;
565 let front = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]];
566 let back: Vec<[f32; 2]> = front.iter().map(|p| [p[0] + dx, p[1] + dy]).collect();
567 d.fill(shade(primary, 0.18), vec![front[1], back[1], back[2], front[2], front[1]]);
569 d.fill(shade(primary, 0.28), vec![front[0], back[0], back[1], front[1], front[0]]);
570 for i in 0..4 { d.stroke(shade(primary, 0.6), vec![front[i], back[i]]); }
571 d.stroke(primary, back.iter().cloned().chain(std::iter::once(back[0])).collect());
572 d.fill(bg, front.iter().cloned().chain(std::iter::once(front[0])).collect());
573 d.stroke(primary, front.iter().cloned().chain(std::iter::once(front[0])).collect());
574 d
575}
576
577pub fn radar3d(cx: f32, cy: f32, r: f32, tilt: f32, sweep: f32, primary: Rgba, track: Rgba) -> Draw {
579 let mut d = Draw::new();
580 for k in 1..=3 {
581 let rr = r * k as f32 / 3.0;
582 let mut pl = Vec::new();
583 for i in 0..=40 {
584 let a = i as f32 / 40.0 * PI * 2.0;
585 pl.push(proj3([a.cos() * rr / r, 0.0, a.sin() * rr / r], 0.0, tilt, cx, cy, r));
586 }
587 d.stroke(track, pl);
588 }
589 let o = proj3([sweep.cos(), 0.0, sweep.sin()], 0.0, tilt, cx, cy, r);
590 let c = proj3([0.0, 0.0, 0.0], 0.0, tilt, cx, cy, r);
591 d.stroke(primary, vec![c, o]);
592 d
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598
599 #[test]
600 fn bar_fill_is_proportional() {
601 let empty = bar(0.0, 0.0, 100.0, 10.0, 0.0, 0xFFFFFF, 0x222222);
602 let half = bar(0.0, 0.0, 100.0, 10.0, 0.5, 0xFFFFFF, 0x222222);
603 assert!(empty.fills.is_empty());
604 assert_eq!(half.fills.len(), 1);
605 let poly = &half.fills[0].1;
607 let wpx = poly[1][0] - poly[0][0];
608 assert!((wpx - 48.0).abs() < 2.0, "got {wpx}");
609 }
610
611 #[test]
612 fn segbar_lights_cells() {
613 let d = segbar(0.0, 0.0, 100.0, 10.0, 0.5, 10, 0xFFFFFF, 0x222222);
614 assert_eq!(d.fills.len(), 5); }
616
617 #[test]
618 fn counter_renders_digits() {
619 let d = counter(0.0, 0.0, 12.0, 20.0, 42, 3, 0xFFFFFF, 0x111111);
620 assert_eq!(d.fills.len(), 21);
622 }
623
624 #[test]
625 fn mix_and_shade() {
626 assert_eq!(mix(0x000000, 0xFFFFFF, 0.5) & 0xFF, 0x7F);
627 assert_eq!(shade(0x808080, 2.0), 0xFFFFFF);
628 }
629}