367 lines
10 KiB
C
367 lines
10 KiB
C
|
#include "private/rendering.h"
|
||
|
#include "context.h"
|
||
|
#include "textures.h"
|
||
|
|
||
|
#include <SDL2/SDL.h>
|
||
|
#include <stb_ds.h>
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <tgmath.h>
|
||
|
|
||
|
|
||
|
void render_queue_clear(void) {
|
||
|
/* since i don't intend to free the queues, */
|
||
|
/* it's faster and simpler to just "start over" */
|
||
|
/* and start overwriting the existing data */
|
||
|
arrsetlen(ctx.render_queue_sprites, 0);
|
||
|
arrsetlen(ctx.render_queue_rectangles, 0);
|
||
|
arrsetlen(ctx.render_queue_circles, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* an implementation note:
|
||
|
* try to avoid doing expensive work in the push functions,
|
||
|
* because they will be called multiple times in the main loop
|
||
|
* before anything is really rendered
|
||
|
*/
|
||
|
|
||
|
/* sprite */
|
||
|
void push_sprite(char *path, t_frect rect) {
|
||
|
textures_load(&ctx.texture_cache, path);
|
||
|
|
||
|
struct sprite_primitive sprite = {
|
||
|
.rect = rect,
|
||
|
.color = (t_color) { 255, 255, 255, 255 },
|
||
|
.path = path,
|
||
|
.rotation = 0.0,
|
||
|
.blend_mode = SDL_BLENDMODE_BLEND,
|
||
|
.atlas_index =
|
||
|
textures_get_atlas_index(&ctx.texture_cache, path),
|
||
|
.layer = 0,
|
||
|
.flip_x = false,
|
||
|
.flip_y = false,
|
||
|
};
|
||
|
|
||
|
arrput(ctx.render_queue_sprites, sprite);
|
||
|
}
|
||
|
|
||
|
|
||
|
void push_sprite_ex(t_frect rect, t_push_sprite_args args) {
|
||
|
textures_load(&ctx.texture_cache, args.path);
|
||
|
|
||
|
struct sprite_primitive sprite = {
|
||
|
.rect = rect,
|
||
|
.color = args.color,
|
||
|
.path = args.path,
|
||
|
.rotation = args.rotation,
|
||
|
.blend_mode = args.blend_mode,
|
||
|
.atlas_index =
|
||
|
textures_get_atlas_index(&ctx.texture_cache, args.path),
|
||
|
.layer = args.layer,
|
||
|
.flip_x = args.flip_x,
|
||
|
.flip_y = args.flip_y,
|
||
|
};
|
||
|
|
||
|
arrput(ctx.render_queue_sprites, sprite);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* rectangle */
|
||
|
void push_rectangle(t_frect rect, t_color color) {
|
||
|
struct rect_primitive rectangle = {
|
||
|
.rect = rect,
|
||
|
.color = color,
|
||
|
};
|
||
|
|
||
|
arrput(ctx.render_queue_rectangles, rectangle);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* circle */
|
||
|
void push_circle(t_fvec2 position, float radius, t_color color) {
|
||
|
struct circle_primitive circle = {
|
||
|
.radius = radius,
|
||
|
.color = color,
|
||
|
.position = position,
|
||
|
};
|
||
|
|
||
|
arrput(ctx.render_queue_circles, circle);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* rendering */
|
||
|
static void render_background(void) {
|
||
|
SDL_SetRenderDrawColor(ctx.renderer, 230, 230, 230, 255);
|
||
|
SDL_Rect rect = {
|
||
|
0,
|
||
|
0,
|
||
|
ctx.window_w,
|
||
|
ctx.window_h
|
||
|
};
|
||
|
SDL_RenderFillRect(ctx.renderer, &rect);
|
||
|
|
||
|
SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* compare functions for the sort in render_sprites */
|
||
|
static int cmp_atlases(const void *a, const void *b) {
|
||
|
int index_a = ((const struct sprite_primitive *)a)->atlas_index;
|
||
|
int index_b = ((const struct sprite_primitive *)b)->atlas_index;
|
||
|
|
||
|
return (index_a > index_b) - (index_a < index_b);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cmp_blend_modes(const void *a, const void *b) {
|
||
|
SDL_BlendMode mode_a = ((const struct sprite_primitive *)a)->blend_mode;
|
||
|
SDL_BlendMode mode_b = ((const struct sprite_primitive *)b)->blend_mode;
|
||
|
|
||
|
return (mode_a > mode_b) - (mode_a < mode_b);
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cmp_colors(const void *a, const void *b) {
|
||
|
t_color color_a = ((const struct sprite_primitive *)a)->color;
|
||
|
t_color color_b = ((const struct sprite_primitive *)b)->color;
|
||
|
|
||
|
/* check reds */
|
||
|
if (color_a.r < color_b.r)
|
||
|
return -1;
|
||
|
else if (color_a.r > color_b.r)
|
||
|
return 1;
|
||
|
|
||
|
/* reds were equal, check greens */
|
||
|
else if (color_a.g < color_b.g)
|
||
|
return -1;
|
||
|
else if (color_a.g > color_b.g)
|
||
|
return 1;
|
||
|
|
||
|
/* greens were equal, check blues */
|
||
|
else if (color_a.b < color_b.b)
|
||
|
return -1;
|
||
|
else if (color_a.b > color_b.b)
|
||
|
return 1;
|
||
|
|
||
|
/* blues were equal, check alphas */
|
||
|
else if (color_a.a < color_b.a)
|
||
|
return -1;
|
||
|
else if (color_a.a > color_b.a)
|
||
|
return 1;
|
||
|
|
||
|
/* entirely equal */
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static int cmp_layers(const void *a, const void *b) {
|
||
|
int layer_a = ((const struct sprite_primitive *)a)->layer;
|
||
|
int layer_b = ((const struct sprite_primitive *)b)->layer;
|
||
|
|
||
|
return (layer_a > layer_b) - (layer_a < layer_b);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* necessary to allow the renderer to draw in batches in the best case */
|
||
|
static void sort_sprites(struct sprite_primitive *sprites) {
|
||
|
size_t sprites_len = arrlenu(sprites);
|
||
|
qsort(sprites, sprites_len, sizeof *sprites, cmp_atlases);
|
||
|
qsort(sprites, sprites_len, sizeof *sprites, cmp_blend_modes);
|
||
|
qsort(sprites, sprites_len, sizeof *sprites, cmp_colors);
|
||
|
qsort(sprites, sprites_len, sizeof *sprites, cmp_layers);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_sprite(struct sprite_primitive *sprite) {
|
||
|
SDL_Rect srcrect_value = { 0 };
|
||
|
SDL_Rect *srcrect = &srcrect_value;
|
||
|
SDL_Texture *texture = NULL;
|
||
|
|
||
|
/* loner */
|
||
|
if (sprite->atlas_index == -1) {
|
||
|
srcrect = NULL;
|
||
|
texture = textures_get_loner(&ctx.texture_cache, sprite->path);
|
||
|
} else {
|
||
|
*srcrect = textures_get_srcrect(&ctx.texture_cache, sprite->path);
|
||
|
texture = textures_get_atlas(&ctx.texture_cache, sprite->atlas_index);
|
||
|
}
|
||
|
|
||
|
SDL_RendererFlip flip = SDL_FLIP_NONE;
|
||
|
if (sprite->flip_x)
|
||
|
flip |= SDL_FLIP_HORIZONTAL;
|
||
|
if (sprite->flip_y)
|
||
|
flip |= SDL_FLIP_VERTICAL;
|
||
|
|
||
|
SDL_SetTextureColorMod(texture,
|
||
|
sprite->color.r,
|
||
|
sprite->color.g,
|
||
|
sprite->color.b);
|
||
|
SDL_SetTextureAlphaMod(texture, sprite->color.a);
|
||
|
SDL_SetTextureBlendMode(texture, sprite->blend_mode);
|
||
|
|
||
|
SDL_Rect rect = {
|
||
|
(int)sprite->rect.x,
|
||
|
(int)sprite->rect.y,
|
||
|
(int)sprite->rect.w,
|
||
|
(int)sprite->rect.h
|
||
|
};
|
||
|
|
||
|
SDL_RenderCopyEx(ctx.renderer,
|
||
|
texture,
|
||
|
srcrect,
|
||
|
&rect,
|
||
|
sprite->rotation,
|
||
|
NULL,
|
||
|
flip);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_rectangle(struct rect_primitive *rectangle) {
|
||
|
SDL_SetRenderDrawColor(ctx.renderer,
|
||
|
rectangle->color.r,
|
||
|
rectangle->color.g,
|
||
|
rectangle->color.b,
|
||
|
rectangle->color.a);
|
||
|
|
||
|
SDL_Rect rect = {
|
||
|
(int)rectangle->rect.x,
|
||
|
(int)rectangle->rect.y,
|
||
|
(int)rectangle->rect.w,
|
||
|
(int)rectangle->rect.h
|
||
|
};
|
||
|
|
||
|
SDL_RenderFillRect(ctx.renderer, &rect);
|
||
|
SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* vertices_out and indices_out MUST BE FREED */
|
||
|
static void create_circle_geometry(t_fvec2 position,
|
||
|
t_color color,
|
||
|
float radius,
|
||
|
size_t num_vertices,
|
||
|
SDL_Vertex **vertices_out,
|
||
|
int **indices_out)
|
||
|
{
|
||
|
SDL_Vertex *vertices = cmalloc(sizeof *vertices * (num_vertices + 1));
|
||
|
int *indices = cmalloc(sizeof *indices * (num_vertices * 3));
|
||
|
|
||
|
/* the angle (in radians) to rotate by on each iteration */
|
||
|
float seg_rotation_angle = (360.0f / (float)num_vertices) * ((float)M_PI / 180);
|
||
|
|
||
|
vertices[0].position.x = (float)position.x;
|
||
|
vertices[0].position.y = (float)position.y;
|
||
|
vertices[0].color.r = color.r;
|
||
|
vertices[0].color.g = color.g;
|
||
|
vertices[0].color.b = color.b;
|
||
|
vertices[0].color.a = color.a;
|
||
|
vertices[0].tex_coord = (SDL_FPoint){ 0, 0 };
|
||
|
|
||
|
/* this point will rotate around the center */
|
||
|
float start_x = 0.0f - radius;
|
||
|
float start_y = 0.0f;
|
||
|
|
||
|
for (size_t i = 1; i < num_vertices + 1; ++i) {
|
||
|
float final_seg_rotation_angle = (float)i * seg_rotation_angle;
|
||
|
|
||
|
vertices[i].position.x =
|
||
|
cos(final_seg_rotation_angle) * start_x -
|
||
|
sin(final_seg_rotation_angle) * start_y;
|
||
|
vertices[i].position.y =
|
||
|
cos(final_seg_rotation_angle) * start_y +
|
||
|
sin(final_seg_rotation_angle) * start_x;
|
||
|
|
||
|
vertices[i].position.x += position.x;
|
||
|
vertices[i].position.y += position.y;
|
||
|
|
||
|
vertices[i].color.r = color.r;
|
||
|
vertices[i].color.g = color.g;
|
||
|
vertices[i].color.b = color.b;
|
||
|
vertices[i].color.a = color.a;
|
||
|
|
||
|
vertices[i].tex_coord = (SDL_FPoint){ 0, 0 };
|
||
|
|
||
|
|
||
|
size_t triangle_offset = 3 * (i - 1);
|
||
|
|
||
|
/* center point index */
|
||
|
indices[triangle_offset] = 0;
|
||
|
/* generated point index */
|
||
|
indices[triangle_offset + 1] = (int)i;
|
||
|
|
||
|
size_t index = (i + 1) % num_vertices;
|
||
|
if (index == 0)
|
||
|
index = num_vertices;
|
||
|
indices[triangle_offset + 2] = (int)index;
|
||
|
}
|
||
|
|
||
|
*vertices_out = vertices;
|
||
|
*indices_out = indices;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_circle(struct circle_primitive *circle) {
|
||
|
SDL_Vertex *vertices = NULL;
|
||
|
int *indices = NULL;
|
||
|
int num_vertices = (int)circle->radius;
|
||
|
|
||
|
create_circle_geometry(circle->position,
|
||
|
circle->color,
|
||
|
circle->radius,
|
||
|
num_vertices,
|
||
|
&vertices,
|
||
|
&indices);
|
||
|
|
||
|
SDL_RenderGeometry(ctx.renderer, NULL,
|
||
|
vertices,
|
||
|
num_vertices + 1, /* vertices + center vertex */
|
||
|
indices,
|
||
|
num_vertices * 3);
|
||
|
|
||
|
free(vertices);
|
||
|
free(indices);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_sprites(void) {
|
||
|
if (ctx.texture_cache.is_dirty)
|
||
|
textures_update_current_atlas(&ctx.texture_cache);
|
||
|
|
||
|
sort_sprites(ctx.render_queue_sprites);
|
||
|
|
||
|
for (size_t i = 0; i < arrlenu(ctx.render_queue_sprites); ++i) {
|
||
|
render_sprite(&ctx.render_queue_sprites[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_rectangles(void) {
|
||
|
for (size_t i = 0; i < arrlenu(ctx.render_queue_rectangles); ++i) {
|
||
|
render_rectangle(&ctx.render_queue_rectangles[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static void render_circles(void) {
|
||
|
for (size_t i = 0; i < arrlenu(ctx.render_queue_circles); ++i) {
|
||
|
render_circle(&ctx.render_queue_circles[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void render(void) {
|
||
|
SDL_SetRenderDrawBlendMode(ctx.renderer, SDL_BLENDMODE_BLEND);
|
||
|
SDL_SetRenderDrawColor(ctx.renderer, 0, 0, 0, 255);
|
||
|
SDL_RenderClear(ctx.renderer);
|
||
|
SDL_SetRenderDrawColor(ctx.renderer, 255, 255, 255, 255);
|
||
|
|
||
|
render_background();
|
||
|
render_sprites();
|
||
|
render_rectangles();
|
||
|
render_circles();
|
||
|
|
||
|
SDL_RenderPresent(ctx.renderer);
|
||
|
}
|