Skip to main content
Jeremias Meister - Tools & Pipeline
Heightmap Generator

Heightmap Generator

A Rust + Slint tool for procedurally generating and eroding heightmaps.

Overview

In the very beginnings of me, learning rust, I chose a Heightmap generator as a learning project. The goal was less the visual output than to learn how to construct a GUI with the help of Slint and get a little bit more into the concept of rust. I got carried away with the little improvements, but I’m happy with the output overall.

To display the terrain in 3d, I made a little CPU based edge renderer rendering a displaced plane into an image and embedding it into the GUI.

My role: Sole author; a personal project to deepen my Rust and Slint GUI skills.

Demo

Features

Deep Dive

Basic Buffer blending:

pub fn blend_buffers(buffer_a: &ImageBuffer<Rgba<u8>, Vec<u8>>, buffer_b: &ImageBuffer<Rgba<u8>, Vec<u8>>, blend_mode: i32) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
    let (width, height) = buffer_a.dimensions();
    
    ImageBuffer::from_fn(width, height, |x, y| {
        let pixel_a = buffer_a.get_pixel(x, y);
        let pixel_b = buffer_b.get_pixel(x, y);
        
        let alpha_a = pixel_a[3] as f32 / 255.0;
        let alpha_b = pixel_b[3] as f32 / 255.0;
        
        let new_alpha = alpha_a * (1.0 - alpha_b) + alpha_b;
        
        let mut blended_pixel = [0u8; 4];
        
        for i in 0..3 {
            let channel_a = pixel_a[i] as f32 / 255.0;
            let channel_b = pixel_b[i] as f32 / 255.0;
            
            match blend_mode {
                // Blend
                0 => {
                    blended_pixel[i] = ((channel_a * (1.0 - alpha_b) + channel_b * alpha_b) * 255.0).min(255.0) as u8;
                },
                // Multiply                
                1 => {
                    let blended_channel = channel_a * (1.0 - alpha_b) + channel_a * channel_b * alpha_b; // Interpolate based on alpha_b
                    blended_pixel[i] = (blended_channel * 255.0).min(255.0) as u8;
                },
                //
                2 => {
                    blended_pixel[i] = ((channel_a * alpha_a + channel_b * alpha_b) * 255.0).min(255.0) as u8;
                },
                _ => {
                    panic!("Invalid blend mode");
                }
            }
        }
        
        blended_pixel[3] = (new_alpha * 255.0).min(255.0) as u8;
        
        Rgba(blended_pixel)
    })
}

The thermal erosion:

pub fn thermal_erosion(
    heightmap: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
    colormap: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
    iterations: usize,
    talus_angle: f32,
    erosion_mode: i32,
) {
    let (width, height) = heightmap.dimensions();

    for _ in 0..iterations {
        let temp_heightmap = heightmap.clone(); // Temporary heightmap to store updates
        let temp_colormap = colormap.clone(); // Temporary colormap to store updates

        for y in 1..(height - 1) {
            for x in 1..(width - 1) {
                let center_pixel = temp_heightmap.get_pixel(x, y);
                let mut changed = false;

                for dx in -1..=1 {
                    for dy in -1..=1 {
                        if dx == 0 && dy == 0 {
                            continue;
                        }

                        let neighbor_pixel = temp_heightmap.get_pixel((x as i32 + dx) as u32, (y as i32 + dy) as u32);
                        let neighbor_color = temp_colormap.get_pixel((x as i32 + dx) as u32, (y as i32 + dy) as u32);

                        if should_erode(*center_pixel, *neighbor_pixel, talus_angle, erosion_mode) {
                            // Update both heightmap and colormap
                            heightmap.put_pixel(x, y, *neighbor_pixel);
                            colormap.put_pixel(x, y, *neighbor_color);
                            changed = true;
                            break;
                        }
                    }
                    if changed {
                        break;
                    }
                }
            }
        }
    }
}