mandelbrot

Mandelbrot viewer written in C and GLSL
git clone anongit@rnpnr.xyz:mandelbrot.git
Log | Files | Refs | Feed | LICENSE

Commit: eb6887ed65073619ba69a34eed66742702be4c56
Parent: 630121998c5f85b2bb1b4e5d8058ad618a5047e7
Author: Randy Palamar
Date:   Sat, 25 May 2024 10:26:10 -0600

add basic zooming and panning

Diffstat:
Mfrag.glsl | 45+++++++++++++++++++++++++--------------------
Mmain.c | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 102 insertions(+), 36 deletions(-)

diff --git a/frag.glsl b/frag.glsl @@ -4,24 +4,8 @@ out vec4 colour; uniform uvec2 u_screen_dim; - -vec2 map_mandelbrot(vec2 v, float aspect) -{ - /* midpoint is (-0.765, 0) */ - /* x is in [-2.00, 0.47] */ - /* x new [-2.5, 1] -> 3.5 */ - /* y is in [-1.12, 1.12] */ - /* y new [-1.5, 1.5] -> 3.0 */ - vec2 delta = vec2(0); - - float desired_aspect = 3.5 / 3; - delta.x = -0.765 - 3.5 * desired_aspect / 2; - delta.y = -1.5; - - float x = v.x * 3.5 * desired_aspect; - float y = v.y * 3; - return vec2(x, y) + delta; -} +uniform vec2 u_pos; +uniform float u_zoom = 1.0; vec3 wavelength2rgb(float lambda) { @@ -68,15 +52,36 @@ vec3 wavelength2rgb(float lambda) return rgb; } +vec2 map_mandelbrot(vec2 v) +{ + vec2 scale = vec2(3.5, 3) / u_zoom; + vec2 delta = vec2(0); + + delta.x = - 0.765 - scale.x / 2; + delta.y = - scale.y / 2; + delta += u_pos / u_zoom; + + float x = scale.x * v.x; + float y = scale.y * v.y; + return vec2(x, y) + delta; +} + void main() { float aspect = u_screen_dim.x / u_screen_dim.y; - vec2 xy0 = map_mandelbrot(gl_FragCoord.xy / u_screen_dim.xy, aspect); + vec2 xy0 = map_mandelbrot(gl_FragCoord.xy / u_screen_dim.xy); int i; float xx = 0, yy = 0; vec2 xy = xy0; - for (i = 0; i < 300 && xx + yy < 15.0; i++) { + for (i = 0; i < 300 && xx + yy < 10.0; i++) { + xx = xy.x * xy.x; + yy = xy.y * xy.y; + xy = vec2(xx - yy + xy0.x, 2 * xy.x * xy.y + xy0.y); + } + + /* extra iterations to reduce error in escape calculation */ + for (int j = 0; j < 2; j++) { xx = xy.x * xy.x; yy = xy.y * xy.y; xy = vec2(xx - yy + xy0.x, 2 * xy.x * xy.y + xy0.y); diff --git a/main.c b/main.c @@ -8,6 +8,7 @@ #include <GLFW/glfw3.h> typedef float f32; +typedef double f64; typedef uint8_t u8; typedef int32_t i32; typedef uint32_t u32; @@ -35,7 +36,9 @@ static struct { u32 vao, vbo; i32 pid; i32 height, width; - i32 u_screen_dim; + i32 u_screen_dim, u_zoom, u_pos; + struct { f32 x, y; } cursor; + f32 zoom; Colour clear_colour; } g_glctx; @@ -66,16 +69,65 @@ fb_callback(GLFWwindow *win, i32 w, i32 h) g_glctx.height = h; g_glctx.width = w; glViewport(0, 0, w, h); - if (g_glctx.u_screen_dim != -1) - glUniform2ui(g_glctx.u_screen_dim, w, h); } static void key_callback(GLFWwindow *win, i32 key, i32 sc, i32 action, i32 mod) { - (void)sc; (void)mod; - if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) - glfwSetWindowShouldClose(win, GL_TRUE); + (void)sc; + + f32 scale = (mod & GLFW_MOD_SHIFT) ? 3 : 1; + + switch (key) { + case GLFW_KEY_ESCAPE: + if (action == GLFW_PRESS) + glfwSetWindowShouldClose(win, GL_TRUE); + break; + case GLFW_KEY_W: + if (action == GLFW_PRESS || action == GLFW_REPEAT) + g_glctx.cursor.y += scale * 0.05; + break; + case GLFW_KEY_A: + if (action == GLFW_PRESS || action == GLFW_REPEAT) + g_glctx.cursor.x -= scale * 0.05; + break; + case GLFW_KEY_S: + if (action == GLFW_PRESS || action == GLFW_REPEAT) + g_glctx.cursor.y -= scale * 0.05; + break; + case GLFW_KEY_D: + if (action == GLFW_PRESS || action == GLFW_REPEAT) + g_glctx.cursor.x += scale * 0.05; + break; + } +} + +static void +scroll_callback(GLFWwindow *win, f64 xdelta, f64 ydelta) +{ + f32 scale = glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) ? 5 : 1; + g_glctx.zoom += scale * ydelta; + if (g_glctx.zoom < 1.0) g_glctx.zoom = 1.0; +} + +static void +mouse_button_callback(GLFWwindow *win, i32 btn, i32 act, i32 mod) +{ + if (btn == GLFW_MOUSE_BUTTON_LEFT && act == GLFW_PRESS) { + f64 xpos, ypos; + glfwGetCursorPos(g_glctx.window, &xpos, &ypos); + f32 delta_x = 2 * xpos / g_glctx.width - 1; + f32 delta_y = 2 * ypos / g_glctx.height - 1; + f32 scale = 1; //g_glctx.zoom; + g_glctx.cursor.x -= scale * delta_x; + g_glctx.cursor.y -= scale * delta_y; + } + + if (btn == GLFW_MOUSE_BUTTON_RIGHT && act == GLFW_PRESS) { + g_glctx.zoom = 1; + g_glctx.cursor.x = 0; + g_glctx.cursor.y = 0; + } } static void @@ -135,7 +187,8 @@ spawn_window(void) glfwSetFramebufferSizeCallback(g_glctx.window, fb_callback); glfwSetKeyCallback(g_glctx.window, key_callback); - //glfwSetScrollCallback(glctx.window, scroll_callback); + glfwSetScrollCallback(g_glctx.window, scroll_callback); + glfwSetMouseButtonCallback(g_glctx.window, mouse_button_callback); g_glctx.clear_colour = (Colour){ .r = 64, .b = 64, .g = 64, .a = 255 }; clear_colour(g_glctx.clear_colour); @@ -205,6 +258,14 @@ get_arena(void) return a; } +static void +validate_uniforms(void) +{ + g_glctx.u_screen_dim = glGetUniformLocation(g_glctx.pid, "u_screen_dim"); + g_glctx.u_zoom = glGetUniformLocation(g_glctx.pid, "u_zoom"); + g_glctx.u_pos = glGetUniformLocation(g_glctx.pid, "u_pos"); +} + int main(void) { @@ -231,9 +292,8 @@ main(void) return -1; } glUseProgram(g_glctx.pid); - g_glctx.u_screen_dim = glGetUniformLocation(g_glctx.pid, "u_screen_dim"); - if (g_glctx.u_screen_dim != -1) - glUniform2ui(g_glctx.u_screen_dim, g_glctx.width, g_glctx.height); + validate_uniforms(); + g_glctx.zoom = 1.0; u32 fcount = 0; f32 last_time = 0; @@ -244,8 +304,9 @@ main(void) f32 dt = current_time - last_time; last_time = current_time; if (++fcount > 1000) { - printf("FPS: %0.03f | dt = %0.03f [ms]\n", - 1 / dt, dt * 1e3); + printf("FPS: %0.03f | dt = %0.03f [ms] | cursor = { " + "%0.04f, %0.04f, %0.01f }\n", 1 / dt, dt * 1e3, + g_glctx.cursor.x, g_glctx.cursor.y, g_glctx.zoom); fcount = 0; } @@ -262,13 +323,13 @@ main(void) glDeleteProgram(g_glctx.pid); g_glctx.pid = pid; glUseProgram(g_glctx.pid); - i32 usd = glGetUniformLocation(pid, "u_screen_dim"); - g_glctx.u_screen_dim = usd; - if (usd != -1) - glUniform2ui(usd, g_glctx.width, g_glctx.height); + validate_uniforms(); } } + glUniform2fv(g_glctx.u_pos, 1, (f32 *)&g_glctx.cursor); + glUniform2ui(g_glctx.u_screen_dim, g_glctx.width, g_glctx.height); + glUniform1f(g_glctx.u_zoom, g_glctx.zoom); clear_colour(g_glctx.clear_colour); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glfwSwapBuffers(g_glctx.window);