diff --git a/resources/fractal.glslf b/resources/fractal.glslf index b1e6fc1..7f78c73 100644 --- a/resources/fractal.glslf +++ b/resources/fractal.glslf @@ -1,148 +1,135 @@ #version 420 core -vec3 hsv2rgb( vec3 c); -bool fast_check( dvec2 z); -dvec2 coord_to_im(dvec2 coord); -in vec4 gl_FragCoord; +#define ESCAPE_BOUNDARY 18 +#define FREQUENCY 0.1 +in highp vec4 gl_FragCoord; +out vec4 Target0; -layout (std140) uniform Mandel { - uniform dvec2 u_MousePos; - uniform dvec2 u_Center; - uniform dvec2 u_Dimension; - uniform dvec2 u_Resolution; - uniform float u_Time; - uniform int u_MaxIter; - uniform int u_IsMandel; -}; -out vec4 Target0; +dvec2 vec2_to_dvec2(vec2 floatvec2); // Naive approach to guaranteeing double precision +dvec2 viewport_coord_to_complex(dvec2 coord); // moves and scales gl_FragCoord to desired complex number -dvec2 get_c() { - dvec2 ret; - if(u_IsMandel != 0) { - ret = coord_to_im(gl_FragCoord.xy); - } else { - ret = coord_to_im(u_MousePos); - } - - return ret; -} +// For this iterative escape-fractal, we need to select start value, and iterative modifier +dvec2 get_c(); +dvec2 get_z(); + +vec3 hsv2rgb( vec3 c); + +layout (std140) uniform Mandel { + uniform dvec2 u_Center; // where on the complex plain the user wants the screen-center + uniform dvec2 u_Dimension; // the width and height user wants the view onto the complex plane + uniform dvec2 u_Resolution; // user interogates the pixel coverage of the view, and provides it here + + uniform vec2 u_MousePos; // Used for julia set, assumes gl_FragCoord format + uniform float u_Time; // Time since start of program in seconds + uniform int u_MaxIteration; // User defined limit of iteration count + uniform int u_IsMandel; // (A bit hacky, TODO) select between MandelBrot and Julia +}; -dvec2 get_z() { - dvec2 ret; - if(u_IsMandel != 0) { - ret = dvec2(0.0, 0.0); - } else { - ret = coord_to_im(gl_FragCoord.xy); - } - - return ret; -} -dvec2 coord_to_im(dvec2 coord) { - dvec2 ret = coord - u_Resolution/2.0; - ret /= u_Resolution/(u_Dimension); - ret += u_Center; - // ret -= u_Dimension/2.0; - // ret -= u_Center/2.0; - // dvec2 scale = u_Resolution/(dvec2(3.5, 2.0)); - // ret /= scale; - return ret; -} void main() { - + + // load up our iteration values dvec2 z = get_z(); dvec2 c = get_c(); - int i; + + int step_count; dvec2 tmp; - // dvec2 zsq = dvec2(z.x*z.x - z.y*z.y, 2*z.x*z.y); - for (i = 0; i < u_MaxIter; i++) { + for (step_count = 0; step_count < u_MaxIteration; step_count++) { + + // Vast majority of performance gets absorbed here + + // TODO micro-optimise + // `z = z * z + c`, with z and c being complex numbers tmp.x = z.x*z.x - z.y*z.y; - tmp.y = 2.0*z.x*z.y; + tmp.y = 2.0 * z.x*z.y; z.x = tmp.x; z.y = tmp.y; z.x += c.x; z.y += c.y; - // z = z * z + c; - // z = dvec2(pow(z.x, 2) - pow(z.y, 2), 2 * z.x * z.y) + c; - - // z.y = (z.x + z.y)*(z.x + z.y) - zsq.x - zsq.y; - // z.y += c.y; - // z.x = zsq.x - zsq.y + c.x; - // zsq.x = z.x * z.x; - // zsq.y = z.y * z.y; + if((z.x*z.x + z.y*z.y) > ESCAPE_BOUNDARY) { + break; + } + } - // a different fast-check - // if((pow(D.x, 2) + pow(D.y, 2)) < 0.001) { - // Target0 = vec4(i/u_MaxIter, 0.0, 0.0, 1.0); - // return; - // } + if (step_count == u_MaxIteration) { + Target0 = vec4(1.0, 1.0, 1.0, 1.0); + } else { + float dist = float(length(z)); + float two = float(2.0); + dist = log(log(dist)) / log(two); - // dvec2 tmp; - // tmp.x = D.x*z.x - D.y*z.y; - // tmp.y = D.x*z.y + D.y*z.x; - // D.x = 2.0*tmp.x; - // D.y = 2.0*tmp.y; + float val = float(step_count) - dist; + float hue = val/10.0 - u_Time * FREQUENCY; - if((z.x*z.x + z.y*z.y) > 18) { - break; - } - // if (!fast_check(z)) { return;} + Target0 = vec4(hsv2rgb(vec3((hue), 1.0, 1.0 )), 1.0); } +} +// enhance... +dvec2 vec2_to_dvec2(vec2 floatvec2) { + return dvec2(floatvec2); +} +// center yourself, THEN grow your mind, then you will be moved on the plane of imagination +dvec2 viewport_coord_to_complex(dvec2 coord) { - if (i == u_MaxIter) { - Target0 = vec4(1.0, 1.0, 1.0, 1.0); + // set the origin to the center of the screen + dvec2 result = dvec2(coord) - u_Resolution/2.0; + + + // Scale down to a 1.0 by 1.0 view of the complex plane + result /= u_Resolution; + + // match the real and im dimensions to user-input + result *= u_Dimension; + + // shift the center reference to where the user asked it to be + result += u_Center; + return result; +} + + +// TODO set it up so that responsibility between mandel and julia decoupled +// at the moment seperate areoas are tightly coupled +dvec2 get_c() { + dvec2 result; + + // for mandel, the iter-step is where that pixel sits in the complex plane + // but for julia, it's based on mouse position + if(u_IsMandel != 0) { + result = viewport_coord_to_complex(gl_FragCoord.xy); } else { - float dist = float(z.x*z.x + z.y*z.y); + result = viewport_coord_to_complex(u_MousePos); + } - dist = log(log(dist))/log(2.0); - float val = float(i) - dist; - // float val = i/ float(u_MaxIter); - Target0 = vec4(hsv2rgb( vec3((val*0.1 - u_Time/5.0), 1.0, 1.0 )), 1.0); + return result; +} + +// TODO see get_c() +dvec2 get_z() { + dvec2 result; + + // for mandel, iterate starting from 0, for julia, the start relates to location in + // the complex plane + if(u_IsMandel != 0) { + result = dvec2(0.0, 0.0); + } else { + result = viewport_coord_to_complex(gl_FragCoord.xy); } + + return result; } -/*z.r = 0; - z.i = 0; - zrsqr = z.r * z.r; - zisqr = z.i * z.i; - while (zrsqr + zisqr <= 4.0) - { - z.i = z.r * z.i; - z.i += z.i; // Multiply by two - z.i += c.i; - z.r = zrsqr – zisqr + c.r; - zrsqr = square(z.r); - zisqr = square(z.i); -}*/ - -// dirty check that coveres points close enough to an attractor -// bool fast_check( dvec2 Z) { -// float r = sqrt(pow(Z.x - 0.25, 2) + pow(Z.y, 2)); -// if (Z.x < r - 2 * pow(r, 2) + 0.25) { -// Target0 = vec4(0, 1.0, 1.0, 1.0); -// return false; -// } - -// if (pow(Z.x + 1, 2) + pow(Z.y, 2) < 0.0625) { -// Target0 = vec4(1.0, 1.0, 1.0, 1.0); -// return false; -// } - -// return true; -// } - - - -// hue-rotates an rgb-value -vec3 hsv2rgb( vec3 c) { + + +// hue, saturation, value to reg, green, blue +vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www ); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); diff --git a/src/direction.rs b/src/direction.rs index e985e1a..a51267a 100644 --- a/src/direction.rs +++ b/src/direction.rs @@ -20,7 +20,6 @@ impl Direction { } mod test { - use super::*; #[test] fn key_code_to_direction() { diff --git a/src/main.rs b/src/main.rs index 4cac5b4..401fff4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,15 +17,15 @@ const SPEED_SCALE: f64 = 0.3; gfx_defines! { - // Input uniforms that get passed into the shader - // TODO set it up so that you can idiomatically select for mandel or julia + // DONE set it up so that you can idiomatically select for mandel or julia + // TODO refactor so more idiomatic in the code structure constant Mandel { - position: [f64; 2] = "u_MousePos", center: [f64; 2] = "u_Center", dimension: [f64; 2] = "u_Dimension", resolution: [f64; 2] = "u_Resolution", + position: [f32; 2] = "u_MousePos", time: f32 = "u_Time", - max_iter: i32 = "u_MaxIter", + max_iter: i32 = "u_MaxIteration", is_mandel: i32 = "u_IsMandel", } } @@ -33,9 +33,11 @@ gfx_defines! { impl Mandel { fn new(ctx: &Context) -> Self { Self { + // TODO varify that these uniforms are set in cpu only + // TODO delegate operations on these uniforms position: [0.0, 0.0], - center: [-0.5, -0.0], // TODO check if this is removable duplicate - dimension: [3.5, 2.0], // TODO check if this is removable duplicate + center: [-0.5, -0.0], + dimension: [3.0, 2.0], time: 0.0, max_iter: 120, resolution: [graphics::size(ctx).0, graphics::size(ctx).1], @@ -48,17 +50,22 @@ impl Mandel { #[derive (Debug)] struct MainState { + // TODO collect these items in a refactor canvas_render_target: Canvas, zoom: f64, mandel_uniforms: Mandel, shader: graphics::Shader, } + + const ITER_STEP: i32 = 5; impl MainState { fn new(ctx: &mut Context) -> GameResult { + let canvas_render_target = Canvas::with_window_size(ctx)?; + let mandel_uniforms = Mandel::new(ctx); let shader = graphics::Shader::from_u8( @@ -72,7 +79,6 @@ impl MainState { None )?; - let canvas_render_target = Canvas::with_window_size(ctx)?; // bring it all together Ok(Self { @@ -83,19 +89,18 @@ impl MainState { }) } - // fn resolution_center_origin(ctx: &Context, pos: &mut[f32; 2]) { - // pos[0] -= ctx.conf.window_mode.width/2.0; - // pos[1] -= ctx.conf.window_mode.height/2.0; - // } - fn set_origin(&mut self, center: [f64; 2]) { self.mandel_uniforms.center = center; } + // TODO command depends on this. change so this depends on command + // by doing += argument fn incriment_max_iter(&mut self) { self.mandel_uniforms.max_iter += ITER_STEP; } + // TODO command depends on this. change so this depends on command + // by doing += argument fn decriment_max_iter(&mut self) { let it = self.mandel_uniforms.max_iter; let it = std::cmp::max(2, it - ITER_STEP); @@ -105,25 +110,29 @@ impl MainState { impl ggez::event::EventHandler for MainState { fn update(&mut self, ctx: &mut Context) -> GameResult { - // shader uniform update + + // time since last frame to provide to the shader let shader_time = ggez::timer::time_since_start(ctx); let shader_time = ggez::timer::duration_to_f64(shader_time) * SPEED_SCALE; - self.mandel_uniforms.time = shader_time as f32; + Ok(()) } fn resize_event(&mut self, ctx: &mut Context, width: f32, height: f32) { - // TODO get from OS rather than hard-code the scale - // self.mandel_uniforms.update_resolution(ctx); - // let im = self.canvas_render_target.image(); + + // We want everything to scale according to a screen-pixel. With a new + // window size, the shader needs new information on pixel resolution let os_scale = graphics::os_hidpi_factor(ctx); self.mandel_uniforms.resolution = [(width*os_scale) as f64, (height*os_scale) as f64]; - println!("size: {:?}", self.mandel_uniforms.resolution); } fn draw(&mut self, ctx: &mut Context) -> GameResult { graphics::clear(ctx, [0.1, 0.1, 0.3, 1.0].into()); + + // Consider the graphics::Canvas object a software-abstraction of the + // computer screen, and graphics::present as its unveiling. Here, we + // have the gpu paint to a clean canvas without interuption, then unveil it. { let _lock = graphics::use_shader(ctx, &self.shader); self.shader.send(ctx, self.mandel_uniforms)?; @@ -141,16 +150,16 @@ impl ggez::event::EventHandler for MainState { _dx: f32, _dy: f32 ) { + + // As with `resize_event()` needing to adjust to match Canvas pixels, + // we need to do the same with mouse position. let scale = graphics::os_hidpi_factor(ctx); let y = graphics::size(ctx).1 as f32 - y; - self.mandel_uniforms.position[0] = (x*scale) as f64; - self.mandel_uniforms.position[1] = (y*scale) as f64; - println!("pos: {:?}", self.mandel_uniforms.position); - - // MainState::resolution_center_origin(&ctx, &mut self.mandel_uniforms.position); - + self.mandel_uniforms.position[0] = x * scale; + self.mandel_uniforms.position[1] = y * scale; } + fn key_down_event( &mut self, ctx: &mut Context, @@ -161,6 +170,7 @@ impl ggez::event::EventHandler for MainState { // TODO work out how to decouble responsibility let mut zoom_coeficient = (0.0, 0.0); + // TODO move this into a function call match Direction::from_keycode(keycode) { Some(Direction::Up) => self.mandel_uniforms.center[1] += self.mandel_uniforms.dimension[1] * 0.2, Some(Direction::Down) => self.mandel_uniforms.center[1] -= self.mandel_uniforms.dimension[1] * 0.2, @@ -169,6 +179,7 @@ impl ggez::event::EventHandler for MainState { None => {}, } + // TODO make this redundant then delete self.mandel_uniforms.center[0] += (zoom_coeficient.0 / self.zoom) as f64; self.mandel_uniforms.center[1] += (zoom_coeficient.1 / self.zoom) as f64; @@ -200,15 +211,9 @@ impl ggez::event::EventHandler for MainState { } fn main() -> GameResult { - let resource_dir = if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let mut path = path::PathBuf::from(manifest_dir); - path.push("resources"); - path - } else { - path::PathBuf::from("./resources") - }; - - let cb = ggez::ContextBuilder::new("shader", "moi").add_resource_path(resource_dir); + let resource_dir = path::PathBuf::from("./resources"); + + let cb = ggez::ContextBuilder::new("shader-driven julia/mandelbrot", "BenPH").add_resource_path(resource_dir).with_conf_file(true); let (ctx, event_loop) = &mut cb.build()?; ctx.conf.window_mode = ggez::conf::WindowMode::resizable(ctx.conf.window_mode, true);