diff --git a/examples/gg/mandelbrot.v b/examples/gg/mandelbrot.v new file mode 100644 index 0000000000..fcbe7f491c --- /dev/null +++ b/examples/gg/mandelbrot.v @@ -0,0 +1,187 @@ +import gg +import gx +import runtime +import time + +const pwidth = 800 + +const pheight = 600 + +const zoom_factor = 1.1 + +struct ViewRect { +mut: + x_min f64 + x_max f64 + y_min f64 + y_max f64 +} + +struct AppState { +mut: + gg &gg.Context = 0 + iidx int + pixels [pheight][pwidth]u32 + view ViewRect = ViewRect{-2.1320316, 0.55896836, -1.2200283, 1.47097206} + ntasks int = runtime.nr_jobs() +} + +const colors = [gx.black, gx.blue, gx.red, gx.green, gx.yellow, gx.orange, gx.purple, gx.white, + gx.indigo, gx.violet, gx.black] + +fn (mut state AppState) update() { + mut sw := time.new_stopwatch() + mut oview := ViewRect{} + for { + sw.restart() + cview := state.view + if oview == cview { + time.sleep(5 * time.millisecond) + continue + } + sheight := pheight / state.ntasks + mut threads := []thread{} + for start := 0; start < pheight; start += sheight { + threads << go state.recalc_lines(cview, start, start + sheight) + } + threads.wait() + println('$state.ntasks threads; $sw.elapsed().milliseconds() ms / frame') + oview = cview + } +} + +fn (mut state AppState) recalc_lines(cview ViewRect, ymin f64, ymax f64) { + for y_pixel := ymin; y_pixel < ymax && y_pixel < pheight; y_pixel++ { + y0 := (y_pixel / pheight) * (cview.y_max - cview.y_min) + cview.y_min + for x_pixel := 0.0; x_pixel < pwidth; x_pixel++ { + x0 := (x_pixel / pwidth) * (cview.x_max - cview.x_min) + cview.x_min + mut x, mut y := x0, y0 + mut iter := 0 + for ; iter < 80; iter++ { + x, y = x * x - y * y + x0, 2 * x * y + y0 + if x * x + y * y > 4 { + break + } + } + state.pixels[int(y_pixel)][int(x_pixel)] = u32(colors[iter % 8].abgr8()) + } + } +} + +fn (mut state AppState) draw() { + mut istream_image := state.gg.get_cached_image_by_idx(state.iidx) + istream_image.update_pixel_data(&state.pixels) + size := gg.window_size() + state.gg.draw_image(0, 0, size.width, size.height, istream_image) +} + +fn (mut state AppState) zoom(zoom_factor f64) { + c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2 + d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min + state.view.x_min = c_x - zoom_factor * d_x + state.view.x_max = c_x + zoom_factor * d_x + state.view.y_min = c_y - zoom_factor * d_y + state.view.y_max = c_y + zoom_factor * d_y +} + +fn (mut state AppState) center(s_x f64, s_y f64) { + c_x, c_y := (state.view.x_max + state.view.x_min) / 2, (state.view.y_max + state.view.y_min) / 2 + d_x, d_y := c_x - state.view.x_min, c_y - state.view.y_min + state.view.x_min = s_x - d_x + state.view.x_max = s_x + d_x + state.view.y_min = s_y - d_y + state.view.y_max = s_y + d_y +} + +// gg callbacks: + +fn graphics_init(mut state AppState) { + state.iidx = state.gg.new_streaming_image(pwidth, pheight, 4, pixel_format: .rgba8) +} + +fn graphics_frame(mut state AppState) { + state.gg.begin() + state.draw() + state.gg.end() +} + +fn graphics_click(x f32, y f32, btn gg.MouseButton, mut state AppState) { + if btn == .right { + size := gg.window_size() + m_x := (x / size.width) * (state.view.x_max - state.view.x_min) + state.view.x_min + m_y := (y / size.height) * (state.view.y_max - state.view.y_min) + state.view.y_min + state.center(m_x, m_y) + } +} + +fn graphics_move(x f32, y f32, mut state AppState) { + if state.gg.mouse_buttons.has(.left) { + size := gg.window_size() + d_x := (f64(state.gg.mouse_dx) / size.width) * (state.view.x_max - state.view.x_min) + d_y := (f64(state.gg.mouse_dy) / size.height) * (state.view.y_max - state.view.y_min) + state.view.x_min -= d_x + state.view.x_max -= d_x + state.view.y_min -= d_y + state.view.y_max -= d_y + } +} + +fn graphics_scroll(e &gg.Event, mut state AppState) { + state.zoom(if e.scroll_y < 0 { zoom_factor } else { 1 / zoom_factor }) +} + +fn graphics_keydown(code gg.KeyCode, mod gg.Modifier, mut state AppState) { + s_x := (state.view.x_max - state.view.x_min) / 5 + s_y := (state.view.y_max - state.view.y_min) / 5 + // movement + mut d_x, mut d_y := 0.0, 0.0 + if code == .enter { + println('> $state.view.x_min | $state.view.x_max | $state.view.y_min | $state.view.y_max') + } + if state.gg.pressed_keys[int(gg.KeyCode.left)] { + d_x -= s_x + } + if state.gg.pressed_keys[int(gg.KeyCode.right)] { + d_x += s_x + } + if state.gg.pressed_keys[int(gg.KeyCode.up)] { + d_y -= s_y + } + if state.gg.pressed_keys[int(gg.KeyCode.down)] { + d_y += s_y + } + state.view.x_min += d_x + state.view.x_max += d_x + state.view.y_min += d_y + state.view.y_max += d_y + // zoom in/out + if state.gg.pressed_keys[int(gg.KeyCode.left_bracket)] + || state.gg.pressed_keys[int(gg.KeyCode.z)] { + state.zoom(1 / zoom_factor) + return + } + if state.gg.pressed_keys[int(gg.KeyCode.right_bracket)] + || state.gg.pressed_keys[int(gg.KeyCode.x)] { + state.zoom(zoom_factor) + return + } +} + +fn main() { + mut state := &AppState{} + state.gg = gg.new_context( + width: 800 + height: 600 + create_window: true + window_title: 'The Mandelbrot Set' + init_fn: graphics_init + frame_fn: graphics_frame + click_fn: graphics_click + move_fn: graphics_move + keydown_fn: graphics_keydown + scroll_fn: graphics_scroll + user_data: state + ) + go state.update() + state.gg.run() +}