From d4f820e37bbb6571af4587adb2a3b3519d76849a Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sat, 5 Oct 2024 08:28:22 +0200 Subject: Draw triangles and lines --- examples/graph.c | 66 ++++++++++++++++++++++ examples/gravity.c | 11 +--- examples/ui.c | 18 +++--- graphics.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 223 insertions(+), 35 deletions(-) create mode 100755 examples/graph.c diff --git a/examples/graph.c b/examples/graph.c new file mode 100755 index 0000000..c7b2877 --- /dev/null +++ b/examples/graph.c @@ -0,0 +1,66 @@ +#if 0 /* +#/ ================================================================ +#/ +#/ graph.c +#/ +#/ ================================================================ +#/ +#/ Self-compilation shell script +#/ +SRC=${0##*./} +BIN=${SRC%.*} +gcc \ + -Wall -Wextra -Werror -pedantic \ + -Wno-old-style-declaration \ + -Wno-missing-braces \ + -Wno-unused-variable \ + -Wno-unused-but-set-variable \ + -Wno-unused-parameter \ + -Wno-overlength-strings \ + -O3 \ + -fsanitize=undefined,address,leak -mshstk \ + -lX11 -lm \ + -o $BIN $SRC && \ + ./$BIN $@ && rm $BIN +exit $? # */ +#endif + +#include "../graphics.c" + +i32 main(i32 argc, c8 **argv) { + (void) argc; + (void) argv; + + platform = (Platform) { + .title = "Graph", + .frame_width = 960, + .frame_height = 720, + }; + + u32 WHITE = u32_from_rgb(1.f, 1.f, 1.f); + u32 BLACK = u32_from_rgb(0.f, 0.f, 0.f); + u32 RED = u32_from_rgb(1.f, 0.f, 0.f); + u32 BLUE = u32_from_rgb(0.f, 0.f, 1.f); + + p_init(); + + while (!platform.done) { + p_wait_events(); + + i64 x = platform.frame_width / 2; + i64 y = platform.frame_height / 2; + + fill_rectangle(OP_SET, WHITE, 0, 0, platform.frame_width, platform.frame_height); + + fill_triangle(OP_SET, RED, 100, 100, 200, 100, 150, 200); + + fill_line(OP_SET, BLUE, 100, 300, 300, 500, 30); + + fill_ellipse(OP_SET, BLACK, x - 140, y - 100, 280, 200); + + p_render_frame(); + } + + p_cleanup(); + return 0; +} diff --git a/examples/gravity.c b/examples/gravity.c index 8bc45e0..246aab1 100755 --- a/examples/gravity.c +++ b/examples/gravity.c @@ -40,13 +40,6 @@ typedef struct { Entity entities[10 * 1024 * 1024]; } World; -i64 time_milliseconds() { - struct timespec t; - timespec_get(&t, TIME_UTC); - - return (i64) t.tv_sec * 1000ll + (i64) t.tv_nsec / 1000000ll; -} - i32 main(i32 argc, c8 **argv) { (void) argc; (void) argv; @@ -64,12 +57,12 @@ i32 main(i32 argc, c8 **argv) { srand(time(0)); static World world = {0}; - i64 time_0 = time_milliseconds(); + i64 time_0 = p_time(); while (!platform.done) { p_handle_events(); - i64 time_elapsed = time_milliseconds() - time_0; + i64 time_elapsed = p_time() - time_0; time_0 += time_elapsed; if (platform.key_pressed[BUTTON_LEFT]) { diff --git a/examples/ui.c b/examples/ui.c index 0847af3..4730a9d 100755 --- a/examples/ui.c +++ b/examples/ui.c @@ -59,12 +59,12 @@ i32 main(i32 argc, c8 **argv) { if (platform.cursor_x >= 40 && platform.cursor_x < 100 && platform.cursor_y >= 40 && platform.cursor_y < 100) { button_0_down = platform.key_down[BUTTON_LEFT]; if (button_0_down) - draw_panel(OP_SET, 0xffffff, 40, 40, 60, 60); + fill_rectangle(OP_SET, 0xffffff, 40, 40, 60, 60); else - draw_panel(OP_SET, 0x00ff00, 40, 40, 60, 60); + fill_rectangle(OP_SET, 0x00ff00, 40, 40, 60, 60); } else { button_0_down = 0; - draw_panel(OP_SET, 0x208020, 40, 40, 60, 60); + fill_rectangle(OP_SET, 0x208020, 40, 40, 60, 60); } if (platform.cursor_x >= 40 && platform.cursor_x < 100 && platform.cursor_y >= 120 && platform.cursor_y < 180) { @@ -72,17 +72,17 @@ i32 main(i32 argc, c8 **argv) { if (platform.key_pressed[BUTTON_LEFT]) button_1_checked = !button_1_checked; if (button_1_down) - draw_panel(OP_SET, 0xffffff, 40, 120, 60, 60); + fill_rectangle(OP_SET, 0xffffff, 40, 120, 60, 60); else if (button_1_checked) - draw_panel(OP_SET, 0xff8080, 40, 120, 60, 60); + fill_rectangle(OP_SET, 0xff8080, 40, 120, 60, 60); else - draw_panel(OP_SET, 0x80ff80, 40, 120, 60, 60); + fill_rectangle(OP_SET, 0x80ff80, 40, 120, 60, 60); } else { button_1_down = 0; if (button_1_checked) - draw_panel(OP_SET, 0xff0000, 40, 120, 60, 60); + fill_rectangle(OP_SET, 0xff0000, 40, 120, 60, 60); else - draw_panel(OP_SET, 0x00ff00, 40, 120, 60, 60); + fill_rectangle(OP_SET, 0x00ff00, 40, 120, 60, 60); } i64 w = platform.frame_width / 2; @@ -246,7 +246,7 @@ i32 main(i32 argc, c8 **argv) { draw_text_area(0, x0 + 8, y0 - 8, w, h, 10., 10., text_len, text); draw_text_area(color, x0, y0, w, h, 10., 10., text_len, text); - draw_text_cursor(0xffffff, x0, y0, w, h, 10., 10., cursor, selection, text_len, text); + draw_selection_cursor(0xffffff, x0, y0, w, h, 10., 10., cursor, selection, text_len, text); p_render_frame(); } diff --git a/graphics.c b/graphics.c index b054db7..50f3b68 100644 --- a/graphics.c +++ b/graphics.c @@ -2,6 +2,12 @@ // // graphics.c // +// ---------------------------------------------------------------- +// +// TODO: +// - Blending. +// - Anti-aliasing. +// // ================================================================ #ifndef GRAPHICS_HEADER_GUARD_ @@ -13,24 +19,53 @@ #include "reduced_system_layer.c" +#define EPSILON 1e-9 + enum { OP_SET, OP_XOR, }; -u32 u32_from_rgb(f32 red, f32 green, f32 blue); -void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height); -void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text); -void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text); +u32 u32_from_rgb (f32 red, f32 green, f32 blue); +void fill_rectangle (u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height); +void fill_triangle (u32 op, u32 color, f64 x0, f64 y0, f64 x1, f64 y1, f64 x2, f64 y2); +void fill_ellipse (u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height); +void fill_line (u32 op, u32 color, f64 x0, f64 y0, f64 x1, f64 y1, f64 width); +void draw_text_area (u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text); +void draw_selection_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text); #endif // GRAPHICS_HEADER_GUARD_ +// ================================================================ + #ifndef GRAPHICS_HEADER #ifndef GRAPHICS_IMPL_GUARD_ #define GRAPHICS_IMPL_GUARD_ #include +f64 min3(f64 a, f64 b, f64 c) { + if (a < b && a < c) + return a; + if (b < c) + return b; + return c; +} + +f64 max3(f64 a, f64 b, f64 c) { + if (a > b && a > c) + return a; + if (b > c) + return b; + return c; +} + +b8 same_sign(f64 a, f64 b) { + if (a >= EPSILON && b <= -EPSILON) return 0; + if (a <= -EPSILON && b >= EPSILON) return 0; + return 1; +} + u32 u32_from_rgb(f32 red, f32 green, f32 blue) { i32 r = (i32) floor(red * 255.f); i32 g = (i32) floor(green * 255.f); @@ -202,7 +237,7 @@ i64 enum_text_rows(i64 num_chars, c32 *text) { return rows; } -void print_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { +void draw_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_chars, c32 *text) { assert(text != NULL); f64 x = x0; @@ -256,7 +291,16 @@ void print_text(u32 color, f64 x0, f64 y0, f64 scale_x, f64 scale_y, i64 num_cha } } -void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { +void put_pixel(i64 i, i64 j, u32 op, u32 color) { + if (i < 0 || i >= platform.frame_width || j < 0 || j >= platform.frame_height) + return; + if (op == OP_XOR) + platform.pixels[j * platform.frame_width + i] ^= color; + else + platform.pixels[j * platform.frame_width + i] = color; +} + +void fill_rectangle(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { i64 i0 = (i64) floor(x0 + .5); i64 j0 = (i64) floor(y0 + .5); i64 i1 = (i64) floor(x0 + width + .5); @@ -269,10 +313,95 @@ void draw_panel(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { for (i64 j = j0; j < j1; ++j) for (i64 i = i0; i < i1; ++i) - if (op == OP_XOR) - platform.pixels[j * platform.frame_width + i] ^= color; - else - platform.pixels[j * platform.frame_width + i] = color; + put_pixel(i, j, op, color); +} + +void fill_triangle(u32 op, u32 color, f64 x0, f64 y0, f64 x1, f64 y1, f64 x2, f64 y2) { + i64 min_x = (i64) floor(min3(x0, x1, x2)); + i64 min_y = (i64) floor(min3(y0, y1, y2)); + i64 max_x = (i64) ceil (max3(x0, x1, x2)); + i64 max_y = (i64) ceil (max3(y0, y1, y2)); + + // Z-components of cross-products + // + f64 z0 = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0); + f64 z1 = (x2 - x1) * (y0 - y1) - (x0 - x1) * (y2 - y1); + f64 z2 = (x0 - x2) * (y1 - y2) - (x1 - x2) * (y0 - y2); + + for (i64 j = min_y; j <= max_y; ++j) + for (i64 i = min_x; i <= max_x; ++i) { + f64 x = (f64) i; + f64 y = (f64) j; + + // Z-components of cross-products + // + f64 pz0 = (x - x0) * (y2 - y0) - (x2 - x0) * (y - y0); + f64 pz1 = (x - x1) * (y0 - y1) - (x0 - x1) * (y - y1); + f64 pz2 = (x - x2) * (y1 - y2) - (x1 - x2) * (y - y2); + + // Check signs + // + if (!same_sign(z0, pz0)) continue; + if (!same_sign(z1, pz1)) continue; + if (!same_sign(z2, pz2)) continue; + + put_pixel(i, j, op, color); + } +} + +void fill_ellipse(u32 op, u32 color, f64 x0, f64 y0, f64 width, f64 height) { + // FIXME PERF: + // Implement better algorithm. + + i64 i0 = (i64) floor(x0 + .5); + i64 j0 = (i64) floor(y0 + .5); + i64 i1 = (i64) floor(x0 + width + .5); + i64 j1 = (i64) floor(y0 + height + .5); + + f64 dw = width / 2; + f64 dh = height / 2; + + if (dw < EPSILON || dh < EPSILON) + return; + + f64 cx = x0 + dw; + f64 cy = y0 + dh; + f64 kx = 1. / dw; + f64 ky = 1. / dh; + + for (i64 j = j0; j < j1; ++j) { + if (j < 0 || j >= platform.frame_height) + continue; + f64 dy = (((f64) j) - cy) * ky; + f64 dydy = dy * dy; + for (i64 i = i0; i < i1; ++i) { + if (i < 0 || i >= platform.frame_width) + continue; + f64 dx = (((f64) i) - cx) * kx; + if (dx * dx + dydy <= 1.0) + put_pixel(i, j, op, color); + } + } +} + +void fill_line(u32 op, u32 color, f64 x0, f64 y0, f64 x1, f64 y1, f64 width) { + f64 dx = x1 - x0; + f64 dy = y1 - y0; + + // Tangent + // + f64 tx = -dy; + f64 ty = dx; + f64 tl = sqrt(tx * tx + ty * ty); + if (tl >= EPSILON) { + tx /= tl; + ty /= tl; + } + tx *= width * .5; + ty *= width * .5; + + fill_triangle(op, color, x0 - tx, y0 - ty, x0 + tx, y0 + ty, x1 + tx, y1 + ty); + fill_triangle(op, color, x0 - tx, y0 - ty, x1 + tx, y1 + ty, x1 - tx, y1 - ty); } void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 num_chars, c32 *text) { @@ -293,10 +422,10 @@ void draw_text_area(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_sc kx = k * max_scale_x; ky = k * max_scale_y; - print_text(color, x0, y0, kx, ky, num_chars, text); + draw_text(color, x0, y0, kx, ky, num_chars, text); } -void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text) { +void draw_selection_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_scale_x, f64 max_scale_y, i64 cursor, i64 selection, i64 num_chars, c32 *text) { assert(max_scale_x > 1e-6); assert(max_scale_y > 1e-6); @@ -329,27 +458,27 @@ void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_ } if (cursor_y == selection_y) - draw_panel(OP_XOR, color, + fill_rectangle(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), kx * (selection_x - cursor_x), ky * (CHAR_NUM_BITS_Y + 1) ); else { - draw_panel(OP_XOR, color, + fill_rectangle(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * (CHAR_NUM_BITS_Y + 1), kx * (num_columns - cursor_x), ky * (CHAR_NUM_BITS_Y + 1) ); for (i64 j = cursor_y + CHAR_NUM_BITS_Y + 1; j < selection_y; j += CHAR_NUM_BITS_Y + 1) - draw_panel(OP_XOR, color, + fill_rectangle(OP_XOR, color, x0, y0 + ky * j - ky * (CHAR_NUM_BITS_Y + 1), kx * num_columns, ky * (CHAR_NUM_BITS_Y + 1) ); - draw_panel(OP_XOR, color, + fill_rectangle(OP_XOR, color, x0, y0 + ky * selection_y - ky * (CHAR_NUM_BITS_Y + 1), kx * selection_x, @@ -357,7 +486,7 @@ void draw_text_cursor(u32 color, f64 x0, f64 y0, f64 width, f64 height, f64 max_ ); } } else - draw_panel(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * CHAR_NUM_BITS_Y, kx * .5, ky * (CHAR_NUM_BITS_Y - 1)); + fill_rectangle(OP_XOR, color, x0 + kx * cursor_x, y0 + ky * cursor_y - ky * CHAR_NUM_BITS_Y, kx * .5, ky * (CHAR_NUM_BITS_Y - 1)); } #endif // GRAPHICS_IMPL_GUARD_ -- cgit v1.2.3