Wed, 02 Jul 2025 23:21:17 +0200
resolve TODOs regarding input.h
a) mouse position must be integer, because it can be negative (though rarely)
b) we should not trade "access complexity" for space in the scancodes array
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * Copyright 2025 Mike Becker. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "ascension/2d.h" #include "ascension/constants.h" #include "ascension/context.h" #include "ascension/error.h" #include "ascension/shader.h" #include <assert.h> typedef struct asc_rectangle_shader_s { AscShaderProgram program; asc_uniform_loc color; asc_uniform_loc border_color; asc_uniform_loc size; asc_uniform_loc thickness; asc_uniform_loc radius; } AscRectangleShader; #define ASC_RECTANGLE_SHADER_FLAG_FILL 1 #define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 #define ASC_RECTANGLE_SHADER_FLAG_BORDER 4 static AscShaderProgram *asc_rectangle_shader_create(int flags) { AscShaderCodes codes; const char * const defines[] = { "#define FILL\n", "#define ROUNDED_CORNERS\n", "#define BORDER\n" }; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "rectangle_frag.glsl", .defines.frag_list = defines, .defines.frag_list_select = flags }, &codes)) { asc_error("Loading rectangle shader failed."); return NULL; } AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscRectangleShader)); if (asc_shader_invalid(shader)) { asc_shader_free_codes(codes); return shader; } asc_ptr_cast(AscRectangleShader, rect_shader, shader); rect_shader->size = asc_shader_get_uniform_loc(shader, "size"); if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { rect_shader->color = asc_shader_get_uniform_loc(shader, "color"); } else { rect_shader->color = -1; } if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_BORDER)) { rect_shader->thickness = asc_shader_get_uniform_loc(shader, "thickness"); rect_shader->border_color = asc_shader_get_uniform_loc(shader, "border_color"); } else { rect_shader->thickness = -1; rect_shader->border_color = -1; } if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { rect_shader->radius = asc_shader_get_uniform_loc(shader, "radius"); } else { rect_shader->radius = -1; } asc_shader_free_codes(codes); asc_error_catch_all_gl(); return shader; } static void asc_rectangle_destroy(AscSceneNode *node) { asc_ptr_cast(AscRectangle, rectangle, node); asc_mesh_destroy(&rectangle->mesh); } static void asc_rectangle_update(AscSceneNode *node) { asc_ptr_cast(AscRectangle, rectangle, node); asc_mesh_plane_2d(&rectangle->mesh, .size = rectangle->size, .uv_scale = rectangle->size); } static void asc_rectangle_draw(const AscCamera *camera, const AscSceneNode *node) { asc_cptr_cast(AscRectangle, rectangle, node); const bool filled = rectangle->filled; const bool round = rectangle->radius > 0; const bool border = rectangle->thickness > 0; // Compute shader flags int shader_flags = 0; if (filled) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_FILL; if (border) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_BORDER; if (round) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_ROUND; // Lookup table for the shader ID const int shader_ids[] = { -1, // unused ASC_SHADER_RECTANGLE_FILL, ASC_SHADER_RECTANGLE_DRAW_ROUND, ASC_SHADER_RECTANGLE_FILL_ROUND, ASC_SHADER_RECTANGLE_DRAW, ASC_SHADER_RECTANGLE_FILL_BORDER, ASC_SHADER_RECTANGLE_DRAW_ROUND, ASC_SHADER_RECTANGLE_FILL_BORDER_ROUND, }; // Look up and activate shader const AscShaderProgram *shader = asc_shader_lookup_or_create( shader_ids[shader_flags], asc_rectangle_shader_create, shader_flags); if (asc_shader_use(shader, camera)) return; asc_cptr_cast(AscRectangleShader, rect_shader, shader); // Upload uniforms asc_shader_upload_model_matrix(shader, node); if (filled) { asc_shader_upload_col4f(rect_shader->color, rectangle->color); } asc_shader_upload_vec2f(rect_shader->size, rectangle->size); if (border) { asc_shader_upload_float(rect_shader->thickness, rectangle->thickness); asc_shader_upload_col4f(rect_shader->border_color, rectangle->border_color); } if (round) { asc_shader_upload_float(rect_shader->radius, rectangle->radius); } // Draw mesh asc_mesh_draw_triangle_strip(&rectangle->mesh); } AscSceneNode *asc_rectangle_create(struct asc_rectangle_create_args args) { AscRectangle *rectangle = cxZallocDefault(sizeof(AscRectangle)); if (args.bounds.size.width + args.bounds.size.height > 0) { rectangle->node.position.x = (float) args.bounds.pos.x; rectangle->node.position.y = (float) args.bounds.pos.y; rectangle->size.width = (float) args.bounds.size.width; rectangle->size.height = (float) args.bounds.size.height; } else { rectangle->node.position.x = (float) args.x; rectangle->node.position.y = (float) args.y; rectangle->size.width = (float) args.width; rectangle->size.height = (float) args.height; } rectangle->radius = (float)args.radius; rectangle->color = asc_col_itof(asc_context.ink); rectangle->border_color = asc_col_itof(args.border_color); rectangle->filled = args.filled; if (!args.filled && args.thickness == 0) { // when we do not fill the rectangle, we need a border rectangle->thickness = 1; } else { rectangle->thickness = args.thickness; } if (!args.filled && asc_col4_test_zero(args.border_color)) { // convenience fallback: // when we are drawing an outline but have no explicit border color, // use the active ink rectangle->border_color = rectangle->color; } AscSceneNode *node = &rectangle->node; node->position.z = ASC_SCENE_2D_DEPTH_OFFSET; node->scale = asc_vec3f_one; node->render_group = asc_context.ink.alpha < 255 ? ASC_RENDER_GROUP_2D_BLEND : ASC_RENDER_GROUP_2D_OPAQUE; node->update_func = asc_rectangle_update; node->destroy_func = asc_rectangle_destroy; node->draw_func = asc_rectangle_draw; asc_node_update(node); return node; } static void asc_ellipsis_destroy(AscSceneNode *node) { asc_ptr_cast(AscEllipsis, ellipsis, node); asc_mesh_destroy(&ellipsis->mesh); } static void asc_ellipsis_update(AscSceneNode *node) { asc_ptr_cast(AscEllipsis, ellipsis, node); asc_vec2f size = asc_vec2f_scale(ellipsis->radii, 2); asc_mesh_plane_2d(&ellipsis->mesh, .size = size, .uv_scale = size); } typedef struct asc_ellipsis_shader_s { AscShaderProgram program; asc_uniform_loc color; asc_uniform_loc border_color; asc_uniform_loc radii; asc_uniform_loc thickness; } AscEllipsisShader; #define ASC_ELLIPSIS_SHADER_FLAG_FILL 1 #define ASC_ELLIPSIS_SHADER_FLAG_BORDER 2 static AscShaderProgram *asc_ellipsis_shader_create(int flags) { AscShaderCodes codes; const char * const defines[] = { "#define FILL\n", "#define BORDER\n" }; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", .files.frag = "ellipsis_frag.glsl", .defines.frag_list = defines, .defines.frag_list_select = flags }, &codes)) { asc_error("Loading ellipsis shader failed."); return NULL; } AscShaderProgram *shader = asc_shader_create(codes, sizeof(AscRectangleShader)); if (asc_shader_invalid(shader)) { asc_shader_free_codes(codes); return shader; } asc_ptr_cast(AscEllipsisShader, ellipsis_shader, shader); ellipsis_shader->radii = asc_shader_get_uniform_loc(shader, "radii"); if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_FILL)) { ellipsis_shader->color = asc_shader_get_uniform_loc(shader, "color"); } else { ellipsis_shader->color = -1; } if (asc_test_flag(flags, ASC_ELLIPSIS_SHADER_FLAG_BORDER)) { ellipsis_shader->thickness = asc_shader_get_uniform_loc(shader, "thickness"); ellipsis_shader->border_color = asc_shader_get_uniform_loc(shader, "border_color"); } else { ellipsis_shader->thickness = -1; ellipsis_shader->border_color = -1; } asc_shader_free_codes(codes); asc_error_catch_all_gl(); return shader; } static void asc_ellipsis_draw(const AscCamera *camera, const AscSceneNode *node) { asc_cptr_cast(AscEllipsis, ellipsis, node); const bool filled = ellipsis->filled; const bool border = ellipsis->thickness > 0; // Compute shader flags int shader_flags = 0; if (filled) shader_flags |= ASC_ELLIPSIS_SHADER_FLAG_FILL; if (border) shader_flags |= ASC_ELLIPSIS_SHADER_FLAG_BORDER; // Lookup table for the shader ID const int shader_ids[] = { -1, // unused ASC_SHADER_ELLIPSIS_FILL, ASC_SHADER_ELLIPSIS_DRAW, ASC_SHADER_ELLIPSIS_FILL_BORDER, }; // Look up and activate shader const AscShaderProgram *shader = asc_shader_lookup_or_create( shader_ids[shader_flags], asc_ellipsis_shader_create, shader_flags); if (asc_shader_use(shader, camera)) return; asc_cptr_cast(AscEllipsisShader, ellipsis_shader, shader); // Upload uniforms asc_shader_upload_model_matrix(shader, node); if (filled) { asc_shader_upload_col4f(ellipsis_shader->color, ellipsis->color); } asc_shader_upload_vec2f(ellipsis_shader->radii, ellipsis->radii); if (border) { asc_shader_upload_float(ellipsis_shader->thickness, ellipsis->thickness); asc_shader_upload_col4f(ellipsis_shader->border_color, ellipsis->border_color); } // Draw mesh asc_mesh_draw_triangle_strip(&ellipsis->mesh); } AscSceneNode *asc_ellipsis_create(struct asc_ellipsis_create_args args) { AscEllipsis *ellipsis = cxZallocDefault(sizeof(AscEllipsis)); if (args.bounds.size.width + args.bounds.size.height > 0) { ellipsis->node.position.x = (float) args.bounds.pos.x; ellipsis->node.position.y = (float) args.bounds.pos.y; ellipsis->radii.x = (float) args.bounds.size.width / 2.f; ellipsis->radii.y = (float) args.bounds.size.height / 2.f; } else { const unsigned cx = ASC_NONZERO_OR(args.x, args.center.x); const unsigned cy = ASC_NONZERO_OR(args.y, args.center.y); const unsigned rx = ASC_NONZERO_OR(args.radius, args.radius_x); const unsigned ry = ASC_NONZERO_OR(args.radius, args.radius_y); ellipsis->node.position.x = (float) (cx-rx); ellipsis->node.position.y = (float) (cy-ry); ellipsis->radii.x = (float) rx; ellipsis->radii.y = (float) ry; } ellipsis->color = asc_col_itof(asc_context.ink); ellipsis->border_color = asc_col_itof(args.border_color); ellipsis->filled = args.filled; if (!args.filled && args.thickness == 0) { // when we do not fill the ellipsis, we need a border ellipsis->thickness = 1; } else { ellipsis->thickness = args.thickness; } if (!args.filled && asc_col4_test_zero(args.border_color)) { // convenience fallback: // when we are drawing an outline but have no explicit border color, // use the active ink ellipsis->border_color = ellipsis->color; } AscSceneNode *node = &ellipsis->node; node->position.z = ASC_SCENE_2D_DEPTH_OFFSET; node->scale = asc_vec3f_one; node->render_group = asc_context.ink.alpha < 255 ? ASC_RENDER_GROUP_2D_BLEND : ASC_RENDER_GROUP_2D_OPAQUE; node->update_func = asc_ellipsis_update; node->destroy_func = asc_ellipsis_destroy; node->draw_func = asc_ellipsis_draw; asc_node_update(node); return node; }