LoginSignup
12
7

More than 5 years have passed since last update.

マンデルブロ集合再び

Last updated at Posted at 2017-04-05

Previous

マンデルブロ集合
以前rubyで描いたものがこちら

400x400の150フレームのgifを描くのに30分近くかかっていて、rubyでやるもんじゃないねって感じだったのですが最近rust再々入門したので練習題材にいいかなと思っていたところ、scivolaさんに先を越されてしまったので少々違うものをと

scivolaさんの物は、周期的な色使い、きっちりした拡大点、最後に最初の模様が出てきて綺麗なアニメーションです
Rust でマンデルブロ集合のズーム・アニメーション

同じことやっても仕方がないので
もっと自由にマンデルブロ集合を旅できるようにしようと
というわけでOpenGLを使ったインタラクティブな描写をしてみます

手元で動かしてみたい方はこちらをcargo run
MacOSでしか動作確認してませんし、山ほどライブラリがインストールされますが
source

Code

rustのOpenGLのtype safeなbindingであるglium
ウインドウやインプットなどのイベントをマネージしてくれるglutin
この二つのクレートを使います

昔cで描いた時は色んな初期化やらコンパイルやらリンクやらバインドやら
なんぞかんぞ面倒だったものがこうも簡単になるなんて、素晴らしいもんです

さて、肝心の発散判定ですがrustではなくGLSL(フラグメントシェーダー)で書いています
正確なベンチマークはありませんが、体感ではGLSLの方が早い気がします

マンデルブロ集合旅行ということで
自由に移動したり、ズームしたり、密度を変えたり、計算精度を変えたりできます

  • 矢印キー: 中心を移動
  • Enter: ズームイン
  • Backspace/Delete: ズームアウト
  • z: 精度を上げる
  • x: 精度を下げる
  • c: 密度を上げる
  • v: 密度を下げる

デフォルトでは密度、精度共にかなり低めに設定してあるのでCPUやGPUとご相談の上適度に調整してみてください

#[macro_use]
extern crate glium;
use glium::{DisplayBuild, Surface, Program};
use glium::{glutin, index, vertex};

#[derive(Copy, Clone)]
struct Vertex {
  pos: [f64; 2]
}

implement_vertex!(Vertex, pos);

fn vertexs(range_y: f64, range_x: f64, density: f64) -> Vec<Vertex> {
  let mut vs = vec!();
  let mut y: f64 = -range_y;

  while y < range_y {
    let mut x: f64 = -range_x;

    while x < range_x {
      vs.push(Vertex { pos: [x,y] });
      x += density;
    }

    y += density;
  }

  return vs;
}

fn main() {
  let display = glutin::WindowBuilder::new().build_glium().unwrap();

  let program = Program::from_source(
    &display, 
    r#"
      #version 400

      in vec2 pos;
      out vec2 p;

      void main() {
        p = pos;
        gl_Position = vec4(pos, 0.0, 1.0);
      }
    "#, 

    r#"
      #version 400

      in vec2 p;
      out vec4 color;

      uniform double max;
      uniform double scale;
      uniform double center_y;
      uniform double center_x;

      vec2 mandelbrot(double a, double b) {
        double y = 0;
        double x = 0;
        for (double n = 0; n < max; n++) {
          double yy = 2 * x * y + b;
          double xx = x * x - y * y + a;
          y = yy;
          x = xx;

          if (sqrt(y * y + x * x) > 4.0) {
            return vec2(true, n / max);
          }
        }

        return vec2(false, max / max);
      }

      void main() {
        double x = center_x + p.x * scale;
        double y = center_y + p.y * scale;
        vec2 m = mandelbrot(x, y);
        if (bool(m.x)) { 
          color = vec4(m.y, m.y, m.y, m.y);
        }
        else {
          color = vec4(0.0, 0.0, 0.0, 0.0);
        }
      }
    "#, 

    None
  );

  if let Err(msg) = program {
    println!("{}", msg); 
    return;
  }
  let program = program.unwrap();

  let index = index::NoIndices(index::PrimitiveType::Points);

  let mut density: f64 = 0.01;
  let mut max: f64 = 100.0;
  let mut scale: f64 = 3.0;
  let mut center_y: f64 = 0.0;
  let mut center_x: f64 = 0.0;

  loop {
    let vertex_buffer = vertex::VertexBuffer::new(
      &display, &vertexs(1.0, 1.0, density)
    ).unwrap();

    let mut frame = display.draw();
    frame.clear_color(0.0, 0.0, 0.0 ,0.0);

    for e in display.wait_events() {
      match e {
        glutin::Event::KeyboardInput(
          glutin::ElementState::Pressed, _, Some(keycode)
        ) => {
          println!("{:?}", e);
          match keycode {
            glutin::VirtualKeyCode::Up => {
              center_y += 0.05 * scale; 
            },

            glutin::VirtualKeyCode::Down => {
              center_y -= 0.05 * scale;
            },

            glutin::VirtualKeyCode::Left => {
              center_x -= 0.05 * scale;
            },

            glutin::VirtualKeyCode::Right => {
              center_x += 0.05 * scale;
            },

            glutin::VirtualKeyCode::Return => {
              scale *= 0.9;
            },

            glutin::VirtualKeyCode::Back => {
              scale /= 0.9;
            },

            glutin::VirtualKeyCode::Z => {
              max += 1.0;
            },

            glutin::VirtualKeyCode::X => {
              max -= 1.0;
            },

            glutin::VirtualKeyCode::C => {
              if density > 0.002 {
                density *= 0.9
              }
            },

            glutin::VirtualKeyCode::V => {
              density /= 0.9;
            },

            glutin::VirtualKeyCode::Escape => {
              frame.finish().unwrap();
              return
            },

            _ => {}
          }

        },

        _ => break
      }
    }

    let uniforms = uniform! {
      max: max,
      scale: scale,
      center_y: center_y,
      center_x: center_x,
    };

    println!("{} {} {} {} {}", center_y, center_x, scale, max, density);

    frame.draw(&vertex_buffer, &index, &program, &uniforms, &Default::default()).unwrap();
    frame.finish().unwrap();
  }
}

Result

output.gif

うーん、GIFじゃ分かりづらいので手元で動かして見てもらいたいです

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7