#include "twn_rendering_c.h" #include "twn_rendering.h" #include "twn_engine_context_c.h" #include "twn_camera.h" #include #include #ifdef EMSCRIPTEN #include #else #include #endif #include #include /* TODO: have a default initialized one */ Matrix4 camera_projection_matrix; Matrix4 camera_look_at_matrix; void render_queue_clear(void) { text_cache_reset_arena(&ctx.text_cache); /* 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_2d, 0); for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0); } /* rectangle */ void push_rectangle(Rect rect, Color color) { RectPrimitive rectangle = { .rect = rect, .color = color, }; Primitive2D primitive = { .type = PRIMITIVE_2D_RECT, .rect = rectangle, }; arrput(ctx.render_queue_2d, primitive); } void push_9slice(char *texture_path, int texture_w, int texture_h, int border_thickness, Rect rect, Color color) { const float bt = (float)border_thickness; /* i know! */ const float bt2 = bt * 2; /* combined size of the two borders in an axis */ Rect top_left = { .x = rect.x, .y = rect.y, .w = bt, .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, top_left), m_opt(texture_region, ((Rect) { 0, 0, bt, bt })), m_opt(color, color), ); Rect top_center = { .x = rect.x + bt, .y = rect.y, .w = rect.w - bt2, /* here bt2 represents the top left and right corners */ .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, top_center), m_opt(texture_region, ((Rect) { bt, 0, (float)texture_w - bt2, bt })), m_opt(color, color), ); Rect top_right = { .x = rect.x + (rect.w - bt), .y = rect.y, .w = bt, .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, top_right), m_opt(texture_region, ((Rect) { (float)texture_w - bt, 0, bt, bt })), m_opt(color, color), ); Rect center_left = { .x = rect.x, .y = rect.y + bt, .w = bt, .h = rect.h - bt2, /* here bt2 represents the top and bottom left corners */ }; m_sprite( m_set(path, texture_path), m_set(rect, center_left), m_opt(texture_region, ((Rect) { 0, bt, bt, (float)texture_h - bt2 })), m_opt(color, color), ); Rect center_right = { .x = rect.x + (rect.w - bt), .y = rect.y + bt, .w = bt, .h = rect.h - bt2, /* here bt2 represents the top and bottom right corners */ }; m_sprite( m_set(path, texture_path), m_set(rect, center_right), m_opt(texture_region, ((Rect) { (float)texture_w - bt, bt, bt, (float)texture_h - bt2 })), m_opt(color, color), ); Rect bottom_left = { .x = rect.x, .y = rect.y + (rect.h - bt), .w = bt, .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, bottom_left), m_opt(texture_region, ((Rect) { 0, (float)texture_h - bt, bt, bt })), m_opt(color, color), ); Rect bottom_center = { .x = rect.x + bt, .y = rect.y + (rect.h - bt), .w = rect.w - bt2, /* here bt2 represents the bottom left and right corners */ .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, bottom_center), m_opt(texture_region, ((Rect) { bt, (float)texture_h - bt, (float)texture_w - bt2, bt })), m_opt(color, color), ); Rect bottom_right = { .x = rect.x + (rect.w - bt), .y = rect.y + (rect.h - bt), .w = bt, .h = bt, }; m_sprite( m_set(path, texture_path), m_set(rect, bottom_right), m_opt(texture_region, ((Rect) { (float)texture_w - bt, (float)texture_h - bt, bt, bt })), m_opt(color, color), ); Rect center = { .x = rect.x + bt, .y = rect.y + bt, .w = rect.w - bt2, .h = rect.h - bt2, }; m_sprite( m_set(path, texture_path), m_set(rect, center), m_opt(texture_region, ((Rect) { bt, bt, (float)texture_w - bt2, (float)texture_h - bt2 })), m_opt(color, color), ); } static void render_2d(void) { use_2d_pipeline(); const size_t render_queue_len = arrlenu(ctx.render_queue_2d); size_t batch_count = 0; for (size_t i = 0; i < render_queue_len; ++i) { const Primitive2D *current = &ctx.render_queue_2d[i]; switch (current->type) { case PRIMITIVE_2D_SPRITE: { const struct SpriteBatch batch = collect_sprite_batch(current, render_queue_len - i); /* TODO: what's even the point? just use OR_EQUAL comparison */ set_depth_range((double)batch_count / UINT16_MAX, 1.0); render_sprites(current, batch); i += batch.size - 1; ++batch_count; break; } case PRIMITIVE_2D_RECT: render_rectangle(¤t->rect); break; case PRIMITIVE_2D_CIRCLE: render_circle(¤t->circle); break; case PRIMITIVE_2D_TEXT: render_text(¤t->text); break; } } } static void render_space(void) { /* nothing to do, abort */ /* as space pipeline isn't used we can have fewer changes and initialization costs */ if (hmlenu(ctx.uncolored_mesh_batches) == 0) return; use_space_pipeline(); apply_fog(); for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) { draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value, ctx.uncolored_mesh_batches[i].key); } pop_fog(); } void render(void) { textures_update_atlas(&ctx.texture_cache); /* fit rendering context onto the resizable screen */ if (ctx.game.window_size_has_changed) { if ((float)ctx.game.window_w / (float)ctx.game.window_h > (float)(ctx.base_render_width / ctx.base_render_height)) { float ratio = (float)ctx.game.window_h / (float)ctx.base_render_height; int w = (int)((float)ctx.base_render_width * ratio); setup_viewport( ctx.game.window_w / 2 - w / 2, 0, w, ctx.game.window_h ); } else { float ratio = (float)ctx.game.window_w / (float)ctx.base_render_width; int h = (int)((float)ctx.base_render_height * ratio); setup_viewport( 0, ctx.game.window_h / 2 - h / 2, ctx.game.window_w, h ); } } clear_draw_buffer(); render_space(); render_skybox(); /* after space, as to use depth buffer for early rejection */ render_2d(); swap_buffers(); } void set_camera(const Camera *const camera) { /* TODO: skip recaulculating if it's the same? */ camera_projection_matrix = camera_perspective(camera); camera_look_at_matrix = camera_look_at(camera); }