Wed, 18 Jun 2025 23:55:08 +0200
add combination of filled rectangle with a border
shader/rectangle_frag.glsl | file | annotate | diff | comparison | revisions | |
src/2d.c | file | annotate | diff | comparison | revisions | |
src/ascension/2d.h | file | annotate | diff | comparison | revisions | |
src/ascension/constants.h | file | annotate | diff | comparison | revisions | |
src/ascension/datatypes.h | file | annotate | diff | comparison | revisions | |
test/snake/snake.c | file | annotate | diff | comparison | revisions |
--- a/shader/rectangle_frag.glsl Tue Jun 17 20:11:53 2025 +0200 +++ b/shader/rectangle_frag.glsl Wed Jun 18 23:55:08 2025 +0200 @@ -1,10 +1,13 @@ layout(location = 0) out vec4 diffuse; in vec2 uvcoord; +uniform vec2 size; +#ifdef FILL uniform vec4 color; -uniform vec2 size; -#ifndef FILL +#endif +#ifdef BORDER uniform float thickness; +uniform vec4 border_color; #endif #ifdef ROUNDED_CORNERS uniform float radius; @@ -50,7 +53,7 @@ } } - #ifdef FILL + #ifndef BORDER // For filled rectangle with rounded corners if (in_corner_region) { float dist = length(corner_distances[corner_idx]); @@ -58,39 +61,57 @@ discard; // Outside the rounded corner } } else if (uvcoord.x < 0.0 || uvcoord.y < 0.0 || uvcoord.x > size.x || uvcoord.y > size.y) { + // TODO: at the moment we don't have fragments here, but we will with glow discard; // Outside the rectangle } diffuse = color; - #else // no FILL + #else // BORDER // For outlined rectangle with rounded corners if (in_corner_region) { float dist = length(corner_distances[corner_idx]); if (dist > radius) { discard; // Outside the rounded corner } else if (dist < radius - thickness) { - discard; // Inside the outline + // Inside the outline + #ifdef FILL + diffuse = color; + #else + discard; + #endif + } else { + // the corner outline + diffuse = border_color; } - diffuse = color; } else if (any(lessThan(uvcoord, vec2(thickness))) || any(greaterThan(uvcoord, size - thickness))) { // On a straight edge if (uvcoord.x >= 0.0 && uvcoord.y >= 0.0 && uvcoord.x <= size.x && uvcoord.y <= size.y) { - diffuse = color; + diffuse = border_color; } else { discard; } } else { - discard; // Inside the outline + // Inside the outline + #ifdef FILL + diffuse = color; + #else + discard; + #endif } - #endif // FILL + #endif // BORDER #else // no ROUNDED_CORNERS - #ifdef FILL + #ifdef BORDER + if (any(notEqual(1.0-step(thickness, uvcoord)+step(size-thickness, uvcoord), vec2(0.0)))) { + diffuse = border_color; + } else { + // Inside the outline + #ifdef FILL + diffuse = color; + #else + discard; + #endif + } + #else // no BORDER diffuse = color; - #else // no FILL - if (any(notEqual(1.0-step(thickness, uvcoord)+step(size-thickness, uvcoord), vec2(0.0)))) { - diffuse = color; - } else { - discard; - } - #endif // FILL + #endif // BORDER #endif // ROUNDED_CORNERS }
--- a/src/2d.c Tue Jun 17 20:11:53 2025 +0200 +++ b/src/2d.c Wed Jun 18 23:55:08 2025 +0200 @@ -37,21 +37,28 @@ typedef struct asc_rectangle_shader_s { AscShaderProgram program; GLint color; + GLint border_color; GLint size; GLint thickness; GLint radius; } AscRectangleShader; -#define ASC_RECTANGLE_SHADER_FLAG_FILL 1 -#define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 +#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; + // TODO: create a utility that rolls out those defines const char * const defines[] = { - "", + NULL, "#define FILL", - "#define ROUNDED_CORNERS", + "#define BORDER\n#define ROUNDED_CORNERS", "#define FILL\n#define ROUNDED_CORNERS", + "#define BORDER", + "#define BORDER\n#define FILL", + "#define BORDER\n#define ROUNDED_CORNERS", + "#define BORDER\n#define FILL\n#define ROUNDED_CORNERS", }; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", @@ -66,12 +73,18 @@ asc_shader_free_codes(codes); return NULL; } - shader->color = glGetUniformLocation(shader->program.gl_id, "color"); shader->size = glGetUniformLocation(shader->program.gl_id, "size"); if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { - shader->thickness = -1; + shader->color = glGetUniformLocation(shader->program.gl_id, "color"); } else { + shader->color = -1; + } + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_BORDER)) { shader->thickness = glGetUniformLocation(shader->program.gl_id, "thickness"); + shader->border_color = glGetUniformLocation(shader->program.gl_id, "border_color"); + } else { + shader->thickness = -1; + shader->border_color = -1; } if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { shader->radius = glGetUniformLocation(shader->program.gl_id, "radius"); @@ -98,20 +111,26 @@ static void asc_rectangle_draw(const AscCamera *camera, const AscSceneNode *node) { asc_ptr_cast(AscRectangle, rectangle, node); - const bool filled = asc_test_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + 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; // Compute shader ID const int shader_ids[] = { - ASC_SHADER_RECTANGLE_DRAW, + -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 @@ -124,16 +143,24 @@ glUniformMatrix4fv(shader->program.model, 1, GL_FALSE, node->world_transform); - glUniform4f(shader->color, - rectangle->color.red, - rectangle->color.green, - rectangle->color.blue, - rectangle->color.alpha - ); + if (filled) { + glUniform4f(shader->color, + rectangle->color.red, + rectangle->color.green, + rectangle->color.blue, + rectangle->color.alpha + ); + } glUniform2f(shader->size, rectangle->width, rectangle->height); - if (!filled) { + if (border) { glUniform1f(shader->thickness, rectangle->thickness); + glUniform4f(shader->border_color, + rectangle->border_color.red, + rectangle->border_color.green, + rectangle->border_color.blue, + rectangle->border_color.alpha + ); } if (round) { glUniform1f(shader->radius, rectangle->radius); @@ -158,12 +185,21 @@ rectangle->height = (float) args.height; } - rectangle->thickness = ASC_NONZERO_OR(1.f, args.thickness); rectangle->radius = (float)args.radius; rectangle->color = asc_col_itof(asc_context.ink); - - if (args.filled) { - asc_set_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + 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;
--- a/src/ascension/2d.h Tue Jun 17 20:11:53 2025 +0200 +++ b/src/ascension/2d.h Wed Jun 18 23:55:08 2025 +0200 @@ -31,17 +31,16 @@ #include "scene_node.h" #include "mesh.h" -#define ASC_RECTANGLE_FILLED 1 - typedef struct asc_rectangle_s { AscSceneNode node; AscMesh mesh; asc_col4f color; + asc_col4f border_color; float width; float height; float radius; float thickness; - int flags; + bool filled; } AscRectangle; struct asc_rectangle_create_args { @@ -58,6 +57,15 @@ * Border thickness */ unsigned int thickness; + /** + * Border color to be used when thickness is larger zero and filled is true. + */ + asc_col4i border_color; + /** + * If true, the rectangle will be filled with the active color. + * If thickness is larger zero, an outline border with that thickness is drawn + * using the border_color. + */ bool filled; };
--- a/src/ascension/constants.h Tue Jun 17 20:11:53 2025 +0200 +++ b/src/ascension/constants.h Wed Jun 18 23:55:08 2025 +0200 @@ -32,14 +32,16 @@ // Internally used shader IDs. // -------------------------------------- -#define ASC_SHADER_INTERNAL_ID(id) (1000000000u+id) +#define ASC_SHADER_INTERNAL_ID(id) (1000000000u+id) -#define ASC_SHADER_SPRITE_RECT ASC_SHADER_INTERNAL_ID(1) -#define ASC_SHADER_SPRITE_UV ASC_SHADER_INTERNAL_ID(2) -#define ASC_SHADER_RECTANGLE_DRAW ASC_SHADER_INTERNAL_ID(3) -#define ASC_SHADER_RECTANGLE_FILL ASC_SHADER_INTERNAL_ID(4) -#define ASC_SHADER_RECTANGLE_DRAW_ROUND ASC_SHADER_INTERNAL_ID(5) -#define ASC_SHADER_RECTANGLE_FILL_ROUND ASC_SHADER_INTERNAL_ID(6) +#define ASC_SHADER_SPRITE_RECT ASC_SHADER_INTERNAL_ID(1) +#define ASC_SHADER_SPRITE_UV ASC_SHADER_INTERNAL_ID(2) +#define ASC_SHADER_RECTANGLE_DRAW ASC_SHADER_INTERNAL_ID(3) +#define ASC_SHADER_RECTANGLE_FILL ASC_SHADER_INTERNAL_ID(4) +#define ASC_SHADER_RECTANGLE_DRAW_ROUND ASC_SHADER_INTERNAL_ID(5) +#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) #endif // ASC_CONSTANTS_H
--- a/src/ascension/datatypes.h Tue Jun 17 20:11:53 2025 +0200 +++ b/src/ascension/datatypes.h Wed Jun 18 23:55:08 2025 +0200 @@ -119,6 +119,8 @@ } asc_col4f; #define asc_col4f_new(r, g, b, a) (asc_col4f){(float)r, (float)g, (float)b, (float)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/test/snake/snake.c Tue Jun 17 20:11:53 2025 +0200 +++ b/test/snake/snake.c Wed Jun 18 23:55:08 2025 +0200 @@ -200,7 +200,7 @@ 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) + .thickness = 4, .radius = 15, .filled = true, .border_color = asc_col4i_new(0, 255, 0, 255)) ); // Main Loop