==> src/doom.c <==1/* ui.rs's "3 doom" section, ported. */
2#include "mesh/top_ui_doom.h"
3
4#include <math.h>
5#include <stdlib.h>
6#include <string.h>
7
8tui_color mt_wall_color(uint32_t kind, uint32_t side, float dist) {
9 const uint8_t *base = MT_WALL_RGB[(size_t)(kind - 1) % 7];
10 float fog;
11 if (isfinite(dist)) {
12 fog = 1.0f / (1.0f + 0.12f * (dist > 0.0f ? dist : 0.0f));
13 if (fog < 0.22f) fog = 0.22f;
14 if (fog > 1.0f) fog = 1.0f;
15 } else {
16 fog = 0.22f;
17 }
18 return mt_rgb(base, fog * (side == 1 ? 0.68f : 1.0f));
19}
20
21bool mt_shown_frame(const mt_section *p, mt_shown *out) {
22 uint32_t show = MESH_DOOM_D_SHOW;
23 uint32_t seq = mt_section_view_u32(p, show + MESH_SH_SEQ);
24 if (seq == 0 || mt_section_view_len(p) < MESH_DOOM_SNAP_BYTES) return false;
25 uint32_t k = seq & 1, b = mesh_doom_show_data(seq & 1);
26 out->arrays[0] = b + MESH_DOOM_SHOW_HEIGHT;
27 out->arrays[1] = b + MESH_DOOM_SHOW_KIND;
28 out->arrays[2] = b + MESH_DOOM_SHOW_SIDE;
29 out->arrays[3] = b + MESH_DOOM_SHOW_DIST;
30 out->frame = mt_section_view_u32(p, show + MESH_SH_BUF + MESH_SH_BUF_BYTES * k + MESH_SHB_FRAME);
31 out->sys = mt_section_view_u32(p, show + MESH_SH_BUF + MESH_SH_BUF_BYTES * k + MESH_SHB_SYS);
32 return true;
33}
34
35/* One row under the frame: each column in the colour of the machine that rendered it. */
36static void render_markers(mt_frame *f, tui_rect area, uint32_t sys, uint32_t parts) {
37 for (uint16_t x = 0; x < area.width; x++) {
38 uint32_t c = (uint32_t)x * MESH_DOOM_WIDTH / tui_max16(area.width, 1);
39 uint32_t part = c * parts / MESH_DOOM_WIDTH;
40 if (part > 7) part = 7;
41 uint32_t k = sys >> (4 * part) & 0xf;
42 tui_cell *cell = tui_buffer_cell(f->buf, (uint16_t)(area.x + x), area.y);
43 if (!cell) continue;
44 tui_cell_set_str(cell, "━");
45 cell->fg = mt_sys_color(k);
46 cell->bg = MT_BG;
47 }
48}
49
50static const uint8_t SKY_TOP[3] = {70, 80, 120}, SKY_LOW[3] = {16, 18, 30}, FLOOR_NEAR[3] = {30, 24, 18}, FLOOR_FAR[3] = {120, 92, 62};
51
52void mt_render_doom_at(mt_frame *f, tui_rect area, const mt_section *p, const uint32_t at[4]) {
53 if (area.width == 0 || area.height == 0) return;
54 float rows = mt_section_view_f32(p, MESH_DOOM_D_ROWS);
55 if (!(isfinite(rows) && rows >= 1.0f)) rows = 1.0f;
56 float px = (float)area.height * 2.0f;
57 float half = px / 2.0f;
58 for (uint16_t x = 0; x < area.width; x++) {
59 size_t c = (size_t)x * MESH_DOOM_WIDTH / area.width;
60 float h = mt_section_view_f32(p, at[0] + 4 * c);
61 uint32_t kind = mt_section_view_u32(p, at[1] + 4 * c);
62 uint32_t side = mt_section_view_u32(p, at[2] + 4 * c);
63 float dist = mt_section_view_f32(p, at[3] + 4 * c);
64 float wall = 0.0f;
65 if (kind != 0 && isfinite(h) && h > 0.0f) {
66 wall = h / rows * px;
67 if (!(wall <= px * 4.0f)) wall = px * 4.0f; /* f32::min: NaN takes the other */
68 }
69 float top = half - wall / 2.0f, bot = half + wall / 2.0f;
70 tui_color wc = wall > 0.0f ? mt_wall_color(kind, side, dist) : MT_BG;
71 for (uint16_t y = 0; y < area.height; y++) {
72 tui_color pix[2];
73 for (int k = 0; k < 2; k++) {
74 float yy = (float)(2 * y + k) + 0.5f;
75 if (wall > 0.0f && yy >= top && yy < bot) pix[k] = wc;
76 else if (yy < half) pix[k] = mt_lerp(SKY_TOP, SKY_LOW, yy / half);
77 else pix[k] = mt_lerp(FLOOR_NEAR, FLOOR_FAR, (yy - half) / half);
78 }
79 tui_cell *cell = tui_buffer_cell(f->buf, (uint16_t)(area.x + x), (uint16_t)(area.y + y));
80 if (!cell) continue;
81 tui_cell_set_str(cell, "▀");
82 cell->fg = pix[0];
83 cell->bg = pix[1];
84 }
85 }
86}
87
88void mt_render_doom(mt_frame *f, tui_rect area, const mt_section *p) {
89 const uint32_t at[4] = {MESH_DOOM_D_HEIGHT, MESH_DOOM_D_KIND, MESH_DOOM_D_SIDE, MESH_DOOM_D_DIST};
90 mt_render_doom_at(f, area, p, at);
91}
92
93/* The 16x16 map, two characters a cell, the player as an arrow. */
94static void render_minimap(mt_frame *f, tui_rect area, const mt_section *p) {
95 float px = mt_section_view_f32(p, MESH_DOOM_D_POS_X), py = mt_section_view_f32(p, MESH_DOOM_D_POS_Y);
96 float dx = mt_section_view_f32(p, MESH_DOOM_D_DIR_X), dy = mt_section_view_f32(p, MESH_DOOM_D_DIR_Y);
97 /* y grows downward on screen as it does in the map's rows. */
98 static const char *const arrows[8] = {"→", "↘", "↓", "↙", "←", "↖", "↑", "↗"};
99 int32_t o = mt_sat_i32(roundf(atan2f(dy, dx) / 0.785398163397448309616f));
100 size_t oct = (size_t)(((o % 8) + 8) % 8);
101 int32_t fx = mt_sat_i32(floorf(px)), fy = mt_sat_i32(floorf(py));
102 for (uint16_t my = 0; my < tui_min16(16, area.height); my++)
103 for (uint16_t mx = 0; mx < tui_min16(16, (uint16_t)(area.width / 2)); mx++) {
104 uint32_t k = mt_section_view_u32(p, MESH_DOOM_D_MAP + 4 * (16 * (size_t)my + mx));
105 const char *sym;
106 tui_style st;
107 if (fx == mx && fy == my) {
108 sym = MT_FMT(f, "%s ", arrows[oct]);
109 st = mt_fg_bg_bold(MT_WARN, MT_FAINT);
110 } else if (k == 0) {
111 sym = "· ";
112 st = tui_fg(MT_FAINT);
113 } else {
114 sym = "██";
115 st = tui_fg(mt_wall_color(k, 0, 0.0f));
116 }
117 tui_buffer_set_string(f->buf, (uint16_t)(area.x + 2 * mx), (uint16_t)(area.y + my), sym, st);
118 }
119}
120
121static void centered_note(mt_frame *f, tui_rect inner, const char *msg) {
122 tui_paragraph p = tui_paragraph_new(tui_text_line(f->a, tui_line_span(f->a, mt_styled(msg, tui_fg_bg(MT_BG, MT_WARN)))));
123 p.alignment = TUI_ALIGN_CENTER;
124 tui_paragraph_render(f->a, &p, mt_rect(inner.x, inner.y, inner.width, 1), f->buf);
125}
126
127static int u32_cmp(const void *a, const void *b) {
128 uint32_t x = *(const uint32_t *)a, y = *(const uint32_t *)b;
129 return x < y ? -1 : x > y;
130}
131
132void mt_draw_doom(mt_frame *f, tui_rect area, const mt_app *app) {
133 const mt_machine *m = mt_app_focused(app);
134 uint16_t side_w = area.width >= 80 ? 36 : 0;
135 tui_rect hs[2];
136 tui_hsplit(area, 2, (tui_constraint[]){TUI_FILL(1), TUI_LENGTH(side_w)}, hs);
137 tui_rect view = hs[0], side = hs[1];
138 const mt_section *doom = mt_machine_program(m, "doom");
139 mt_place place;
140 bool has_place = doom && mt_place_of(doom, &place);
141 bool placed = has_place && place.kind != MT_PLACE_LOCAL;
142 /* fps: the frames shown when placed (whole frames, matched halves), else doom's "frames" counter. */
143 const char *fps_s = "-";
144 if (placed) {
145 fps_s = mt_ff(f, mt_rate_per_s(&m->doom_shown), 1);
146 } else if (doom) {
147 for (size_t i = 0; i < MESH_COUNTERS; i++) {
148 char cn[3 * MESH_NAME_BYTES + 1];
149 mt_section_counter_name(doom, i, cn);
150 if (strcmp(cn, "frames") == 0) {
151 fps_s = mt_ff(f, mt_machine_counter_rate(m, mt_section_slot(doom), i), 1);
152 break;
153 }
154 }
155 }
156 uint32_t fanout = 4;
157 if (doom) {
158 fanout = mt_section_pt(doom, MESH_PT_FANOUT);
159 if (fanout < 1) fanout = 1;
160 }
161 const char *where_s = "";
162 if (has_place && place.kind == MT_PLACE_SPLIT) {
163 mt_part_run runs[MESH_PLACE_PARTS_MAX];
164 size_t nr = mt_part_runs(place.assign, fanout, runs);
165 const char *j = "";
166 for (size_t i = 0; i < nr; i++) {
167 uint32_t c0 = runs[i].first * MESH_DOOM_WIDTH / fanout, c1 = (runs[i].last + 1) * MESH_DOOM_WIDTH / fanout - 1;
168 j = MT_FMT(f, "%s%s%u-%u system %u", j, i ? " · " : "", c0, c1, runs[i].system);
169 }
170 where_s = MT_FMT(f, " split: cols %s", j);
171 } else if (has_place && place.kind == MT_PLACE_PIN) {
172 where_s = MT_FMT(f, " pinned to system %u", place.pin);
173 }
174 tui_color color;
175 tui_line title = mt_machine_title(f, m, true, MT_FMT(f, "doom %s fps%s", fps_s, where_s), &color);
176 tui_block block = mt_panel(f, title, color);
177 tui_rect inner = tui_block_inner(&block, view);
178 mt_block(f, &block, view);
179 const mt_section *p = mt_bound(f, inner, m, "doom");
180 if (!p) return;
181 mt_shown sf;
182 bool shown = placed && mt_shown_frame(p, &sf);
183 if (shown) {
184 /* The frame, then one row marking which machine rendered each column. */
185 tui_rect vs[2];
186 tui_vsplit(inner, 2, (tui_constraint[]){TUI_FILL(1), TUI_LENGTH(1)}, vs);
187 mt_render_doom_at(f, vs[0], p, sf.arrays);
188 uint32_t parts = mt_section_pt(p, MESH_PT_FANOUT);
189 render_markers(f, vs[1], sf.sys, parts < 1 ? 1 : parts);
190 } else {
191 mt_render_doom(f, inner, p);
192 }
193 uint32_t here = m->has_snap ? mt_snapshot_host_u32(&m->snap, MESH_S_SYSTEM) : 0;
194 bool pinned_away = has_place && place.kind == MT_PLACE_PIN && place.pin != here;
195 if (!mt_section_running(p) && !pinned_away)
196 centered_note(f, inner, MT_FMT(f, " doom is %s: press Esc, then d to start it ", mt_section_state_name(p)));
197 else if (placed && !shown)
198 centered_note(f, inner, " placed: waiting for the first whole frame ");
199 if (side_w == 0) return;
200 tui_rect ss[2];
201 tui_vsplit(side, 2, (tui_constraint[]){TUI_LENGTH(18), TUI_FILL(1)}, ss);
202 tui_block map_block = mt_panel(f, tui_line_span(f->a, mt_label(" map ")), MT_FAINT);
203 tui_rect map_inner = tui_block_inner(&map_block, ss[0]);
204 mt_block(f, &map_block, ss[0]);
205 render_minimap(f, map_inner, p);
206 float px = mt_section_view_f32(p, MESH_DOOM_D_POS_X), py = mt_section_view_f32(p, MESH_DOOM_D_POS_Y);
207 float dx = mt_section_view_f32(p, MESH_DOOM_D_DIR_X), dy = mt_section_view_f32(p, MESH_DOOM_D_DIR_Y);
208 uint32_t me = 0;
209 mt_machine_system(m, &me);
210 tui_line place_line;
211 if (!has_place) {
212 place_line = MT_LINE(f, mt_label("place "), mt_fgs("-", MT_FAINT));
213 } else if (place.kind == MT_PLACE_LOCAL) {
214 place_line = MT_LINE(f, mt_label("place "), mt_val("local"), mt_label(" p/v: place"));
215 } else if (place.kind == MT_PLACE_PIN) {
216 place_line = MT_LINE(f, mt_label("place "), mt_fgb(MT_FMT(f, "pin %u", place.pin), mt_sys_color(place.pin)),
217 mt_label(place.pin == me ? " runs here" : " mirrored here"));
218 } else {
219 size_t mine = 0;
220 for (size_t b = 0; b < fanout && b < MESH_PLACE_PARTS_MAX; b++) mine += place.assign[b] == (uint8_t)me;
221 const char *role = place.assign[0] == me ? MT_FMT(f, " owner (pose), %zu part%s", mine, mine == 1 ? "" : "s")
222 : mine > 0 ? MT_FMT(f, " follower, %zu part%s", mine, mine == 1 ? "" : "s")
223 : " mirror (no parts)";
224 place_line = MT_LINE(f, mt_label("place "), mt_fgb("split", MT_ACCENT), mt_label(role));
225 }
226 uint32_t frame_no = shown ? sf.frame : mt_section_view_u32(p, MESH_DOOM_D_FRAME);
227 mt_lines lines = {0};
228 mt_lines_push(f, &lines, MT_LINE(f, mt_label("fps "), mt_fgb(fps_s, MT_WARN)));
229 mt_lines_push(f, &lines, place_line);
230 mt_lines_push(f, &lines, MT_LINE(f, mt_label("frame "), mt_val(MT_FMT(f, "%u", frame_no)), mt_label(shown ? " (shown)" : "")));
231 mt_lines_push(f, &lines, MT_LINE(f, mt_label("pos "), mt_val(MT_FMT(f, "%s, %s", mt_ff(f, px, 2), mt_ff(f, py, 2)))));
232 mt_lines_push(f, &lines, MT_LINE(f, mt_label("dir "), mt_val(MT_FMT(f, "%s, %s", mt_ffp(f, dx, 2), mt_ffp(f, dy, 2)))));
233 mt_lines_push(f, &lines, MT_LINE(f, mt_label("cols "), mt_val(MT_FMT(f, "%u", MESH_DOOM_WIDTH)), mt_label(" rays, 1 lane each")));
234 mt_lines_push(f, &lines, MT_LINE(f, mt_label("slot "), mt_val(MT_FMT(f, "%u", mt_section_slot(p))),
235 mt_label(MT_FMT(f, " nodes %u", mt_section_pt(p, MESH_PT_NODES)))));
236 size_t nparts = fanout < MESH_PLACE_PARTS_MAX ? fanout : MESH_PLACE_PARTS_MAX;
237 if (has_place && place.kind == MT_PLACE_SPLIT) {
238 /* The assignment, one coloured system digit per part. */
239 tui_line parts = tui_line_span(f->a, mt_label("parts "));
240 for (size_t i = 0; i < nparts; i++) {
241 uint8_t k = place.assign[i];
242 tui_line_push(f->a, &parts, k == MESH_PART_NONE ? mt_label("- ") : mt_fgb(MT_FMT(f, "%u ", k), mt_sys_color(k)));
243 }
244 mt_lines_push(f, &lines, parts);
245 uint32_t lat = mt_section_view_u32(p, MESH_DOOM_D_SHOW + MESH_SH_LAT_US);
246 mt_lines_push(f, &lines, MT_LINE(f, mt_label("added "), mt_val(MT_FMT(f, "%s ms", mt_ff(f, (float)lat / 1000.0f, 2))), mt_label(" (last part)")));
247 }
248 if (has_place && place.kind != MT_PLACE_LOCAL) {
249 uint32_t to = mt_place_input_to(place, me);
250 mt_lines_push(f, &lines, MT_LINE(f, mt_label("keys "), mt_val(to == me ? "here" : MT_FMT(f, "→ system %u", to))));
251 /* A legend of the systems whose colours the marker row uses. */
252 uint32_t syss[MESH_PLACE_PARTS_MAX + 8];
253 size_t ns = 0;
254 if (place.kind == MT_PLACE_PIN) {
255 syss[ns++] = place.pin;
256 } else {
257 for (size_t i = 0; i < nparts; i++)
258 if (place.assign[i] != MESH_PART_NONE) syss[ns++] = place.assign[i];
259 }
260 if (shown)
261 for (uint32_t b = 0; b < fanout && b < 8; b++) syss[ns++] = sf.sys >> (4 * b) & 0xf;
262 qsort(syss, ns, sizeof *syss, u32_cmp);
263 tui_line legend = tui_line_empty();
264 for (size_t i = 0; i < ns; i++) {
265 if (i && syss[i] == syss[i - 1]) continue;
266 tui_line_push(f->a, &legend, mt_fgs("━", mt_sys_color(syss[i])));
267 tui_line_push(f->a, &legend, mt_label(MT_FMT(f, " system %u ", syss[i])));
268 }
269 mt_lines_push(f, &lines, legend);
270 }
271 tui_block b = mt_panel(f, tui_line_span(f->a, mt_label(" player ")), MT_FAINT);
272 tui_paragraph pp = tui_paragraph_new(mt_text(f, &lines));
273 pp.block = &b;
274 tui_paragraph_render(f->a, &pp, ss[1], f->buf);
275}