Skip to content

Latest commit

 

History

History
223 lines (184 loc) · 7.05 KB

File metadata and controls

223 lines (184 loc) · 7.05 KB

Creating a Mandelbrot set visualization with Rust and WebAssembly involves using wasm-bindgen and web-sys to interact with the WebGL API. Here's how you can translate your Web Component into Rust and compile it to WebAssembly:

Step 1: Setup Rust Project

  1. Install Rust and the wasm32 target:

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
    rustup target add wasm32-unknown-unknown
  2. Install wasm-bindgen-cli:

    cargo install wasm-bindgen-cli
  3. Create a new Rust project:

    cargo new mandelbrot --lib
    cd mandelbrot
  4. Add dependencies in Cargo.toml:

    [dependencies]
    wasm-bindgen = "0.2"
    web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement", "WebGl2RenderingContext"] }
    
    [lib]
    crate-type = ["cdylib"]
    
    [profile.release]
    opt-level = "z"

Step 2: Write Rust Code

Create the src/lib.rs file with the following content:

use wasm_bindgen::prelude::*;
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext, WebGlProgram, WebGlShader};

#[wasm_bindgen(start)]
pub fn start() {
    let window = window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    let canvas = document.create_element("canvas").unwrap();
    canvas.set_id("mandelbrot-canvas");
    body.append_child(&canvas).unwrap();
    let canvas: HtmlCanvasElement = canvas.dyn_into::<HtmlCanvasElement>().unwrap();
    canvas.set_width(window.inner_width().unwrap().as_f64().unwrap() as u32);
    canvas.set_height(window.inner_height().unwrap().as_f64().unwrap() as u32);

    let gl: WebGl2RenderingContext = canvas
        .get_context("webgl2")
        .unwrap()
        .unwrap()
        .dyn_into::<WebGl2RenderingContext>()
        .unwrap();

    let vertex_shader = compile_shader(
        &gl,
        WebGl2RenderingContext::VERTEX_SHADER,
        r#"#version 300 es
        in vec4 a_position;
        void main() {
            gl_Position = a_position;
        }"#,
    )
    .unwrap();

    let fragment_shader = compile_shader(
        &gl,
        WebGl2RenderingContext::FRAGMENT_SHADER,
        r#"#version 300 es
        precision highp float;
        uniform float u_zoomFactor;
        uniform vec2 u_center;
        uniform vec2 u_resolution;
        out vec4 outColor;

        vec3 getColor(int iterations, int maxIterations) {
            float t = float(iterations) / float(maxIterations);
            float r = 9.0 * (1.0 - t) * t * t * t;
            float g = 15.0 * (1.0 - t) * (1.0 - t) * t * t;
            float b = 8.5 * (1.0 - t) * (1.0 - t) * (1.0 - t) * t;
            return vec3(r, g, b);
        }

        void main() {
            vec2 c = (gl_FragCoord.xy - u_resolution / 2.0) * 4.0 / (u_resolution * (u_zoomFactor * 0.5)) + u_center;
            vec2 z = vec2(0.0);
            int maxIterations = int(min(200.0, max(u_zoomFactor, 30.)));; // Dynamically adjust iterations
            int i;
            for (i = 0; i < maxIterations; i++) {
                vec2 z2 = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c;
                if (dot(z2, z2) > 4.0) break;
                z = z2;
            }
            outColor = vec4(getColor(i, maxIterations), 1.0);
        }"#,
    )
    .unwrap();

    let program = link_program(&gl, &vertex_shader, &fragment_shader).unwrap();
    gl.use_program(Some(&program));

    // Define the positions for the rectangle.
    let positions: [f32; 12] = [
        -1.0, -1.0,
        1.0, -1.0,
        -1.0, 1.0,
        -1.0, 1.0,
        1.0, -1.0,
        1.0, 1.0,
    ];

    let buffer = gl.create_buffer().unwrap();
    gl.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
    unsafe {
        let positions_array_buf_view = js_sys::Float32Array::view(&positions);
        gl.buffer_data_with_array_buffer_view(
            WebGl2RenderingContext::ARRAY_BUFFER,
            &positions_array_buf_view,
            WebGl2RenderingContext::STATIC_DRAW,
        );
    }

    let position_location = gl.get_attrib_location(&program, "a_position") as u32;
    gl.enable_vertex_attrib_array(position_location);
    gl.vertex_attrib_pointer_with_i32(position_location, 2, WebGl2RenderingContext::FLOAT, false, 0, 0);

    let zoom_factor_location = gl.get_uniform_location(&program, "u_zoomFactor").unwrap();
    let center_location = gl.get_uniform_location(&program, "u_center").unwrap();
    let resolution_location = gl.get_uniform_location(&program, "u_resolution").unwrap();

    gl.uniform1f(Some(&zoom_factor_location), 1.0);
    gl.uniform2f(Some(&center_location), -0.743643887037151, 0.131825904205330);
    gl.uniform2f(Some(&resolution_location), canvas.width() as f32, canvas.height() as f32);

    gl.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, 6);
}

fn compile_shader(
    gl: &WebGl2RenderingContext,
    shader_type: u32,
    source: &str,
) -> Result<WebGlShader, String> {
    let shader = gl.create_shader(shader_type).ok_or_else(|| String::from("Unable to create shader object"))?;
    gl.shader_source(&shader, source);
    gl.compile_shader(&shader);

    if gl.get_shader_parameter(&shader, WebGl2RenderingContext::COMPILE_STATUS).as_bool().unwrap_or(false) {
        Ok(shader)
    } else {
        Err(gl.get_shader_info_log(&shader).unwrap_or_else(|| String::from("Unknown error creating shader")))
    }
}

fn link_program(
    gl: &WebGl2RenderingContext,
    vertex_shader: &WebGlShader,
    fragment_shader: &WebGlShader,
) -> Result<WebGlProgram, String> {
    let program = gl.create_program().ok_or_else(|| String::from("Unable to create shader object"))?;
    gl.attach_shader(&program, vertex_shader);
    gl.attach_shader(&program, fragment_shader);
    gl.link_program(&program);

    if gl.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS).as_bool().unwrap_or(false) {
        Ok(program)
    } else {
        Err(gl.get_program_info_log(&program).unwrap_or_else(|| String::from("Unknown error creating program object")))
    }
}

Step 3: Build the Project

  1. Build the Rust project:

    cargo build --release --target wasm32-unknown-unknown
  2. Generate WebAssembly bindings:

    wasm-bindgen target/wasm32-unknown-unknown/release/mandelbrot.wasm --out-dir ./pkg --web

Step 4: Set Up HTML and JavaScript

Create an index.html file to load the WebAssembly module:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mandelbrot Set</title>
</head>
<body>
    <script type="module">
        import init from './pkg/mandelbrot.js';
        init();
    </script>
</body>
</html>

Step 5: Serve the Project

Use a simple HTTP server to serve your project. You can use http-server from npm:

npx http-server .

Open the URL provided by the server to see your Mandelbrot set visualization in action.

You now have a Rust and WebAssembly implementation of the Mandelbrot set!