use rustial_engine::{
Camera, CameraConstraints, CameraMode, CameraProjection, GeoCoord, LayerStack, MapState,
TerrainConfig,
};
pub struct MapBuilder {
camera: Camera,
constraints: CameraConstraints,
layers: LayerStack,
terrain_config: Option<TerrainConfig>,
terrain_cache_size: usize,
requested_zoom: Option<u8>,
}
impl MapBuilder {
pub fn new() -> Self {
Self {
camera: Camera::default(),
constraints: CameraConstraints::default(),
layers: LayerStack::new(),
terrain_config: None,
terrain_cache_size: 256,
requested_zoom: None,
}
}
pub fn center(mut self, lat: f64, lon: f64) -> Self {
self.camera.set_target(GeoCoord::from_lat_lon(lat, lon));
self
}
pub fn zoom(mut self, zoom: u8) -> Self {
self.requested_zoom = Some(zoom.min(22));
self
}
pub fn pitch(mut self, pitch: f64) -> Self {
self.camera.set_pitch(pitch);
self
}
pub fn yaw(mut self, yaw: f64) -> Self {
self.camera.set_yaw(yaw);
self
}
pub fn fov_y(mut self, radians: f64) -> Self {
self.camera.set_fov_y(radians);
self
}
pub fn projection(mut self, projection: CameraProjection) -> Self {
self.camera.set_projection(projection);
self
}
pub fn mode(mut self, mode: CameraMode) -> Self {
self.camera.set_mode(mode);
self
}
pub fn viewport(mut self, width: u32, height: u32) -> Self {
self.camera.set_viewport(width, height);
self
}
pub fn constraints(mut self, constraints: CameraConstraints) -> Self {
self.constraints = constraints;
self
}
pub fn layers(mut self, layers: LayerStack) -> Self {
self.layers = layers;
self
}
pub fn terrain(mut self, config: TerrainConfig) -> Self {
self.terrain_config = Some(config);
self
}
pub fn terrain_cache_size(mut self, size: usize) -> Self {
self.terrain_cache_size = size;
self
}
pub fn build(self) -> MapState {
let mut layers = self.layers;
let terrain_config = self.terrain_config.unwrap_or_default();
let mut state = MapState::with_terrain(terrain_config, self.terrain_cache_size);
state.set_camera_target(*self.camera.target());
state.set_camera_projection(self.camera.projection());
state.set_camera_distance(self.camera.distance());
state.set_camera_pitch(self.camera.pitch());
state.set_camera_yaw(self.camera.yaw());
state.set_camera_fov_y(self.camera.fov_y());
state.set_camera_mode(self.camera.mode());
state.set_viewport(self.camera.viewport_width(), self.camera.viewport_height());
state.set_max_pitch(self.constraints.max_pitch);
state.set_min_distance(self.constraints.min_distance);
state.set_max_distance(self.constraints.max_distance);
if let Some(z) = self.requested_zoom {
const C: f64 = 2.0 * std::f64::consts::PI * 6_378_137.0;
const TILE_PX: f64 = 256.0;
let target_mpp = C / (TILE_PX * (1u64 << z) as f64);
let vh = state.camera().viewport_height().max(1) as f64;
let distance = match state.camera().mode() {
CameraMode::Perspective => {
target_mpp * vh / (2.0 * (state.camera().fov_y() / 2.0).tan())
}
CameraMode::Orthographic => target_mpp * vh / 2.0,
};
state.set_camera_distance(distance.clamp(
state.constraints().min_distance,
state.constraints().max_distance,
));
}
while let Some(layer) = layers.remove(0) {
state.push_layer(layer);
}
state
}
}
impl Default for MapBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_default() {
let state = MapBuilder::new().build();
assert_eq!(state.zoom_level(), 0);
}
#[test]
fn builder_with_center_and_zoom() {
let mut state = MapBuilder::new()
.center(51.1, 17.0)
.zoom(10)
.viewport(1920, 1080)
.build();
state.update();
assert!((state.camera().target().lat - 51.1).abs() < 1e-6);
assert!((state.camera().target().lon - 17.0).abs() < 1e-6);
assert!(state.zoom_level() >= 9 && state.zoom_level() <= 11);
}
#[test]
fn builder_transfers_layers() {
use rustial_engine::BackgroundLayer;
let mut layers = LayerStack::new();
layers.push(Box::new(BackgroundLayer::new("bg", [0.2, 0.3, 0.4, 1.0])));
let state = MapBuilder::new().layers(layers).build();
assert_eq!(state.layers().len(), 1);
assert_eq!(state.background_color(), Some([0.2, 0.3, 0.4, 1.0]));
}
#[test]
fn builder_transfers_camera_projection() {
let state = MapBuilder::new()
.projection(CameraProjection::Equirectangular)
.build();
assert_eq!(
state.camera().projection(),
CameraProjection::Equirectangular
);
}
}