src/drawing.c
  1#include <math.h>
  2#include <stdio.h>
  3#include <stdlib.h>
  4
  5#include "alba.h"
  6
  7#define PI 3.14159265358979323846
  8#define MAX_ERROR 0.25
  9
 10void draw_triangles_indexed(
 11    AlbaWindow* window,
 12    const uint32_t num_vertices,
 13    const float* vertices,
 14    const float* attributes,
 15    const uint32_t num_indices,
 16    uint32_t* indices
 17)
 18{
 19    const uint32_t offset = window->drawing.new_vertices.length / 2;
 20
 21    dynarray_extend(&window->drawing.new_vertices, num_vertices * 2, vertices);
 22    dynarray_extend(&window->drawing.new_attributes, num_vertices * 4, attributes);
 23
 24    if (offset > 0)
 25    {
 26        // Fix indices
 27        for (uint32_t i = 0; i < num_indices; i++)
 28        {
 29            indices[i] += offset;
 30        }
 31    }
 32    dynarray_extend(&window->drawing.new_indices, num_indices, indices);
 33
 34    window->drawing.dirty = 1;
 35}
 36
 37void draw_triangles(
 38    AlbaWindow* window,
 39    const uint32_t num_vertices,
 40    const float* vertices,
 41    const float* attributes
 42)
 43{
 44    uint32_t* indices = malloc(num_vertices * sizeof(uint32_t));
 45    if (indices == NULL)
 46    {
 47        printf(
 48            "error: unable to allocate %lu bytes for triangle indices\n",
 49            num_vertices * sizeof(uint32_t)
 50        );
 51        return;
 52    }
 53
 54    for (uint32_t i = 0; i < num_vertices; i++)
 55    {
 56        indices[i] = i;
 57    }
 58
 59    draw_triangles_indexed(window, num_vertices, vertices, attributes, num_vertices, indices);
 60
 61    free(indices);
 62}
 63
 64void draw_triangle(AlbaWindow* window, const float vertices[6], const AlbaColor color)
 65{
 66    const float attributes[] = {
 67        color.r, color.g, color.b, color.a,
 68        color.r, color.g, color.b, color.a,
 69        color.r, color.g, color.b, color.a,
 70    };
 71    // Does not try to consider winding order
 72    uint32_t indices[] = {0, 1, 2};
 73    draw_triangles_indexed(window, 3, vertices, attributes, 3, indices);
 74}
 75
 76void draw_quad(AlbaWindow* window, const float vertices[8], const AlbaColor color)
 77{
 78    const float attributes[] = {
 79        color.r, color.g, color.b, color.a,
 80        color.r, color.g, color.b, color.a,
 81        color.r, color.g, color.b, color.a,
 82        color.r, color.g, color.b, color.a,
 83    };
 84    // Does not try to consider winding order
 85    uint32_t indices[] = {0, 1, 2, 0, 2, 3};
 86    draw_triangles_indexed(window, 4, vertices, attributes, 6, indices);
 87}
 88
 89void draw_rect_aa(
 90    AlbaWindow* window,
 91    const float x0, const float y0, const float x1, const float y1,
 92    const AlbaColor color
 93)
 94{
 95    if (x0 > x1)
 96    {
 97        printf("error: invalid rect coordinates, x0 > x1: %f > %f\n", x0, x1);
 98        exit(1);
 99    }
100    if (y0 > y1)
101    {
102        printf("error: invalid rect coordinates, y0 > y1: %f > %f\n", y0, y1);
103        exit(1);
104    }
105
106    const float vertices[] = {
107        x0, y0,
108        x0, y1,
109        x1, y1,
110        x1, y0,
111    };
112    draw_quad(window, vertices, color);
113}
114
115void generate_regular_polygon(
116    DynArray* array, const uint32_t num_sides,
117    const float x, const float y, const float r)
118{
119    dynarray_reserve(array, array->length + num_sides * 2);
120
121    const float step_angle = 2 * PI / num_sides;
122    for (uint32_t i = 0; i < num_sides; i++)
123    {
124        dynarray_extend(
125            array, 2,
126            &(float[]){
127                x + r * sin(step_angle * i),
128                y - r * cos(step_angle * i)
129            }
130        );
131    }
132}
133
134void generate_circle(DynArray* array, const float x, const float y, const float r)
135{
136    const uint32_t num_sides = round(PI / acos(1 - MAX_ERROR / r));
137    generate_regular_polygon(array, num_sides, x, y, r);
138}
139
140void generate_rounded_rect_aa(
141    DynArray* array,
142    const float x0, const float y0, const float x1, const float y1,
143    float r
144)
145{
146    // Always a multiple of 4
147    const uint32_t num_sides = round((PI / acos(1 - MAX_ERROR / r)) / 4) * 4;
148    const uint32_t num_sides_per_corner = num_sides / 4;
149
150    const float step_angle = 2 * PI / num_sides;
151
152    if (x1 - x0 < r) r = (x1 - x0) / 2;
153    if (y1 - y0 < r) r = (y1 - y0) / 2;
154
155    dynarray_reserve(array, array->length + 4 + num_sides);
156
157    uint32_t i = 0;
158    for (; i < num_sides_per_corner + 1; i++)
159    {
160        dynarray_extend(
161            array, 2,
162            &(float[]){
163                x0 + r - r * sin(step_angle * i),
164                y0 + r - r * cos(step_angle * i)
165            });
166    }
167    i -= 1;
168
169    for (; i < 2 * num_sides_per_corner + 1; i++)
170    {
171        dynarray_extend(
172            array, 2,
173            &(float[]){
174                x0 + r - r * sin(step_angle * i),
175                y1 - r - r * cos(step_angle * i)
176            });
177    }
178    i -= 1;
179
180    for (; i < 3 * num_sides_per_corner + 1; i++)
181    {
182        dynarray_extend(
183            array, 2,
184            &(float[]){
185                x1 - r - r * sin(step_angle * i),
186                y1 - r - r * cos(step_angle * i)
187            });
188    }
189    i -= 1;
190
191    for (; i < 4 * num_sides_per_corner + 1; i++)
192    {
193        dynarray_extend(
194            array, 2,
195            &(float[]){
196                x1 - r - r * sin(step_angle * i),
197                y0 + r - r * cos(step_angle * i)
198            });
199    }
200}
201
202void draw_concave_polygon(AlbaWindow* window, const float x, const float y, DynArray* contour, AlbaColor color)
203{
204    const uint32_t num_sides = contour->length / 2;
205    const uint32_t num_vertices = num_sides + 1;
206
207    dynarray_append(contour, &x);
208    dynarray_append(contour, &y);
209
210    DynArray colors = dynarray_new(sizeof(uint32_t), num_vertices * 4);
211    for (uint32_t i = 0; i < num_vertices; i++)
212    {
213        dynarray_extend(&colors, 4, (float*)&color);
214    }
215
216    uint32_t* indices = malloc(num_sides * 3 * sizeof(uint32_t));
217    for (uint32_t i = 0; i < num_sides; i++)
218    {
219        indices[i * 3] = num_vertices - 1;
220        indices[i * 3 + 1] = i;
221        indices[i * 3 + 2] = i + 1;
222    }
223    indices[num_sides * 3 - 1] = 0;
224
225    draw_triangles_indexed(window, num_sides + 1, contour->data, colors.data, num_sides * 3, indices);
226
227    free(indices);
228}
229
230void draw_regular_polygon(
231    AlbaWindow* window, const uint32_t num_sides,
232    const float x, const float y, const float r,
233    const AlbaColor color
234)
235{
236    if (num_sides <= 2)
237    {
238        printf("error: invalid number of sides in call to draw_regular_polygon: %d. "
239               "A polygon must have at least 3 sides\n", num_sides);
240        exit(1);
241    }
242
243    DynArray polyon = dynarray_new(sizeof(float), 0);
244    generate_regular_polygon(&polyon, num_sides, x, y, r);
245    draw_concave_polygon(window, x, y, &polyon, color);
246    dynarray_release(&polyon);
247}
248
249void draw_circle(AlbaWindow* window, const float x, const float y, const float r, const AlbaColor color)
250{
251    DynArray circle = dynarray_new(sizeof(float), 0);
252    generate_circle(&circle, x, y, r);
253    draw_concave_polygon(window, x, y, &circle, color);
254    dynarray_release(&circle);
255}
256
257
258void draw_rounded_rect_aa(
259    AlbaWindow* window,
260    const float x0, const float y0, const float x1, const float y1,
261    const float r, const AlbaColor color)
262{
263    if (r == 0)
264    {
265        draw_rect_aa(window, x0, y0, x1, y1, color);
266        return;
267    }
268
269    DynArray contour = dynarray_new(sizeof(float), 0);
270    generate_rounded_rect_aa(&contour, x0, y0, x1, y1, r);
271    draw_concave_polygon(window, (x0 + x1) / 2, (y0 + y1) / 2, &contour, color);
272}