diff --git a/build.zig b/build.zig index b553968..0d3b8d4 100644 --- a/build.zig +++ b/build.zig @@ -108,6 +108,11 @@ pub fn build(b: *std.Build) !void { .path = "examples/core/basic_window.zig", .desc = "Creates a basic window with text", }, + .{ + .name = "core_monitor_change", + .path = "examples/core/core_monitor_change.zig", + .desc = "Simple Monitor Manager", + }, .{ .name = "basic_window_web", .path = "examples/core/basic_window_web.zig", @@ -183,6 +188,11 @@ pub fn build(b: *std.Build) !void { .path = "examples/shaders/raymarching.zig", .desc = "Uses a raymarching in a shader to render shapes", }, + .{ + .name = "shaders_ascii_rendering", + .path = "examples/shaders/shaders_ascii_rendering.zig", + .desc = "Post-processing to render in ASCII", + }, .{ .name = "shaders_basic_pbr", .path = "examples/shaders/shaders_basic_pbr.zig", diff --git a/examples/core/core_monitor_change.zig b/examples/core/core_monitor_change.zig new file mode 100644 index 0000000..194ff50 --- /dev/null +++ b/examples/core/core_monitor_change.zig @@ -0,0 +1,153 @@ +// raylib-zig (c) 2025 Maicon Santana (@maiconpintoabreu) + +const std = @import("std"); +const rl = @import("raylib"); + +const MAX_MONITORS = 10; + +// Monitor Details +const Monitor = struct { + position: rl.Vector2, + name: [*c]const u8, + width: i32, + height: i32, + physicalWidth: i32, + physicalHeight: i32, + refreshRate: i32, +}; + +pub fn main() anyerror!void { + // Initialization + //-------------------------------------------------------------------------------------- + const screenWidth: i32 = 800; + const screenHeight: i32 = 450; + + var monitors: [MAX_MONITORS]Monitor = undefined; + + rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - monitor change"); + defer rl.closeWindow(); // Close window and OpenGL context + + var currentMonitorIndex: i32 = rl.getCurrentMonitor(); + var monitorCount: usize = 0; + + rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!rl.windowShouldClose()) { // Detect window close button or ESC key + // Update + //---------------------------------------------------------------------------------- + + // Variables to find the max x and Y to calculate the scale + var maxWidth: i32 = 1; + var maxHeight: i32 = 1; + + // Monitor offset is to fix when monitor position x is negative + var monitorOffsetX: f32 = 0; + + // Rebuild monitors array every frame + monitorCount = @intCast(rl.getMonitorCount()); + for (0..monitorCount) |i| { + const intI: i32 = @intCast(i); + monitors[i] = .{ + .position = rl.getMonitorPosition(intI), + .name = rl.getMonitorName(intI), + .width = rl.getMonitorWidth(intI), + .height = rl.getMonitorHeight(intI), + .physicalWidth = rl.getMonitorPhysicalWidth(intI), + .physicalHeight = rl.getMonitorPhysicalHeight(intI), + .refreshRate = rl.getMonitorRefreshRate(intI), + }; + if (monitors[i].position.x < monitorOffsetX) { + monitorOffsetX = monitors[i].position.x * -1.0; + } + + const width: i32 = @as(i32, @intFromFloat(monitors[i].position.x)) + monitors[i].width; + const height: i32 = @as(i32, @intFromFloat(monitors[i].position.y)) + monitors[i].height; + + if (maxWidth < width) maxWidth = width; + if (maxHeight < height) maxHeight = height; + } + + if (rl.isKeyPressed(.enter) and monitorCount > 1) { + currentMonitorIndex += 1; + + // Set index to 0 if the last one + if (currentMonitorIndex == monitorCount) currentMonitorIndex = 0; + + rl.setWindowMonitor(currentMonitorIndex); // Move window to currentMonitorIndex + } else { + // Get currentMonitorIndex if manually moved + currentMonitorIndex = rl.getCurrentMonitor(); + } + + var monitorScale: f32 = 0.6; + + const intMonitorOffsetX: i32 = @intFromFloat(monitorOffsetX); + + if (maxHeight > maxWidth + intMonitorOffsetX) { + monitorScale *= @as(f32, @floatFromInt(screenHeight)) / @as(f32, @floatFromInt(maxHeight)); + } else { + monitorScale *= @as(f32, @floatFromInt(screenWidth)) / @as(f32, @floatFromInt((maxWidth + intMonitorOffsetX))); + } + + // Draw + //---------------------------------------------------------------------------------- + rl.beginDrawing(); + defer rl.endDrawing(); + + rl.clearBackground(.ray_white); + + rl.drawText("Press [Enter] to move window to next monitor available", 20, 20, 20, .dark_gray); + + rl.drawRectangleLines(20, 60, screenWidth - 40, screenHeight - 100, .dark_gray); + + // Draw Monitor Rectangles with information inside + for (0..monitorCount) |i| { + // Calculate retangle position and size using monitorScale + const rec: rl.Rectangle = .{ + .x = (monitors[i].position.x + monitorOffsetX) * monitorScale + 140, + .y = monitors[i].position.y * monitorScale + 80, + .width = @as(f32, @floatFromInt(monitors[i].width)) * monitorScale, + .height = @as(f32, @floatFromInt(monitors[i].height)) * monitorScale, + }; + + // Draw monitor name and information inside the rectangle + rl.drawText( + rl.textFormat("[%i] %s", .{ i, monitors[i].name }), + @intFromFloat(rec.x + 10.0), + @intFromFloat(rec.y + (100.0 * monitorScale)), + @as(i32, @intFromFloat(120.0 * monitorScale)), + .blue, + ); + rl.drawText(rl.textFormat("Resolution: [%ipx x %ipx]\nRefreshRate: [%ihz]\nPhysical Size: [%imm x %imm]\nPosition: %3.0f x %3.0f", .{ + monitors[i].width, + monitors[i].height, + monitors[i].refreshRate, + monitors[i].physicalWidth, + monitors[i].physicalHeight, + monitors[i].position.x, + monitors[i].position.y, + }), @intFromFloat(rec.x + 10), @intFromFloat(rec.y + (200 * monitorScale)), @as(i32, @intFromFloat(120.0 * monitorScale)), .dark_gray); + + // Highlight current monitor + if (i == currentMonitorIndex) { + rl.drawRectangleLinesEx(rec, 5, .red); + const windowPosition: rl.Vector2 = .{ + .x = (rl.getWindowPosition().x + monitorOffsetX) * monitorScale + 140, + .y = rl.getWindowPosition().y * monitorScale + 80, + }; + + // Draw window position based on monitors + rl.drawRectangleV( + windowPosition, + .{ .x = @as(f32, @floatFromInt(screenWidth)) * monitorScale, .y = @as(f32, @floatFromInt(screenHeight)) * monitorScale }, + rl.fade(.green, 0.5), + ); + } else { + rl.drawRectangleLinesEx(rec, 5, .gray); + } + } + //---------------------------------------------------------------------------------- + } +} diff --git a/examples/shaders/resources/LICENSE.md b/examples/shaders/resources/LICENSE.md new file mode 100644 index 0000000..4591d44 --- /dev/null +++ b/examples/shaders/resources/LICENSE.md @@ -0,0 +1,4 @@ +| resource | author | licence | notes | +| :----------------- | :-----------: | :------ | :---- | +| fudesumi.png | [Eiden Marsal](https://www.artstation.com/marshall_z) | [CC-BY-NC](https://creativecommons.org/licenses/by-nc/4.0/) | - | +| raysan.png | [@raysan5](https://github.com/raysan5) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | - | \ No newline at end of file diff --git a/examples/shaders/resources/fudesumi.png b/examples/shaders/resources/fudesumi.png new file mode 100644 index 0000000..1bf4ab7 Binary files /dev/null and b/examples/shaders/resources/fudesumi.png differ diff --git a/examples/shaders/resources/raysan.png b/examples/shaders/resources/raysan.png new file mode 100644 index 0000000..36e13ba Binary files /dev/null and b/examples/shaders/resources/raysan.png differ diff --git a/examples/shaders/resources/shaders/glsl100/ascii.fs b/examples/shaders/resources/shaders/glsl100/ascii.fs new file mode 100644 index 0000000..54e20bf --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/ascii.fs @@ -0,0 +1,80 @@ +#version 100 + +precision mediump float; + +// Input from the vertex shader +varying vec2 fragTexCoord; + +// Output color for the screen +varying vec4 finalColor; + +uniform sampler2D texture0; +uniform vec2 resolution; + +// Fontsize less then 9 may be not complete +uniform float fontSize; + +float GreyScale(in vec3 col) +{ + return dot(col, vec3(0.2126, 0.7152, 0.0722)); +} + +float GetCharacter(float n, vec2 p) +{ + p = floor(p*vec2(-4.0, 4.0) + 2.5); + + // Check if the calculated coordinate is inside the 5x5 grid (from 0.0 to 4.0) + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + float a = floor(p.x + 0.5) + 5.0*floor(p.y + 0.5); + + // This checked if the 'a'-th bit of 'n' was set + float shiftedN = floor(n/pow(2.0, a)); + + if (mod(shiftedN, 2.0) == 1.0) + { + return 1.0; // The bit is on + } + } + + return 0.0; // The bit is off, or we are outside the grid +} + +// ----------------------------------------------------------------------------- +// Main shader logic +// ----------------------------------------------------------------------------- + +void main() +{ + vec2 charPixelSize = vec2(fontSize, fontSize); + vec2 uvCellSize = charPixelSize/resolution; + + // The cell size is based on the fontSize set by application + vec2 cellUV = floor(fragTexCoord/uvCellSize)*uvCellSize; + + vec3 cellColor = texture2D(texture0, cellUV).rgb; + + // Gray is used to define what character will be selected to draw + float gray = GreyScale(cellColor); + + float n = 4096.0; + + // Character set from https://www.shadertoy.com/view/lssGDj + // Create new bitmaps https://thrill-project.com/archiv/coding/bitmap/ + if (gray > 0.2) n = 65600.0; // : + if (gray > 0.3) n = 18725316.0; // v + if (gray > 0.4) n = 15255086.0; // o + if (gray > 0.5) n = 13121101.0; // & + if (gray > 0.6) n = 15252014.0; // 8 + if (gray > 0.7) n = 13195790.0; // @ + if (gray > 0.8) n = 11512810.0; // # + + vec2 localUV = (fragTexCoord - cellUV)/uvCellSize; // Range [0.0, 1.0] + + vec2 p = localUV*2.0 - 1.0; // Range [-1.0, 1.0] + + // cellColor and charShape will define the color of the char + vec3 color = cellColor*GetCharacter(n, p); + + gl_FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl120/ascii.fs b/examples/shaders/resources/shaders/glsl120/ascii.fs new file mode 100644 index 0000000..09c572a --- /dev/null +++ b/examples/shaders/resources/shaders/glsl120/ascii.fs @@ -0,0 +1,78 @@ +#version 120 + +// Input from the vertex shader +varying vec2 fragTexCoord; + +// Output color for the screen +varying vec4 finalColor; + +uniform sampler2D texture0; +uniform vec2 resolution; + +// Fontsize less then 9 may be not complete +uniform float fontSize; + +float GreyScale(in vec3 col) +{ + return dot(col, vec3(0.2126, 0.7152, 0.0722)); +} + +float GetCharacter(float n, vec2 p) +{ + p = floor(p*vec2(-4.0, 4.0) + 2.5); + + // Check if the calculated coordinate is inside the 5x5 grid (from 0.0 to 4.0) + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + float a = floor(p.x + 0.5) + 5.0*floor(p.y + 0.5); + + // This checked if the 'a'-th bit of 'n' was set + float shiftedN = floor(n/pow(2.0, a)); + + if (mod(shiftedN, 2.0) == 1.0) + { + return 1.0; // The bit is on + } + } + + return 0.0; // The bit is off, or we are outside the grid +} + +// ----------------------------------------------------------------------------- +// Main shader logic +// ----------------------------------------------------------------------------- + +void main() +{ + vec2 charPixelSize = vec2(fontSize, fontSize); + vec2 uvCellSize = charPixelSize / resolution; + + // The cell size is based on the fontSize set by application + vec2 cellUV = floor(fragTexCoord / uvCellSize)*uvCellSize; + + vec3 cellColor = texture2D(texture0, cellUV).rgb; + + // Gray is used to define what character will be selected to draw + float gray = GreyScale(cellColor); + + float n = 4096.0; + + // Character set from https://www.shadertoy.com/view/lssGDj + // Create new bitmaps https://thrill-project.com/archiv/coding/bitmap/ + if (gray > 0.2) n = 65600.0; // : + if (gray > 0.3) n = 18725316.0; // v + if (gray > 0.4) n = 15255086.0; // o + if (gray > 0.5) n = 13121101.0; // & + if (gray > 0.6) n = 15252014.0; // 8 + if (gray > 0.7) n = 13195790.0; // @ + if (gray > 0.8) n = 11512810.0; // # + + vec2 localUV = (fragTexCoord - cellUV)/uvCellSize; // Range [0.0, 1.0] + + vec2 p = localUV*2.0 - 1.0; // Range [-1.0, 1.0] + + // cellColor and charShape will define the color of the char + vec3 color = cellColor*GetCharacter(n, p); + + gl_FragColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/resources/shaders/glsl330/ascii.fs b/examples/shaders/resources/shaders/glsl330/ascii.fs new file mode 100644 index 0000000..3f73bf2 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl330/ascii.fs @@ -0,0 +1,73 @@ +#version 330 + +// Input from the vertex shader +in vec2 fragTexCoord; + +// Output color for the screen +out vec4 finalColor; + +uniform sampler2D texture0; +uniform vec2 resolution; + +// Fontsize less then 9 may be not complete +uniform float fontSize; + +float GreyScale(in vec3 col) +{ + return dot(col, vec3(0.2126, 0.7152, 0.0722)); +} + +float GetCharacter(int n, vec2 p) +{ + p = floor(p*vec2(-4.0, 4.0) + 2.5); + + // Check if the coordinate is inside the 5x5 grid (0 to 4) + if (clamp(p.x, 0.0, 4.0) == p.x && clamp(p.y, 0.0, 4.0) == p.y) + { + int a = int(round(p.x) + 5.0*round(p.y)); + if (((n >> a) & 1) == 1) + { + return 1.0; + } + } + + return 0.0; // The bit is off, or we are outside the grid +} + +// ----------------------------------------------------------------------------- +// Main shader logic +// ----------------------------------------------------------------------------- + +void main() +{ + vec2 charPixelSize = vec2(fontSize, fontSize); + vec2 uvCellSize = charPixelSize/resolution; + + // The cell size is based on the fontSize set by application + vec2 cellUV = floor(fragTexCoord/uvCellSize)*uvCellSize; + + vec3 cellColor = texture(texture0, cellUV).rgb; + + // Gray is used to define what character will be selected to draw + float gray = GreyScale(cellColor); + + int n = 4096; + + // Character set from https://www.shadertoy.com/view/lssGDj + // Create new bitmaps https://thrill-project.com/archiv/coding/bitmap/ + if (gray > 0.2) n = 65600; // : + if (gray > 0.3) n = 18725316; // v + if (gray > 0.4) n = 15255086; // o + if (gray > 0.5) n = 13121101; // & + if (gray > 0.6) n = 15252014; // 8 + if (gray > 0.7) n = 13195790; // @ + if (gray > 0.8) n = 11512810; // # + + vec2 localUV = (fragTexCoord - cellUV)/uvCellSize; // Range [0.0, 1.0] + + vec2 p = localUV*2.0 - 1.0; // Range [-1.0, 1.0] + + vec3 color = cellColor*GetCharacter(n, p); + + finalColor = vec4(color, 1.0); +} \ No newline at end of file diff --git a/examples/shaders/shaders_ascii_rendering.zig b/examples/shaders/shaders_ascii_rendering.zig new file mode 100644 index 0000000..b1b25ba --- /dev/null +++ b/examples/shaders/shaders_ascii_rendering.zig @@ -0,0 +1,100 @@ +// raylib-zig (c) 2025 Maicon Santana (@maiconpintoabreu) + +const builtin = @import("builtin"); +const rl = @import("raylib"); + +const GLSL_VERSION: i16 = if (builtin.cpu.arch.isWasm()) 100 else 330; + +pub fn main() anyerror!void { + // Initialization + //-------------------------------------------------------------------------------------- + const screenWidth = 800; + const screenHeight = 450; + + rl.initWindow(screenWidth, screenHeight, "raylib-zig [core] example - monitor change"); + // De-Initialization + //-------------------------------------------------------------------------------------- + defer rl.closeWindow(); // Close window and OpenGL context + + // Texture to test static drawing + const fudesumi: rl.Texture2D = try rl.loadTexture("examples/shaders/resources/fudesumi.png"); + defer fudesumi.unload(); + // Texture to test moving drawing + const raysan: rl.Texture2D = try rl.loadTexture("examples/shaders/resources/raysan.png"); + defer raysan.unload(); + + // Load shader to be used on postprocessing + const shader: rl.Shader = try rl.loadShader( + null, + rl.textFormat("examples/shaders/resources/shaders/glsl%i/ascii.fs", .{GLSL_VERSION}), + ); + + // These locations are used to send data to the GPU + const resolutionLoc: i32 = rl.getShaderLocation(shader, "resolution"); + const fontSizeLoc: i32 = rl.getShaderLocation(shader, "fontSize"); + + // Set the character size for the ASCII effect + // Fontsize should be 9 or more + var fontSize: f32 = 9.0; + + // Send the updated values to the shader + const resolution: rl.Vector2 = .{ .x = @floatFromInt(screenWidth), .y = @floatFromInt(screenHeight) }; + rl.setShaderValue(shader, resolutionLoc, &resolution, .vec2); + + var circlePos: rl.Vector2 = .{ .x = 40.0, .y = @as(f32, @floatFromInt(screenHeight)) * 0.5 }; + var circleSpeed: f32 = 1.0; + + // RenderTexture to apply the postprocessing later + const target: rl.RenderTexture2D = try rl.loadRenderTexture(screenWidth, screenHeight); + + rl.setTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!rl.windowShouldClose()) { // Detect window close button or ESC key + + // Update + //---------------------------------------------------------------------------------- + circlePos.x += circleSpeed; + if ((circlePos.x > 200.0) or (circlePos.x < 40.0)) circleSpeed *= -1; // Revert speed + + if (rl.isKeyPressed(.left) and (fontSize > 9.0)) fontSize -= 1.0; // Reduce fontSize + if (rl.isKeyPressed(.right) and (fontSize < 15.0)) fontSize += 1.0; // Increase fontSize + + // Set fontsize for the shader + rl.setShaderValue(shader, fontSizeLoc, &fontSize, .float); + + // Draw + //---------------------------------------------------------------------------------- + { + rl.beginTextureMode(target); + defer rl.endTextureMode(); + + rl.clearBackground(.white); + + // Draw scene in our render texture + rl.drawTexture(fudesumi, 500, -30, .white); + rl.drawTextureV(raysan, circlePos, .white); + } + rl.beginDrawing(); + defer rl.endDrawing(); + rl.clearBackground(.ray_white); + { + rl.beginShaderMode(shader); + defer rl.endShaderMode(); + // Draw the scene texture (that we rendered earlier) to the screen + // The shader will process every pixel of this texture + rl.drawTextureRec(target.texture, .{ + .x = 0, + .y = 0, + .width = @floatFromInt(target.texture.width), + .height = -@as(f32, @floatFromInt(target.texture.height)), + }, .zero(), .white); + } + + rl.drawRectangle(0, 0, screenWidth, 40, .black); + rl.drawText(rl.textFormat("Ascii effect - FontSize:%2.0f - [Left] -1 [Right] +1 ", .{fontSize}), 120, 10, 20, .light_gray); + rl.drawFPS(10, 10); + //---------------------------------------------------------------------------------- + } +}