Created
February 24, 2020 01:41
-
-
Save sbowman/eb277f162b4890346fe704653b4c2a53 to your computer and use it in GitHub Desktop.
Replacement for Rust rltk::field_of_view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use super::Map; | |
use super::rltk::{BaseMap, Point}; | |
// Transform represents a sector to shadow cast. | |
struct Transform { | |
xx: i32, | |
xy: i32, | |
yx: i32, | |
yy: i32, | |
} | |
// Viewer holds information about the location and viewing radius of the character we're | |
// calculating the FOV for. | |
struct Viewer { | |
x: i32, | |
y: i32, | |
radius: i32, | |
} | |
// Calculates the field of view on the map using shadow casting. See: | |
// | |
// * http://www.roguebasin.com/index.php?title=FOV_using_recursive_shadowcasting | |
// * http://www.roguebasin.com/index.php?title=C%2B%2B_shadowcasting_implementation | |
// | |
// Returns the vector of points visible to the viewer located at (x, y) with the given viewing | |
// radius. | |
pub fn calculate(x: i32, y: i32, radius: i32, map: &Map) -> Vec<rltk::Point> { | |
let transforms: Vec<Transform> = vec![ | |
Transform { xx: 1, xy: 0, yx: 0, yy: 1 }, | |
Transform { xx: 0, xy: 1, yx: 1, yy: 0 }, | |
Transform { xx: 0, xy: -1, yx: 1, yy: 0 }, | |
Transform { xx: -1, xy: 0, yx: 0, yy: 1 }, | |
Transform { xx: -1, xy: 0, yx: 0, yy: -1 }, | |
Transform { xx: 0, xy: -1, yx: -1, yy: 0 }, | |
Transform { xx: 0, xy: 1, yx: -1, yy: 0 }, | |
Transform { xx: 1, xy: 0, yx: 0, yy: -1 }, | |
]; | |
let viewer = Viewer { | |
x, | |
y, | |
radius, | |
}; | |
let mut visible: Vec<rltk::Point> = Vec::new(); | |
// The viewer's location is always visible | |
visible.push(rltk::Point{x, y}); | |
for transform in transforms { | |
cast_light(&mut visible, map, &viewer, 1, 1.0, 0.0, &transform); | |
} | |
visible | |
} | |
fn cast_light(visible: &mut Vec<rltk::Point>, map: &Map, viewer: &Viewer, row: i32, start_slope: f32, end_slope: f32, transform: &Transform) { | |
if start_slope < end_slope { | |
return; | |
} | |
let radius_sq = viewer.radius * viewer.radius; | |
let mut start_slope = start_slope; | |
let mut next_start_slope = start_slope; | |
for i in row..=viewer.radius { | |
let mut blocked = false; | |
let dy = -i; | |
for dx in -i..=0 { | |
let left_slope = (dx as f32 - 0.5) / (dy as f32 + 0.5); | |
let right_slope = (dx as f32 + 0.5) / (dy as f32 - 0.5); | |
if start_slope < right_slope { | |
continue; | |
} | |
if end_slope > left_slope { | |
break; | |
} | |
let sax = dx * transform.xx + dy * transform.xy; | |
let say = dx * transform.yx + dy * transform.yy; | |
if (sax < 0 && sax.abs() > viewer.x) || (say < 0 && say.abs() > viewer.y) { | |
continue; | |
} | |
let ax = viewer.x + sax; | |
let ay = viewer.y + say; | |
if ax >= map.width || ay >= map.height { | |
continue; | |
} | |
if dx * dx + dy * dy < radius_sq { | |
visible.push(rltk::Point{x: ax, y: ay}); | |
} | |
if blocked { | |
if map.is_opaque(map.xy_idx(ax, ay)) { | |
next_start_slope = right_slope; | |
continue; | |
} | |
blocked = false; | |
start_slope = next_start_slope; | |
continue | |
} | |
if map.is_opaque(map.xy_idx(ax, ay)) { | |
blocked = true; | |
next_start_slope = right_slope; | |
cast_light(visible, map, viewer, row + 1, start_slope, left_slope, transform); | |
} | |
} | |
if blocked { | |
break; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I was getting strange results using the
rltk::field_of_view
call describe in http://bfnightly.bracketproductions.com/rustbook/chapter_5.html, so I wrote my own based on the shadow casting algorithm on Roguebasin. Use it by calling:in place of the
rltk::field_of_view
call, and removing theviewshed.visible_tiles.retain
line.