From 522840ca22700d3c2a0d7be487d64fa190bfab0e Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 29 Dec 2024 04:14:22 +0200 Subject: [PATCH] add function for drawing a blurred rectangle --- gui/app.zig | 232 +++++++++++++++++++++++++++++++++++++++++++++------ gui/main.zig | 2 +- 2 files changed, 206 insertions(+), 28 deletions(-) diff --git a/gui/app.zig b/gui/app.zig index e22046c..4137b7f 100644 --- a/gui/app.zig +++ b/gui/app.zig @@ -7,6 +7,9 @@ const UIStack = @import("./ui_stack.zig"); const RectUtils = @import("./rect_utils.zig"); const rl = @import("raylib"); const srcery = @import("./srcery.zig"); +const rlgl_h = @cImport({ + @cInclude("rlgl.h"); +}); const assert = std.debug.assert; @@ -229,13 +232,181 @@ fn createOrGetRenderTexture(maybe_render_texture: *?rl.RenderTexture) !rl.Render return maybe_render_texture.*.?; } -fn drawRectangleRoundedUV(rect: rl.Rectangle, roundness: f32, color: rl.Color) void { - if (roundness == 0) { - rl.drawRectangleRec(rect, color); +// Modified version of `DrawRectangleRounded` where the UV texture coordiantes are consistent and align +fn drawRectangleRoundedUV(rec: rl.Rectangle, roundness: f32, color: rl.Color) void { + assert(roundness < 1); + + if (roundness <= 0 or rec.width <= 1 or rec.height <= 1) { + rl.drawRectangleRec(rec, color); + return; + } + + const radius: f32 = @min(rec.width, rec.height) * roundness / 2; + if (radius <= 0.0) return; + + // Calculate the maximum angle between segments based on the error rate (usually 0.5f) + const smooth_circle_error_rate = 0.5; + const th: f32 = std.math.acos(2 * std.math.pow(f32, 1 - smooth_circle_error_rate / radius, 2) - 1); + var segments: i32 = @intFromFloat(@ceil(2 * std.math.pi / th) / 4.0); + if (segments <= 0) segments = 4; + + const step_length = 90.0 / @as(f32, @floatFromInt(segments)); + + // Quick sketch to make sense of all of this, + // there are 9 parts to draw, also mark the 12 points we'll use + // + // P0____________________P1 + // /| |\ + // /1| 2 |3\ + // P7 /__|____________________|__\ P2 + // | |P8 P9| | + // | 8 | 9 | 4 | + // | __|____________________|__ | + // P6 \ |P11 P10| / P3 + // \7| 6 |5/ + // \|____________________|/ + // P5 P4 + + // Coordinates of the 12 points that define the rounded rect + const radius_u = radius / rec.width; + const radius_v = radius / rec.height; + const points = [_]rl.Vector2{ + .{ .x = radius_u , .y = 0 }, // P0 + .{ .x = 1 - radius_u , .y = 0 }, // P1 + .{ .x = 1 , .y = radius_v }, // P2 + .{ .x = 1 , .y = 1 - radius_v }, // P3 + .{ .x = 1 - radius_u , .y = 1 }, // P4 + .{ .x = radius_u , .y = 1 }, // P5 + .{ .x = 0 , .y = 1 - radius_v }, // P6 + .{ .x = 0 , .y = radius_v }, // P7 + .{ .x = radius_u , .y = radius_v }, // P8 + .{ .x = 1 - radius_u , .y = radius_v }, // P9 + .{ .x = 1 - radius_u , .y = 1 - radius_v }, // P10 + .{ .x = radius_u , .y = 1 - radius_v }, // P11 + }; + + const texture = rl.getShapesTexture(); + const shape_rect = rl.getShapesTextureRectangle(); + + const texture_width: f32 = @floatFromInt(texture.width); + const texture_height: f32 = @floatFromInt(texture.height); + + rl.gl.rlBegin(rlgl_h.RL_TRIANGLES); + defer rl.gl.rlEnd(); + + rl.gl.rlSetTexture(texture.id); + defer rl.gl.rlSetTexture(0); + + // Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner + const centers = [_]rl.Vector2{ points[8], points[9], points[10], points[11] }; + const angles = [_]f32{ 180.0, 270.0, 0.0, 90.0 }; + for (0..4) |k| { + var angle = angles[k]; + const center = centers[k]; + for (0..@intCast(segments)) |_| { + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + + const rad_per_deg = std.math.rad_per_deg; + + const triangle = .{ + center, + .{ + .x = center.x + @cos(rad_per_deg*(angle + step_length))*radius_u, + .y = center.y + @sin(rad_per_deg*(angle + step_length))*radius_v + }, + .{ + .x = center.x + @cos(rad_per_deg * angle)*radius_u, + .y = center.y + @sin(rad_per_deg * angle)*radius_v + } + }; + + inline for (triangle) |point| { + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); + } + + angle += step_length; + } + } + + // [2] Upper Rectangle + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + inline for (.{ 0, 8, 9, 1, 0, 9 }) |index| { + const point = points[index]; + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); + } + + // [4] Right Rectangle + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + inline for (.{ 9, 10, 3, 2, 9, 3 }) |index| { + const point = points[index]; + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); + } + + // [6] Bottom Rectangle + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + inline for (.{ 11, 5, 4, 10, 11, 4 }) |index| { + const point = points[index]; + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); + } + + // [8] Left Rectangle + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + inline for (.{ 7, 6, 11, 8, 7, 11 }) |index| { + const point = points[index]; + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); + } + + // [9] Middle Rectangle + rl.gl.rlColor4ub(color.r, color.g, color.b, color.a); + inline for (.{ 8, 11, 10, 9, 8, 10 }) |index| { + const point = points[index]; + rl.gl.rlTexCoord2f( + (shape_rect.x + shape_rect.width * point.x) / texture_width, + (shape_rect.y + shape_rect.height * point.y) / texture_height + ); + rl.gl.rlVertex2f( + rec.x + rec.width * point.x, + rec.y + rec.height * point.y + ); } } -fn drawBlurredWorld(self: *App, rect: rl.Rectangle, roundness: f32, color: rl.Color) !void { +fn drawBlurredWorld(self: *App, rect: rl.Rectangle, color: rl.Color) !void { const blur_both = try createOrGetRenderTexture(&self.blur_texture_both); const previous_texture = rl.getShapesTexture(); @@ -250,10 +421,35 @@ fn drawBlurredWorld(self: *App, rect: rl.Rectangle, roundness: f32, color: rl.Co .height = -rect.height, }; rl.setShapesTexture(blur_both.texture, shape_rect); - drawRectangleRoundedUV(rect, roundness, 0, color); + + const border = 2; + const roundness = 0.2; + drawRectangleRoundedUV(rect, roundness, color); + rl.drawRectangleRoundedLinesEx(RectUtils.shrink(rect, border - 1, border - 1), roundness, 0, border, srcery.bright_white.alpha(0.3)); } -pub fn drawWorld(self: *App) !void { +pub fn drawWorld(self: *App) void { + rl.clearBackground(srcery.black); + + rl.drawCircleV(rl.Vector2.zero(), 5, rl.Color.red); + + const map_size = self.map_position_max.subtract(self.map_position_min); + for (0..@intCast(map_size.y)) |oy| { + for (0..@intCast(map_size.x)) |ox| { + const map_index = @as(usize, @intCast(map_size.x)) * oy + ox; + const x = self.map_position_min.x + @as(i64, @intCast(ox)); + const y = self.map_position_min.y + @as(i64, @intCast(oy)); + const texture_index = self.map_texture_indexes.items[map_index]; + const texture = self.map_textures.items[texture_index].texture; + + const tile_size = rl.Vector2.init(224, 224); + const position = rl.Vector2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size); + rl.drawTextureV(texture, position, rl.Color.white); + } + } +} + +pub fn drawWorldAndBlur(self: *App) !void { const screen_width = rl.getScreenWidth(); const screen_height = rl.getScreenHeight(); const screen_size = rl.Vector2.init(@floatFromInt(screen_width), @floatFromInt(screen_height)); @@ -267,27 +463,10 @@ pub fn drawWorld(self: *App) !void { blur_original.begin(); defer blur_original.end(); - rl.clearBackground(rl.Color.black.alpha(0)); - self.camera.begin(); defer self.camera.end(); - rl.drawCircleV(rl.Vector2.zero(), 5, rl.Color.red); - - const map_size = self.map_position_max.subtract(self.map_position_min); - for (0..@intCast(map_size.y)) |oy| { - for (0..@intCast(map_size.x)) |ox| { - const map_index = @as(usize, @intCast(map_size.x)) * oy + ox; - const x = self.map_position_min.x + @as(i64, @intCast(ox)); - const y = self.map_position_min.y + @as(i64, @intCast(oy)); - const texture_index = self.map_texture_indexes.items[map_index]; - const texture = self.map_textures.items[texture_index].texture; - - const tile_size = rl.Vector2.init(224, 224); - const position = rl.Vector2.init(@floatFromInt(x), @floatFromInt(y)).multiply(tile_size); - rl.drawTextureV(texture, position, rl.Color.white); - } - } + self.drawWorld(); } // 2 pass. Apply horizontal blur @@ -421,12 +600,11 @@ pub fn tick(self: *App) !void { rl.clearBackground(srcery.black); - try self.drawWorld(); + try self.drawWorldAndBlur(); try self.drawBlurredWorld( .{ .x = 20, .y = 20, .width = 200, .height = 200 }, - 0.5, - rl.Color.gray + srcery.xgray10 ); rl.drawFPS( diff --git a/gui/main.zig b/gui/main.zig index 9e8a21d..c7437ce 100644 --- a/gui/main.zig +++ b/gui/main.zig @@ -114,7 +114,7 @@ pub fn main() anyerror!void { rl.setWindowMinSize(200, 200); rl.setWindowState(.{ .vsync_hint = true, - // .window_resizable = true + .window_resizable = true }); var app = try App.init(allocator, &artificer);