skip to content
==> 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}