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