1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use super::{Framebuffer, Map, PI, Player, RGB, Sprite, Sprites, Texture};

const BLUE: RGB = RGB{r:0,g:50,b:200,a:255};
const BLACK: RGB = RGB{r:0,g:0,b:0,a:255};
const GRAY: RGB = RGB{r:150,g:150,b:150,a:255};
const DARK_GRAY: RGB = RGB{r:20,g:20,b:20,a:255};
const RED: RGB = RGB{r:255,g:0,b:0,a:255};

pub fn render(mut framebuffer: &mut Framebuffer,
              map: &mut Map, player: &mut Player,
              sprites: &mut Sprites, mon_textures: &Texture,
              wall_textures: &Texture) {
    framebuffer.clear(RGB{r: 255, g: 255, b: 255, a: 255});

    let mut z_buffer: Vec<f64> = vec![1e3; framebuffer.width];

    // Render walls and cone
    let middle = framebuffer.height as f64 / 2.;
    for i in 0..framebuffer.width {
        let ray_angle: f64 = (1. - i as f64 / framebuffer.width as f64) * (player.direction - player.fov / 2.) + i as f64 / framebuffer.width as f64 * (player.direction + player.fov / 2.);
        let x_offset = ray_angle.cos();
        let y_offset = ray_angle.sin();

        let mut distance = 0.0;
        let mut projected_distance = 0.0;
        while distance <= 40.0 {
            distance += 0.02;

            let ray_x: f64 = player.x + distance * x_offset;
            let ray_y: f64 = player.y + distance * y_offset;

            let x = cap_f64(ray_x, 15);
            let y = cap_f64(ray_y, 15);

            match map.is_empty(x, y) {
                // Cone, Ceiling, Floor
                true => {
                    ()
                },
                // Walls
                false => {
                    projected_distance = distance * (ray_angle - player.direction).cos();
                    let wall_height = (framebuffer.height as f64 / projected_distance);

                    z_buffer[i] = projected_distance;

                    let local_ray_x = ray_x - (ray_x + 0.5).floor();
                    let local_ray_y = ray_y - (ray_y + 0.5).floor();

                    let texture_id = map.get(x, y);
                    let texture_x: isize = wall_x_texture_coord(ray_x, ray_y, &wall_textures);
                    let column: Vec<RGB> = wall_textures.scale_column(texture_id,
                        texture_x as usize, wall_height as usize);

                    let wall_top = middle - wall_height / 2.;
                    let wall_bottom = wall_top + wall_height;

                    for j in 0..framebuffer.height as isize {
                        if j < wall_top as isize {
                            let mut blue = BLUE.clone();
                            let multiplier = (150. * ( j as f64 / framebuffer.height as f64 * 2.)) as u8;
                            blue.r = multiplier;
                            blue.g += multiplier;
                            framebuffer.set_pixel(i, j as usize, blue);
                        } else if j >= wall_bottom as isize {
                            let mut gray = DARK_GRAY.clone();
                            let multiplier = (50. * ( j as f64 / framebuffer.height as f64 * 2.)) as u8;
                            gray.r += multiplier;
                            gray.g += multiplier;
                            gray.b += multiplier;
                            framebuffer.set_pixel(i, j as usize, gray);
                        } else {
                            let texture_y = (j as f64 - wall_top) / wall_height * column.len() as f64;
                            framebuffer.set_pixel(i, j as usize, column[texture_y as usize]);
                        }
                    }
                    break
                },
            }
        }
    }

    // Show sprites on map and render to screen
    update_sprites(sprites, &player);
    for sprite in sprites {
        //render_sprite_to_map(&sprite, &mut framebuffer, &map);
        render_sprite_to_screen(&sprite, &mon_textures, &z_buffer, &mut framebuffer, &player);
    }

}

fn update_sprites(sprites: &mut Sprites, player: &Player) {
    for i in 0..sprites.len() {
        sprites[i].player_distance = ((player.x - sprites[i].x).powf(2.)
                                    + (player.y - sprites[i].y).powf(2.)).sqrt();
    }
    sprites.sort_by(|a, b|
        b.player_distance.partial_cmp(&a.player_distance).unwrap());
}

fn render_sprite_to_map(sprite: &Sprite, framebuffer: &mut Framebuffer, map: &Map) {
    let rect_w = framebuffer.width / ( map.width * 2 );
    let rect_h = framebuffer.height / map.height;
    let x = (sprite.x * rect_w as f64 - 3.) as usize;
    let y = (sprite.y * rect_h as f64 - 3.) as usize;
    framebuffer.draw_rect(x, y, 5, 5, RED);
}

fn render_sprite_to_screen(sprite: &Sprite, mon_textures: &Texture, z_buffer: &Vec<f64>,
                           framebuffer: &mut Framebuffer, player: &Player) {

    let mut sprite_direction: f64 = (sprite.y - player.y).atan2(sprite.x - player.x);
    while sprite_direction - player.direction > PI { sprite_direction -= 2.*PI; };
    while sprite_direction - player.direction < -PI { sprite_direction += 2.*PI; };

    let sprite_screen_size: isize = 1000isize.min((framebuffer.height as f64 / sprite.player_distance) as isize);

    let v_offset: isize = (framebuffer.height as f64 / 2. - sprite_screen_size as f64 / 2.) as isize;
    let h_offset: isize = ((sprite_direction - player.direction)
                        / player.fov * (framebuffer.width as f64 / 2.0)
                        + (framebuffer.width as f64 / 2.0) / 2.0
                        - mon_textures.size as f64 / 2.0) as isize;

    for i in 0..sprite_screen_size {
        if h_offset + i < 0 || h_offset + i >= framebuffer.width as isize { continue; }
        if z_buffer[(h_offset + i) as usize] < sprite.player_distance { continue; }
        for j in 0..sprite_screen_size {
            if v_offset + j < 0 || v_offset + j >= framebuffer.height as isize { continue; }

            let tex_x: usize = i as usize * mon_textures.size / sprite_screen_size as usize;
            let tex_y: usize = j  as usize* mon_textures.size / sprite_screen_size as usize;
            let color: RGB = *mon_textures.get(tex_x, tex_y, sprite.texture_id);

            let x: usize = (framebuffer.width as f64 + h_offset as f64 + i as f64) as usize;
            let y: usize = (v_offset + j) as usize;

            if color.a > 128 { framebuffer.set_pixel(x, y, color); }
        }
    }
}


fn cap_f64(n: f64, capped: usize) -> usize {
    match n as usize {
        x if x > capped => capped,
        _ => n as usize,
    }
}

fn wall_x_texture_coord(x: f64, y: f64, textures: &Texture) -> isize {
    let hit_x: f64 = x - (x + 0.5).floor();
    let hit_y: f64 = y - (y + 0.5).floor();

    let mut texture_x: isize = (hit_x * textures.size as f64) as isize;
    if hit_y.abs() > hit_x.abs() {
        texture_x = (hit_y * textures.size as f64) as isize;
    };
    if texture_x < 0 {
        texture_x += textures.size as isize;
    };
    texture_x
}