diff options
Diffstat (limited to '')
| -rw-r--r-- | src/renderer.rs | 117 |
1 files changed, 69 insertions, 48 deletions
diff --git a/src/renderer.rs b/src/renderer.rs index aa87b54..885e5de 100644 --- a/src/renderer.rs +++ b/src/renderer.rs | |||
| @@ -74,6 +74,18 @@ pub struct Renderer { | |||
| 74 | floor_dist_lut: Vec<f64>, | 74 | floor_dist_lut: Vec<f64>, |
| 75 | } | 75 | } |
| 76 | 76 | ||
| 77 | // ---- entity sprites --------------------------------------------------- | ||
| 78 | |||
| 79 | /// Screen-space info for an entity sprite to render. | ||
| 80 | struct Sprite { | ||
| 81 | sx: isize, | ||
| 82 | sy: isize, | ||
| 83 | scale: f64, | ||
| 84 | ch: char, | ||
| 85 | color: u32, | ||
| 86 | cam_z: f64, | ||
| 87 | } | ||
| 88 | |||
| 77 | impl Renderer { | 89 | impl Renderer { |
| 78 | pub fn new(screen_w: usize, screen_h: usize) -> Self { | 90 | pub fn new(screen_w: usize, screen_h: usize) -> Self { |
| 79 | let half_h = screen_h as f64 / 2.0; | 91 | let half_h = screen_h as f64 / 2.0; |
| @@ -306,19 +318,9 @@ impl Renderer { | |||
| 306 | ) { | 318 | ) { |
| 307 | let mw = game.maze.width(); | 319 | let mw = game.maze.width(); |
| 308 | let mh = game.maze.height(); | 320 | let mh = game.maze.height(); |
| 309 | |||
| 310 | // Collect visible entities with their screen info. | ||
| 311 | struct Sprite { | ||
| 312 | sx: isize, | ||
| 313 | sy: isize, | ||
| 314 | scale: f64, | ||
| 315 | ch: char, | ||
| 316 | color: u32, | ||
| 317 | cam_z: f64, | ||
| 318 | } | ||
| 319 | |||
| 320 | let mut sprites: Vec<Sprite> = Vec::new(); | 321 | let mut sprites: Vec<Sprite> = Vec::new(); |
| 321 | 322 | ||
| 323 | // Static entities on the grid. | ||
| 322 | for y in 0..mh { | 324 | for y in 0..mh { |
| 323 | for x in 0..mw { | 325 | for x in 0..mw { |
| 324 | let entity = match game.maze.entity_at(x, y) { | 326 | let entity = match game.maze.entity_at(x, y) { |
| @@ -329,46 +331,15 @@ impl Renderer { | |||
| 329 | let ex = x as f64 + 0.5; | 331 | let ex = x as f64 + 0.5; |
| 330 | let ey = y as f64 + 0.5; | 332 | let ey = y as f64 + 0.5; |
| 331 | 333 | ||
| 332 | let dx = ex - game.player.x; | 334 | self.push_entity_sprite(game, ex, ey, entity, fov, half_h, &mut sprites); |
| 333 | let dy = ey - game.player.y; | ||
| 334 | |||
| 335 | let fx = game.player.angle.cos(); | ||
| 336 | let fy = game.player.angle.sin(); | ||
| 337 | let rx = -fy; | ||
| 338 | let ry = fx; | ||
| 339 | |||
| 340 | let cam_z = dx * fx + dy * fy; | ||
| 341 | if cam_z <= 0.1 { | ||
| 342 | continue; | ||
| 343 | } | ||
| 344 | |||
| 345 | let cam_x = dx * rx + dy * ry; | ||
| 346 | |||
| 347 | let half_fov_tan = (fov / 2.0).tan(); | ||
| 348 | let screen_x_norm = cam_x / (cam_z * half_fov_tan); | ||
| 349 | if screen_x_norm < -1.0 || screen_x_norm > 1.0 { | ||
| 350 | continue; | ||
| 351 | } | ||
| 352 | |||
| 353 | let sx = ((screen_x_norm + 1.0) * 0.5 * self.screen_w as f64) as isize; | ||
| 354 | |||
| 355 | let entity_h = (self.screen_h as f64 / cam_z * 0.4) as isize; | ||
| 356 | let sy = (half_h - entity_h as f64 * 0.3) as isize; | ||
| 357 | |||
| 358 | let scale = (entity_h.max(6) as f64 / LETTER_H as f64).max(0.5); | ||
| 359 | let (ch, color) = entity_char_and_color(entity); | ||
| 360 | |||
| 361 | sprites.push(Sprite { | ||
| 362 | sx, | ||
| 363 | sy, | ||
| 364 | scale, | ||
| 365 | ch, | ||
| 366 | color, | ||
| 367 | cam_z, | ||
| 368 | }); | ||
| 369 | } | 335 | } |
| 370 | } | 336 | } |
| 371 | 337 | ||
| 338 | // Animating enemies (mid-move, not on the grid). | ||
| 339 | for (wx, wy, entity) in game.enemy_render_entities() { | ||
| 340 | self.push_entity_sprite(game, wx, wy, &entity, fov, half_h, &mut sprites); | ||
| 341 | } | ||
| 342 | |||
| 372 | // Sort farthest first so nearby entities draw on top. | 343 | // Sort farthest first so nearby entities draw on top. |
| 373 | sprites.sort_by(|a, b| b.cam_z.partial_cmp(&a.cam_z).unwrap()); | 344 | sprites.sort_by(|a, b| b.cam_z.partial_cmp(&a.cam_z).unwrap()); |
| 374 | 345 | ||
| @@ -387,6 +358,56 @@ impl Renderer { | |||
| 387 | } | 358 | } |
| 388 | } | 359 | } |
| 389 | 360 | ||
| 361 | /// Project a world-space entity position and push a Sprite if on screen. | ||
| 362 | fn push_entity_sprite( | ||
| 363 | &self, | ||
| 364 | game: &Game, | ||
| 365 | ex: f64, | ||
| 366 | ey: f64, | ||
| 367 | entity: &Entity, | ||
| 368 | fov: f64, | ||
| 369 | half_h: f64, | ||
| 370 | sprites: &mut Vec<Sprite>, | ||
| 371 | ) { | ||
| 372 | let dx = ex - game.player.x; | ||
| 373 | let dy = ey - game.player.y; | ||
| 374 | |||
| 375 | let fx = game.player.angle.cos(); | ||
| 376 | let fy = game.player.angle.sin(); | ||
| 377 | let rx = -fy; | ||
| 378 | let ry = fx; | ||
| 379 | |||
| 380 | let cam_z = dx * fx + dy * fy; | ||
| 381 | if cam_z <= 0.1 { | ||
| 382 | return; | ||
| 383 | } | ||
| 384 | |||
| 385 | let cam_x = dx * rx + dy * ry; | ||
| 386 | |||
| 387 | let half_fov_tan = (fov / 2.0).tan(); | ||
| 388 | let screen_x_norm = cam_x / (cam_z * half_fov_tan); | ||
| 389 | if screen_x_norm < -1.0 || screen_x_norm > 1.0 { | ||
| 390 | return; | ||
| 391 | } | ||
| 392 | |||
| 393 | let sx = ((screen_x_norm + 1.0) * 0.5 * self.screen_w as f64) as isize; | ||
| 394 | |||
| 395 | let entity_h = (self.screen_h as f64 / cam_z * 0.4) as isize; | ||
| 396 | let sy = (half_h - entity_h as f64 * 0.3) as isize; | ||
| 397 | |||
| 398 | let scale = (entity_h.max(6) as f64 / LETTER_H as f64).max(0.5); | ||
| 399 | let (ch, color) = entity_char_and_color(entity); | ||
| 400 | |||
| 401 | sprites.push(Sprite { | ||
| 402 | sx, | ||
| 403 | sy, | ||
| 404 | scale, | ||
| 405 | ch, | ||
| 406 | color, | ||
| 407 | cam_z, | ||
| 408 | }); | ||
| 409 | } | ||
| 410 | |||
| 390 | fn draw_char_scaled( | 411 | fn draw_char_scaled( |
| 391 | &self, | 412 | &self, |
| 392 | buffer: &mut [u32], | 413 | buffer: &mut [u32], |
