プログラムでシダを描画する

　やっはろー。これは、[Rust Advent Calendar 2014](http://www.adventar.org/calendars/462) の 7日目の記事です。Rust から OpenGL を叩いてみたかったので、シダを描くことにしました。

　元ネタです。

> [プログラムでシダを描画する - 強火で進め](http://d.hatena.ne.jp/nakamura001/20140505/1399316565)

> [「プログラムでシダを描画する」一覧 - Qiita](http://qiita.com/qt-luigi/items/b22204c7ef73260324bd)

```rust
// rustc 0.13.0-nightly (d9c7c00b9 2014-12-04 21:33:07 +0000)

extern crate gl;
extern crate glfw;

use std::{mem, ptr, rand};
use gl::types::{GLboolean, GLfloat, GLsizeiptr};
use glfw::Context;

const WIDTH: uint = 512;
const HEIGHT: uint = 512;

static VERTEX_SOURCE: &'static str = "
  #version 150

  in vec2 position;
  out vec2 texcoord;

  void main() {
    texcoord = (vec2(position.x, -1.0 * position.y) + 1.0) / 2.0;
    gl_Position = vec4(position, 0.0, 1.0);
  }";

static FRAGMENT_SOURCE: &'static str = "
  #version 150

  uniform sampler2D tex;
  in vec2 texcoord;
  out vec4 color;

  void main() {
    if (0 < texture(tex, texcoord).r) {
      color = vec4(0.125, 0.502, 0.125, 1.0);
    } else {
      color = vec4(0.0);
    }
  }";

fn main() {
  let image = {
    static mut buffer: [u8, ..WIDTH * HEIGHT] = [0, ..WIDTH * HEIGHT];

    fn fern(n: uint, x: f64, y: f64) {
      fn w1x(x: f64, y: f64) -> f64 { 0.836 * x + 0.044 * y }
      fn w1y(x: f64, y: f64) -> f64 { -0.044 * x + 0.836 * y + 0.169 }
      fn w2x(x: f64, y: f64) -> f64 { -0.141 * x + 0.302 * y }
      fn w2y(x: f64, y: f64) -> f64 { 0.302 * x + 0.141 * y + 0.127 }
      fn w3x(x: f64, y: f64) -> f64 { 0.141 * x - 0.302 * y }
      fn w3y(x: f64, y: f64) -> f64 { 0.302 * x + 0.141 * y + 0.169 }
      fn w4x(_: f64, _: f64) -> f64 { 0.0 }
      fn w4y(_: f64, y: f64) -> f64 { 0.175337 * y }

      if 0 < n {
        fern(n - 1, w1x(x, y), w1y(x, y));
        if rand::random::<f64>() < 0.3 { fern(n - 1, w2x(x, y), w2y(x, y)); } 
        if rand::random::<f64>() < 0.3 { fern(n - 1, w3x(x, y), w3y(x, y)); } 
        if rand::random::<f64>() < 0.3 { fern(n - 1, w4x(x, y), w4y(x, y)); } 
      } else {
        let x = (x * WIDTH as f64 + WIDTH as f64 * 0.5) as uint;
        let y = (HEIGHT as f64 - y * HEIGHT as f64) as uint;
        unsafe { buffer[x + y * HEIGHT] = 1; }
      }
    }

    fern(20, 0.0, 0.0);
    unsafe { buffer.to_vec() }
  };

  unsafe {
    let glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
    glfw.window_hint(glfw::WindowHint::ContextVersion(3, 2));
    glfw.window_hint(glfw::WindowHint::OpenglForwardCompat(true));
    glfw.window_hint(glfw::WindowHint::OpenglProfile(glfw::OpenGlProfileHint::Core));

    let (window, _) =
      glfw.create_window(WIDTH as u32, HEIGHT as u32, "fern", glfw::WindowMode::Windowed).unwrap();
    window.make_current();
    gl::load_with(|s| window.get_proc_address(s));

    let program = gl::CreateProgram();
    for &(type_, source) in [(gl::VERTEX_SHADER, VERTEX_SOURCE),
                             (gl::FRAGMENT_SHADER, FRAGMENT_SOURCE)].iter() {
      let shader = gl::CreateShader(type_);
      source.with_c_str(|source| gl::ShaderSource(shader, 1, &source, ptr::null()));
      gl::CompileShader(shader);
      gl::AttachShader(program, shader);
      gl::DeleteShader(shader);
    }
    gl::LinkProgram(program);
    gl::UseProgram(program);
    "color".with_c_str(|p| gl::BindFragDataLocation(program, 0, p));
    "tex".with_c_str(|p| gl::Uniform1i(gl::GetUniformLocation(program, p), 0));
    gl::UseProgram(0);

    let mut array = 0;
    gl::GenVertexArrays(1, &mut array);
    gl::BindVertexArray(array);

    let position = [-1.0f32, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0];
    let mut buffer = 0;
    gl::GenBuffers(1, &mut buffer);
    gl::BindBuffer(gl::ARRAY_BUFFER, buffer);
    gl::BufferData(gl::ARRAY_BUFFER,
                   (position.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
                   mem::transmute(position.as_ptr()),
                   gl::STATIC_DRAW);
    "position".with_c_str(|p| {
      let location = gl::GetAttribLocation(program, p) as u32;
      gl::EnableVertexAttribArray(location);
      gl::VertexAttribPointer(location, 2, gl::FLOAT, gl::FALSE as GLboolean, 0, ptr::null());
    });
    gl::BindBuffer(gl::ARRAY_BUFFER, 0);
    gl::BindVertexArray(0);

    let mut texture = 0;
    gl::GenTextures(1, &mut texture);
    gl::BindTexture(gl::TEXTURE_2D, texture);
    gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
    gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
    gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as i32,
                   WIDTH as i32, HEIGHT as i32,
                   0, gl::RED, gl::UNSIGNED_BYTE, mem::transmute(image.as_ptr()));
    gl::BindTexture(gl::TEXTURE_2D, 0);

    gl::ClearColor(1.0, 1.0, 1.0, 1.0);
    gl::Enable(gl::BLEND);
    gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);

    while !window.should_close() {
      glfw.poll_events();

      gl::Clear(gl::COLOR_BUFFER_BIT);

      gl::UseProgram(program);
      gl::BindTexture(gl::TEXTURE_2D, texture);
      gl::BindVertexArray(array);

      gl::DrawArrays(gl::TRIANGLE_STRIP, 0, 4);

      gl::BindVertexArray(0);
      gl::BindTexture(gl::TEXTURE_2D, 0);
      gl::UseProgram(0);

      window.swap_buffers();
    }

    gl::DeleteTextures(1, &texture);
    gl::DeleteBuffers(1, &buffer);
    gl::DeleteVertexArrays(1, &array);
    gl::DeleteProgram(program);
  }
}
```

![シダ](https://qiita-image-store.s3.amazonaws.com/0/4250/92ef4595-52bb-475e-04cb-40fcee623464.png)

楽しい！₍₍ (ง╹◡╹)ว ⁾⁾

> [床井研究室](http://marina.sys.wakayama-u.ac.jp/~tokoi/)

> [wgld.org](http://wgld.org/)

> [bjz/gl-rs - GitHub](https://github.com/bjz/gl-rs)

> [bjz/glfw-rs - GitHub](https://github.com/bjz/glfw-rs)