add combination of filled rectangle with a border

Wed, 18 Jun 2025 23:55:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 18 Jun 2025 23:55:08 +0200
changeset 159
da7ebfcdd159
parent 158
f650994ec543
child 160
80700db530ff

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

mercurial