| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- use cgmath::{Matrix3, Point3, Transform, vec3};
- use coord::{LatLonRad, ScreenCoord, TextureRect, TileCoord};
- use map_view::MapView;
- use std::collections::HashSet;
- use std::f64::consts::{PI, FRAC_1_PI};
- use std::f64;
-
-
- #[derive(Clone, Debug)]
- pub struct VisibleTile {
- pub tile: TileCoord,
- }
-
- impl From<TileCoord> for VisibleTile {
- fn from(tc: TileCoord) -> Self {
- VisibleTile {
- tile: tc,
- }
- }
- }
-
- #[derive(Clone, Debug)]
- pub struct TexturedVisibleTile {
- pub tile_coord: TileCoord,
- pub tex_rect: TextureRect,
- pub tex_minmax: TextureRect,
- }
-
-
- #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
- pub enum TileNeighbor {
- Coord(TileCoord),
- NorthPole,
- SouthPole,
- }
-
- /// Tile neighbors using sphere topology
- pub fn tile_neighbors(origin: TileCoord, result: &mut Vec<TileNeighbor>) {
- result.clear();
-
- let zoom_level_tiles = TileCoord::get_zoom_level_tiles(origin.zoom);
-
- if origin.y < 0 || origin.y >= zoom_level_tiles {
- // Tile is out of bounds
- return;
- }
-
- // Normalize x coordinate
- let origin = TileCoord {
- zoom: origin.zoom,
- x: ((origin.x % zoom_level_tiles) + zoom_level_tiles) % zoom_level_tiles,
- y: origin.y,
- };
-
- match (origin.zoom, origin.y) {
- (0, _) => {},
- (1, _) => {
- result.extend(&[
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + 1) % zoom_level_tiles,
- origin.y)
- ),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- origin.x,
- (origin.y + 1) % zoom_level_tiles),
- ),
- ]);
- },
- (_, 0) => {
- result.extend(&[
- TileNeighbor::NorthPole,
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- origin.x,
- origin.y + 1,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + 1) % zoom_level_tiles,
- origin.y,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
- origin.y,
- )),
- ]);
- },
- (_, y) if y == zoom_level_tiles - 1 => {
- result.extend(&[
- TileNeighbor::SouthPole,
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- origin.x,
- origin.y - 1,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + 1) % zoom_level_tiles,
- origin.y,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
- origin.y,
- )),
- ]);
- },
- _ => {
- result.extend(&[
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- origin.x,
- origin.y + 1,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- origin.x,
- origin.y - 1,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + 1) % zoom_level_tiles,
- origin.y,
- )),
- TileNeighbor::Coord(TileCoord::new(
- origin.zoom,
- (origin.x + zoom_level_tiles - 1) % zoom_level_tiles,
- origin.y,
- )),
- ]);
- },
- }
- }
-
-
- #[derive(Clone, Debug)]
- pub struct OrthograficView {
- }
-
- impl OrthograficView {
- /// Returns true if the rendering covers the whole viewport.
- pub fn covers_viewport(map_view: &MapView) -> bool {
- let sphere_diameter = 2.0f64.powf(map_view.zoom) *
- (f64::consts::FRAC_1_PI * f64::from(map_view.tile_size));
-
- // Add a little safety margin (the constant factor) since the rendered globe is not a
- // perfect sphere and its screen area is underestimated by the tesselation.
- map_view.width.hypot(map_view.height) < sphere_diameter * 0.9
- }
-
- /// Returns the tile zoom value that is used for rendering with the current zoom.
- //TODO Insert real implementation. Add TileCoord parameter -> lower resolution at the poles
- pub fn tile_zoom(map_view: &MapView) -> u32 {
- (map_view.zoom + map_view.tile_zoom_offset).floor().max(0.0) as u32
- }
-
- //TODO Return the transformation matrix that is used here to avoid redundant calculation.
- /// Returns a `Vec` of all tiles that are visible in the current viewport.
- pub fn visible_tiles(map_view: &MapView) -> Vec<VisibleTile> {
- let uzoom = Self::tile_zoom(map_view);
-
- match uzoom {
- 0 => return vec![TileCoord::new(0, 0, 0).into()],
- 1 => {
- // return every tile
- return vec![
- TileCoord::new(1, 0, 0).into(),
- TileCoord::new(1, 0, 1).into(),
- TileCoord::new(1, 1, 0).into(),
- TileCoord::new(1, 1, 1).into(),
- ]},
- _ => {},
- }
-
- let center_tile = map_view.center.on_tile_at_zoom(uzoom).nearest_valid();
-
- let transform = Self::transformation_matrix(map_view);
-
- let tile_is_visible = |tc: TileCoord| -> bool {
- let nw = tc.latlon_rad_north_west();
- let se = tc.latlon_rad_south_east();
- let vertices = [
- transform.transform_point(nw.to_sphere_point3()),
- transform.transform_point(se.to_sphere_point3()),
- transform.transform_point(LatLonRad::new(nw.lat, se.lon).to_sphere_point3()),
- transform.transform_point(LatLonRad::new(se.lat, nw.lon).to_sphere_point3()),
- ];
-
- if vertices.iter().all(|v| v.z > 0.0) {
- // Tile is on the backside of the sphere
- false
- } else {
- // Check bounding box of vertices against screen.
- //TODO Create true bounding box of tile that also accounts for curved borders.
- vertices.iter().fold(false, |acc, v| acc || v.x >= -1.0) &&
- vertices.iter().fold(false, |acc, v| acc || v.x <= 1.0) &&
- vertices.iter().fold(false, |acc, v| acc || v.y >= -1.0) &&
- vertices.iter().fold(false, |acc, v| acc || v.y <= 1.0)
- }
- };
-
- let mut tiles = vec![center_tile.into()];
-
- let mut stack: Vec<TileNeighbor> = vec![];
- tile_neighbors(center_tile, &mut stack);
-
- let mut visited: HashSet<TileNeighbor> = HashSet::new();
- visited.insert(TileNeighbor::Coord(center_tile));
- visited.extend(stack.iter());
-
- let mut neighbors = Vec::with_capacity(4);
-
- while let Some(tn) = stack.pop() {
- if let TileNeighbor::Coord(tc) = tn {
- if tile_is_visible(tc) {
- tiles.push(tc.into());
- tile_neighbors(tc, &mut neighbors);
- for tn in &neighbors {
- if !visited.contains(tn) {
- visited.insert(*tn);
- stack.push(*tn);
- }
- }
- }
- }
- }
-
- tiles
- }
-
- pub fn diameter_physical_pixels(map_view: &MapView) -> f64 {
- 2.0f64.powf(map_view.zoom) * (FRAC_1_PI * f64::from(map_view.tile_size))
- }
-
- pub fn transformation_matrix(map_view: &MapView) -> Matrix3<f64> {
- let (scale_x, scale_y) = {
- let diam = Self::diameter_physical_pixels(map_view);
- (diam / map_view.width, diam / map_view.height)
- };
-
- let scale_mat: Matrix3<f64> = Matrix3::from_cols(
- vec3(scale_x, 0.0, 0.0),
- vec3(0.0, scale_y, 0.0),
- vec3(0.0, 0.0, 1.0),
- );
-
- let center_latlon = map_view.center.to_latlon_rad();
-
- let rot_mat_x: Matrix3<f64> = {
- let alpha = center_latlon.lon + (PI * 0.5);
- let cosa = alpha.cos();
- let sina = alpha.sin();
- Matrix3::from_cols(
- vec3(cosa, 0.0, -sina),
- vec3(0.0, 1.0, 0.0),
- vec3(sina, 0.0, cosa),
- )
- };
-
- let rot_mat_y: Matrix3<f64> = {
- let alpha = -center_latlon.lat;
- let cosa = alpha.cos();
- let sina = alpha.sin();
- Matrix3::from_cols(
- vec3(1.0, 0.0, 0.0),
- vec3(0.0, cosa, sina),
- vec3(0.0, -sina, cosa),
- )
- };
-
- Transform::<Point3<f64>>::concat(
- &scale_mat,
- &Transform::<Point3<f64>>::concat(&rot_mat_y, &rot_mat_x)
- )
- }
-
- // Returns the inverse rotation matrix of the given view.
- pub fn inv_rotation_matrix(map_view: &MapView) -> Matrix3<f64> {
- let center_latlon = map_view.center.to_latlon_rad();
-
- let rot_mat_x: Matrix3<f64> = {
- let alpha = -center_latlon.lon - (PI * 0.5);
- let cosa = alpha.cos();
- let sina = alpha.sin();
- Matrix3::from_cols(
- vec3(cosa, 0.0, -sina),
- vec3(0.0, 1.0, 0.0),
- vec3(sina, 0.0, cosa),
- )
- };
-
- let rot_mat_y: Matrix3<f64> = {
- let alpha = center_latlon.lat;
- let cosa = alpha.cos();
- let sina = alpha.sin();
- Matrix3::from_cols(
- vec3(1.0, 0.0, 0.0),
- vec3(0.0, cosa, sina),
- vec3(0.0, -sina, cosa),
- )
- };
-
- Transform::<Point3<f64>>::concat(&rot_mat_x, &rot_mat_y)
- }
-
- // Returns the coordinates of the location that is nearest to the given `ScreenCoord`.
- pub fn screen_coord_to_sphere_point(map_view: &MapView, screen_coord: ScreenCoord) -> Point3<f64> {
- // Point on unit sphere
- let sphere_point = {
- let recip_radius = 2.0 * Self::diameter_physical_pixels(map_view).recip();
- let sx = (screen_coord.x - map_view.width * 0.5) * recip_radius;
- let sy = (screen_coord.y - map_view.height * 0.5) * -recip_radius;
- let t = 1.0 - sx * sx - sy * sy;
- if t >= 0.0 {
- // screen_coord is on the sphere
- Point3::new(
- sx,
- sy,
- -(t.sqrt()),
- )
- } else {
- // screen_coord is outside of sphere -> pick nearest.
- let scale = sx.hypot(sy).recip();
- Point3::new(
- sx * scale,
- sy * scale,
- 0.0,
- )
- }
- };
-
- // Rotate
- let inv_trans = Self::inv_rotation_matrix(map_view);
- inv_trans.transform_point(sphere_point)
- }
-
- // Returns the coordinates of the location that is nearest to the given `ScreenCoord`.
- pub fn screen_coord_to_latlonrad(map_view: &MapView, screen_coord: ScreenCoord) -> LatLonRad {
- let p = Self::screen_coord_to_sphere_point(map_view, screen_coord);
-
- // Transform to latitude, longitude
- LatLonRad::new(p.y.asin(), p.z.atan2(p.x))
- }
-
- /// Change zoom value by `zoom_delta` and zoom to a position given in screen coordinates.
- pub fn zoom_at(map_view: &mut MapView, pos: ScreenCoord, zoom_delta: f64) {
- //TODO Do something sophisticated: Increase zoom and rotate slightly so that the given
- // ScreenCoord points to the same geographical location.
- /*
- let latlon = Self::screen_coord_to_latlonrad(map_view, pos);
-
- let delta_x = pos.x - map_view.width * 0.5;
- let delta_y = pos.y - map_view.height * 0.5;
-
- map_view.center = latlon.into();
- */
-
- map_view.zoom += zoom_delta;
- }
-
- /// Change zoom value by `zoom_delta` and zoom to a position given in screen coordinates.
- pub fn set_zoom_at(map_view: &mut MapView, pos: ScreenCoord, zoom: f64) {
- //TODO Do something sophisticated, just like with Self::zoom_at
- map_view.zoom = zoom;
- }
- }
-
- #[cfg(test)]
- mod tests {
- use orthografic_view::*;
-
- #[test]
- fn tilecoord_neighbors() {
- let mut result = vec![];
-
- tile_neighbors(TileCoord::new(0, 0, 0), &mut result);
- assert!(result.is_empty());
-
- tile_neighbors(TileCoord::new(0, 0, -1), &mut result);
- assert!(result.is_empty());
-
- tile_neighbors(TileCoord::new(3, 0, -1), &mut result);
- assert!(result.is_empty());
-
- tile_neighbors(TileCoord::new(1, 0, 0), &mut result);
- assert_eq!(result.len(), 2);
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 1, 0))).is_some());
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 0, 1))).is_some());
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(1, 1, 1))).is_none());
-
- tile_neighbors(TileCoord::new(2, 0, 0), &mut result);
- assert_eq!(result.len(), 4);
- assert!(result.iter().find(|&&x| x == TileNeighbor::NorthPole).is_some());
-
- tile_neighbors(TileCoord::new(2, 0, 3), &mut result);
- assert_eq!(result.len(), 4);
- assert!(result.iter().find(|&&x| x == TileNeighbor::SouthPole).is_some());
-
- tile_neighbors(TileCoord::new(2, 3, 1), &mut result);
- assert_eq!(result.len(), 4);
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 2, 1))).is_some());
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 0, 1))).is_some());
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 3, 0))).is_some());
- assert!(result.iter().find(|&&x| x == TileNeighbor::Coord(TileCoord::new(2, 3, 2))).is_some());
- }
- }
|