diff -r 8178eee19656 -r bd57fe3f6360 src/2d.c --- a/src/2d.c Tue Jul 01 00:00:50 2025 +0200 +++ b/src/2d.c Tue Jul 01 20:28:49 2025 +0200 @@ -198,3 +198,157 @@ 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; +}