Tue, 01 Jul 2025 20:28:49 +0200
add ellipsis shader - resolves #385
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/shader/ellipsis_frag.glsl Tue Jul 01 20:28:49 2025 +0200 @@ -0,0 +1,60 @@ + +layout(location = 0) out vec4 diffuse; +in vec2 uvcoord; + +uniform vec2 radii; +#ifdef FILL +uniform vec4 color; +#endif +#ifdef BORDER +uniform float thickness; +uniform vec4 border_color; +#endif + +void main(void) { + // Calculate center of the ellipse + vec2 center = radii; + + // Calculate position relative to center + vec2 pos = uvcoord - center; + + // For an ellipse, we need to calculate normalized distance based on the formula: + // (x/a)² + (y/b)² = 1 where a and b are the semi-axes + vec2 normalized = pos / center; + + // Calculate squared distance in ellipse space + // Value = 1.0 exactly at the ellipse boundary + float dist_squared = dot(normalized, normalized); + + #ifndef BORDER + // For filled ellipse + if (dist_squared > 1.0) { + discard; // Outside the ellipse + } + diffuse = color; + #else // BORDER + // For outlined ellipse + // Calculate inner border threshold based on thickness + // We need to determine the inner ellipse boundary + float border_ratio = thickness / min(center.x, center.y); + + // The inner ellipse has a smaller radius but follows the same equation + // We need to calculate what normalized value corresponds to the inner ellipse + float inner_threshold = 1.0 - border_ratio; + inner_threshold = inner_threshold * inner_threshold; // Square it because we're comparing to dist_squared + + if (dist_squared > 1.0) { + discard; // Outside the ellipse + } else if (dist_squared > inner_threshold) { + // In the border region + diffuse = border_color; + } else { + // Inside the outline + #ifdef FILL + diffuse = color; + #else + discard; + #endif + } + #endif // BORDER +} \ No newline at end of file
--- 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; +}
--- a/src/ascension/2d.h Tue Jul 01 00:00:50 2025 +0200 +++ b/src/ascension/2d.h Tue Jul 01 20:28:49 2025 +0200 @@ -72,12 +72,47 @@ #define asc_rectangle(...) asc_rectangle_create((struct asc_rectangle_create_args) { __VA_ARGS__ }) +typedef struct asc_ellipsis_s { + AscSceneNode node; + AscMesh mesh; + asc_col4f color; + asc_col4f border_color; + asc_vec2f radii; + float thickness; + bool filled; +} AscEllipsis; struct asc_ellipsis_create_args { + /** + * The bounds of the ellipsis. + * Preferred over all other settings. + * When you specify bounds, you cannot specify a center and radiuses. + */ + asc_recti bounds; + /** + * The center point of the ellipsis. + * Preferred over x and y. + */ asc_vec2i center; + /** + * The x coordinate of the center, if center is not specified. + */ int x; + /** + * The y coordinate of the center, if center is not specified. + */ int y; + /** + * The radius in both directions (use for circles). + */ + unsigned int radius; + /** + * The radius in x-direction. + */ unsigned int radius_x; + /** + * The radius in y-direction. + */ unsigned int radius_y; /** * Border thickness
--- a/src/ascension/constants.h Tue Jul 01 00:00:50 2025 +0200 +++ b/src/ascension/constants.h Tue Jul 01 20:28:49 2025 +0200 @@ -42,6 +42,9 @@ #define ASC_SHADER_RECTANGLE_FILL_ROUND ASC_SHADER_INTERNAL_ID(6) #define ASC_SHADER_RECTANGLE_FILL_BORDER ASC_SHADER_INTERNAL_ID(7) #define ASC_SHADER_RECTANGLE_FILL_BORDER_ROUND ASC_SHADER_INTERNAL_ID(8) +#define ASC_SHADER_ELLIPSIS_DRAW ASC_SHADER_INTERNAL_ID(10) +#define ASC_SHADER_ELLIPSIS_FILL ASC_SHADER_INTERNAL_ID(11) +#define ASC_SHADER_ELLIPSIS_FILL_BORDER ASC_SHADER_INTERNAL_ID(12) #endif // ASC_CONSTANTS_H
--- a/src/ascension/datatypes.h Tue Jul 01 00:00:50 2025 +0200 +++ b/src/ascension/datatypes.h Tue Jul 01 20:28:49 2025 +0200 @@ -120,6 +120,9 @@ } asc_col4f; #define asc_col4f_new(r, g, b, a) (asc_col4f){(float)r, (float)g, (float)b, (float)a} +#define asc_rgb(r,g,b) asc_col4i_new(r,g,b,255) +#define asc_rgba(r,g,b,a) asc_col4i_new(r,g,b,a) + #define asc_col4_test_zero(v) (v.red == 0 && v.green == 0 && v.blue == 0 && v.alpha == 0) typedef float asc_mat4f[16];
--- a/src/ascension/transform.h Tue Jul 01 00:00:50 2025 +0200 +++ b/src/ascension/transform.h Tue Jul 01 20:28:49 2025 +0200 @@ -114,7 +114,7 @@ __attribute__((__unused__)) asc_transform transform, __attribute__((__unused__)) asc_vec2f vec ) { - // TODO: implement + // TODO: implement - and think about the point of origin! } ASC_TRANFORM_FUNC void asc_transform_from_vec2f(
--- a/test/snake/snake.c Tue Jul 01 00:00:50 2025 +0200 +++ b/test/snake/snake.c Tue Jul 01 20:28:49 2025 +0200 @@ -194,41 +194,6 @@ // create spaceship create_spaceship(); - // TODO: play around with the test rectangles - asc_ink_rgb(255, 0, 0); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 200, .y = 250, .width = 100, .height = 75, - .thickness = 4, .radius = 15, .filled = true, .border_color = asc_col4i_new(0, 255, 0, 255)) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 50, .y = 150, .width = 100, .height = 75, - .thickness = 4, .radius = 15, .filled = false, .border_color = asc_col4i_new(0, 255, 0, 255)) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 50, .y = 25, .width = 100, .height = 75, - .thickness = 4, .filled = false, .border_color = asc_col4i_new(0, 255, 0, 255)) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 250, .y = 25, .width = 100, .height = 75, - .thickness = 4, .filled = true, .border_color = asc_col4i_new(0, 255, 0, 255)) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 350, .y = 250, .width = 100, .height = 75, - .radius = 15, .filled = true) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 350, .y = 50, .width = 100, .height = 75, - .filled = true) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 50, .y = 350, .width = 100, .height = 75, - .radius = 15) - ); - asc_scene_add_node(MAIN_SCENE, - asc_rectangle(.x = 350, .y = 350, .width = 100, .height = 75) - ); - - // Main Loop do { // quit application on any error