src/2d.c

changeset 173
bd57fe3f6360
parent 169
6e6717d9c776
--- 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;
+}

mercurial