mirror of
https://github.com/maplibre/maplibre-rs.git
synced 2025-12-08 19:05:57 +00:00
Add Zoom type and ChangeObserver, also improve performance by tessellating less
This commit is contained in:
parent
56ab38b29e
commit
37a29539d7
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
use cgmath::num_traits::Pow;
|
use cgmath::num_traits::Pow;
|
||||||
use cgmath::{Matrix4, Point3, Vector3};
|
use cgmath::{AbsDiffEq, Matrix4, Point3, Vector3};
|
||||||
|
|
||||||
use style_spec::source::TileAddressingScheme;
|
use style_spec::source::TileAddressingScheme;
|
||||||
|
|
||||||
@ -40,6 +41,68 @@ impl fmt::Debug for Quadkey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Zoom(f64);
|
||||||
|
|
||||||
|
impl Zoom {
|
||||||
|
pub fn new(zoom: f64) -> Self {
|
||||||
|
Zoom(zoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Zoom {
|
||||||
|
fn default() -> Self {
|
||||||
|
Zoom(0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Zoom {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", (self.0 * 100.0).round() / 100.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Add for Zoom {
|
||||||
|
type Output = Zoom;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Zoom(self.0 + rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Sub for Zoom {
|
||||||
|
type Output = Zoom;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
Zoom(self.0 - rhs.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zoom {
|
||||||
|
pub fn scale_to_tile(&self, coords: &WorldTileCoords) -> f64 {
|
||||||
|
2.0_f64.powf(coords.z as f64 - self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale_to_zoom_level(&self, z: u8) -> f64 {
|
||||||
|
2.0_f64.powf(z as f64 - self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale_delta(&self, zoom: &Zoom) -> f64 {
|
||||||
|
2.0_f64.powf(zoom.0 - self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn level(&self) -> u8 {
|
||||||
|
self.0.floor() as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Zoom {}
|
||||||
|
impl PartialEq for Zoom {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.0.abs_diff_eq(&other.0, 0.05)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Within each tile there is a separate coordinate system. Usually this coordinate system is
|
/// Within each tile there is a separate coordinate system. Usually this coordinate system is
|
||||||
/// within [`crate::coords::EXTENT`]. Therefore, `x` and `y` must be within the bounds of
|
/// within [`crate::coords::EXTENT`]. Therefore, `x` and `y` must be within the bounds of
|
||||||
/// [`crate::coords::EXTENT`].
|
/// [`crate::coords::EXTENT`].
|
||||||
@ -146,7 +209,7 @@ impl WorldTileCoords {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn transform_for_zoom(&self, zoom: f64) -> Matrix4<f64> {
|
pub fn transform_for_zoom(&self, zoom: Zoom) -> Matrix4<f64> {
|
||||||
/*
|
/*
|
||||||
For tile.z = zoom:
|
For tile.z = zoom:
|
||||||
=> scale = 512
|
=> scale = 512
|
||||||
@ -155,7 +218,7 @@ impl WorldTileCoords {
|
|||||||
If tile.z > zoom:
|
If tile.z > zoom:
|
||||||
=> scale < 512
|
=> scale < 512
|
||||||
*/
|
*/
|
||||||
let tile_scale = TILE_SIZE * 2.0.pow(zoom - self.z as f64);
|
let tile_scale = TILE_SIZE * Zoom::new(self.z as f64).scale_delta(&zoom);
|
||||||
|
|
||||||
let translate = Matrix4::from_translation(Vector3::new(
|
let translate = Matrix4::from_translation(Vector3::new(
|
||||||
self.x as f64 * tile_scale,
|
self.x as f64 * tile_scale,
|
||||||
@ -309,10 +372,6 @@ pub struct WorldCoords {
|
|||||||
pub y: f64,
|
pub y: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn world_size_at_zoom(zoom: f64) -> f64 {
|
|
||||||
TILE_SIZE * 2.0.pow(zoom)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tiles_with_z(z: u8) -> f64 {
|
fn tiles_with_z(z: u8) -> f64 {
|
||||||
2.0.pow(z)
|
2.0.pow(z)
|
||||||
}
|
}
|
||||||
@ -322,8 +381,8 @@ impl WorldCoords {
|
|||||||
Self { x, y }
|
Self { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_world_tile(self, z: u8, zoom: f64) -> WorldTileCoords {
|
pub fn into_world_tile(self, z: u8, zoom: Zoom) -> WorldTileCoords {
|
||||||
let tile_scale = 2.0.pow(z as f64 - zoom) / TILE_SIZE; // TODO: Deduplicate
|
let tile_scale = zoom.scale_to_zoom_level(z) / TILE_SIZE; // TODO: Deduplicate
|
||||||
let x = self.x * tile_scale;
|
let x = self.x * tile_scale;
|
||||||
let y = self.y * tile_scale;
|
let y = self.y * tile_scale;
|
||||||
|
|
||||||
@ -371,7 +430,7 @@ pub struct ViewRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ViewRegion {
|
impl ViewRegion {
|
||||||
pub fn new(view_region: Aabb2<f64>, padding: i32, zoom: f64, z: u8) -> Self {
|
pub fn new(view_region: Aabb2<f64>, padding: i32, zoom: Zoom, z: u8) -> Self {
|
||||||
let min_world: WorldCoords = WorldCoords::at_ground(view_region.min.x, view_region.min.y);
|
let min_world: WorldCoords = WorldCoords::at_ground(view_region.min.x, view_region.min.y);
|
||||||
let min_world_tile: WorldTileCoords = min_world.into_world_tile(z, zoom);
|
let min_world_tile: WorldTileCoords = min_world.into_world_tile(z, zoom);
|
||||||
let max_world: WorldCoords = WorldCoords::at_ground(view_region.max.x, view_region.max.y);
|
let max_world: WorldCoords = WorldCoords::at_ground(view_region.max.x, view_region.max.y);
|
||||||
@ -439,6 +498,7 @@ impl fmt::Display for WorldCoords {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use cgmath::{Point2, Vector4};
|
use cgmath::{Point2, Vector4};
|
||||||
|
|
||||||
use style_spec::source::TileAddressingScheme;
|
use style_spec::source::TileAddressingScheme;
|
||||||
|
|
||||||
use crate::coords::{Quadkey, TileCoords, ViewRegion, WorldCoords, WorldTileCoords, EXTENT};
|
use crate::coords::{Quadkey, TileCoords, ViewRegion, WorldCoords, WorldTileCoords, EXTENT};
|
||||||
@ -447,7 +507,7 @@ mod tests {
|
|||||||
const TOP_LEFT: Vector4<f64> = Vector4::new(0.0, 0.0, 0.0, 1.0);
|
const TOP_LEFT: Vector4<f64> = Vector4::new(0.0, 0.0, 0.0, 1.0);
|
||||||
const BOTTOM_RIGHT: Vector4<f64> = Vector4::new(EXTENT, EXTENT, 0.0, 1.0);
|
const BOTTOM_RIGHT: Vector4<f64> = Vector4::new(EXTENT, EXTENT, 0.0, 1.0);
|
||||||
|
|
||||||
fn to_from_world(tile: (i32, i32, u8), zoom: f64) {
|
fn to_from_world(tile: (i32, i32, u8), zoom: Zoom) {
|
||||||
let tile = WorldTileCoords::from(tile);
|
let tile = WorldTileCoords::from(tile);
|
||||||
let p1 = tile.transform_for_zoom(zoom) * TOP_LEFT;
|
let p1 = tile.transform_for_zoom(zoom) * TOP_LEFT;
|
||||||
let p2 = tile.transform_for_zoom(zoom) * BOTTOM_RIGHT;
|
let p2 = tile.transform_for_zoom(zoom) * BOTTOM_RIGHT;
|
||||||
|
|||||||
@ -63,8 +63,8 @@ impl QueryHandler {
|
|||||||
let view_proj = state.camera.calc_view_proj(perspective);
|
let view_proj = state.camera.calc_view_proj(perspective);
|
||||||
let inverted_view_proj = view_proj.invert();
|
let inverted_view_proj = view_proj.invert();
|
||||||
|
|
||||||
let z = state.visible_z();
|
let z = state.visible_z(); // FIXME: can be wrong, if tiles of different z are visible
|
||||||
let zoom = state.zoom;
|
let zoom = state.zoom();
|
||||||
|
|
||||||
if let Some(coordinates) = state
|
if let Some(coordinates) = state
|
||||||
.camera
|
.camera
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::UpdateState;
|
use super::UpdateState;
|
||||||
|
|
||||||
|
use crate::coords::Zoom;
|
||||||
use crate::render::render_state::RenderState;
|
use crate::render::render_state::RenderState;
|
||||||
use crate::Scheduler;
|
use crate::Scheduler;
|
||||||
use cgmath::num_traits::Pow;
|
use cgmath::num_traits::Pow;
|
||||||
@ -8,20 +9,19 @@ use std::time::Duration;
|
|||||||
|
|
||||||
pub struct ZoomHandler {
|
pub struct ZoomHandler {
|
||||||
window_position: Option<Vector2<f64>>,
|
window_position: Option<Vector2<f64>>,
|
||||||
zoom_delta: f64,
|
zoom_delta: Option<Zoom>,
|
||||||
sensitivity: f64,
|
sensitivity: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateState for ZoomHandler {
|
impl UpdateState for ZoomHandler {
|
||||||
fn update_state(&mut self, state: &mut RenderState, _scheduler: &Scheduler, _dt: Duration) {
|
fn update_state(&mut self, state: &mut RenderState, _scheduler: &Scheduler, _dt: Duration) {
|
||||||
if self.zoom_delta != 0.0 {
|
if let Some(zoom_delta) = self.zoom_delta {
|
||||||
if let Some(window_position) = self.window_position {
|
if let Some(window_position) = self.window_position {
|
||||||
let current_zoom = state.zoom;
|
let current_zoom = state.zoom();
|
||||||
let next_zoom = current_zoom + self.zoom_delta;
|
let next_zoom = current_zoom + zoom_delta;
|
||||||
|
|
||||||
state.zoom = next_zoom;
|
state.update_zoom(next_zoom);
|
||||||
self.zoom_delta = 0.0;
|
self.zoom_delta = None;
|
||||||
println!("zoom: {}", state.zoom);
|
|
||||||
|
|
||||||
let perspective = &state.perspective;
|
let perspective = &state.perspective;
|
||||||
let view_proj = state.camera.calc_view_proj(perspective);
|
let view_proj = state.camera.calc_view_proj(perspective);
|
||||||
@ -31,7 +31,7 @@ impl UpdateState for ZoomHandler {
|
|||||||
.camera
|
.camera
|
||||||
.window_to_world_at_ground(&window_position, &inverted_view_proj)
|
.window_to_world_at_ground(&window_position, &inverted_view_proj)
|
||||||
{
|
{
|
||||||
let scale = 2.0.pow(next_zoom - current_zoom);
|
let scale = current_zoom.scale_delta(&next_zoom);
|
||||||
|
|
||||||
let delta = Vector3::new(
|
let delta = Vector3::new(
|
||||||
cursor_position.x * scale,
|
cursor_position.x * scale,
|
||||||
@ -50,7 +50,7 @@ impl ZoomHandler {
|
|||||||
pub fn new(sensitivity: f64) -> Self {
|
pub fn new(sensitivity: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
window_position: None,
|
window_position: None,
|
||||||
zoom_delta: 0.0,
|
zoom_delta: None,
|
||||||
sensitivity,
|
sensitivity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,14 +64,22 @@ impl ZoomHandler {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_zoom(&mut self, delta: f64) {
|
||||||
|
self.zoom_delta = Some(self.zoom_delta.unwrap_or_default() + Zoom::new(delta));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) {
|
pub fn process_scroll(&mut self, delta: &winit::event::MouseScrollDelta) {
|
||||||
self.zoom_delta += match delta {
|
self.update_zoom(
|
||||||
winit::event::MouseScrollDelta::LineDelta(_horizontal, vertical) => *vertical as f64,
|
match delta {
|
||||||
winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
|
winit::event::MouseScrollDelta::LineDelta(_horizontal, vertical) => {
|
||||||
y: scroll,
|
*vertical as f64
|
||||||
..
|
}
|
||||||
}) => *scroll / 100.0,
|
winit::event::MouseScrollDelta::PixelDelta(winit::dpi::PhysicalPosition {
|
||||||
} * self.sensitivity;
|
y: scroll,
|
||||||
|
..
|
||||||
|
}) => *scroll / 100.0,
|
||||||
|
} * self.sensitivity,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_key_press(
|
pub fn process_key_press(
|
||||||
@ -87,11 +95,11 @@ impl ZoomHandler {
|
|||||||
|
|
||||||
match key {
|
match key {
|
||||||
winit::event::VirtualKeyCode::Plus | winit::event::VirtualKeyCode::I => {
|
winit::event::VirtualKeyCode::Plus | winit::event::VirtualKeyCode::I => {
|
||||||
self.zoom_delta += amount;
|
self.update_zoom(amount);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
winit::event::VirtualKeyCode::Minus | winit::event::VirtualKeyCode::K => {
|
winit::event::VirtualKeyCode::Minus | winit::event::VirtualKeyCode::K => {
|
||||||
self.zoom_delta -= amount;
|
self.update_zoom(-amount);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use geozero::geo_types::GeoWriter;
|
|||||||
use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor};
|
use geozero::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor};
|
||||||
use rstar::{Envelope, PointDistance, RTree, RTreeObject, AABB};
|
use rstar::{Envelope, PointDistance, RTree, RTreeObject, AABB};
|
||||||
|
|
||||||
use crate::coords::{InnerCoords, Quadkey, WorldCoords, WorldTileCoords, EXTENT, TILE_SIZE};
|
use crate::coords::{InnerCoords, Quadkey, WorldCoords, WorldTileCoords, Zoom, EXTENT, TILE_SIZE};
|
||||||
use crate::util::math::bounds_from_points;
|
use crate::util::math::bounds_from_points;
|
||||||
|
|
||||||
pub struct GeometryIndex {
|
pub struct GeometryIndex {
|
||||||
@ -33,7 +33,7 @@ impl GeometryIndex {
|
|||||||
&self,
|
&self,
|
||||||
world_coords: &WorldCoords,
|
world_coords: &WorldCoords,
|
||||||
z: u8,
|
z: u8,
|
||||||
zoom: f64,
|
zoom: Zoom,
|
||||||
) -> Option<Vec<&IndexedGeometry<f64>>> {
|
) -> Option<Vec<&IndexedGeometry<f64>>> {
|
||||||
let world_tile_coords = world_coords.into_world_tile(z, zoom);
|
let world_tile_coords = world_coords.into_world_tile(z, zoom);
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ impl GeometryIndex {
|
|||||||
.build_quad_key()
|
.build_quad_key()
|
||||||
.and_then(|key| self.index.get(&key))
|
.and_then(|key| self.index.get(&key))
|
||||||
{
|
{
|
||||||
let scale = 2.0f64.pow(z as f64 - zoom); // TODO deduplicate
|
let scale = zoom.scale_delta(&Zoom::new(z as f64)); // FIXME: can be wrong, if tiles of different z are visible
|
||||||
|
|
||||||
let delta_x = world_coords.x / TILE_SIZE * scale - world_tile_coords.x as f64;
|
let delta_x = world_coords.x / TILE_SIZE * scale - world_tile_coords.x as f64;
|
||||||
let delta_y = world_coords.y / TILE_SIZE * scale - world_tile_coords.y as f64;
|
let delta_y = world_coords.y / TILE_SIZE * scale - world_tile_coords.y as f64;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
use vector_tile::parse_tile_bytes;
|
use vector_tile::parse_tile_bytes;
|
||||||
|
|
||||||
/// Describes through which channels work-requests travel. It describes the flow of work.
|
/// Describes through which channels work-requests travel. It describes the flow of work.
|
||||||
use crate::coords::{WorldCoords, WorldTileCoords};
|
use crate::coords::{WorldCoords, WorldTileCoords, Zoom};
|
||||||
use crate::io::tile_cache::TileCache;
|
use crate::io::tile_cache::TileCache;
|
||||||
use crate::io::{
|
use crate::io::{
|
||||||
LayerTessellateMessage, TessellateMessage, TileFetchResult, TileRequest, TileRequestID,
|
LayerTessellateMessage, TessellateMessage, TileFetchResult, TileRequest, TileRequestID,
|
||||||
@ -155,7 +155,7 @@ impl ThreadLocalState {
|
|||||||
&self,
|
&self,
|
||||||
world_coords: &WorldCoords,
|
world_coords: &WorldCoords,
|
||||||
z: u8,
|
z: u8,
|
||||||
zoom: f64,
|
zoom: Zoom,
|
||||||
) -> Option<Vec<IndexedGeometry<f64>>> {
|
) -> Option<Vec<IndexedGeometry<f64>>> {
|
||||||
if let Ok(mut geometry_index) = self.geometry_index.lock() {
|
if let Ok(mut geometry_index) = self.geometry_index.lock() {
|
||||||
geometry_index
|
geometry_index
|
||||||
@ -256,7 +256,7 @@ impl ThreadLocalState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("tile at {} finished", &coords);
|
tracing::info!("tile at {} finished", &tile_request.coords);
|
||||||
|
|
||||||
self.tessellate_result_sender
|
self.tessellate_result_sender
|
||||||
.send(TessellateMessage::Tile(TileTessellateMessage {
|
.send(TessellateMessage::Tile(TileTessellateMessage {
|
||||||
@ -330,9 +330,9 @@ impl Scheduler {
|
|||||||
&mut self,
|
&mut self,
|
||||||
coords: &WorldTileCoords,
|
coords: &WorldTileCoords,
|
||||||
layers: &HashSet<String>,
|
layers: &HashSet<String>,
|
||||||
) -> Result<(), SendError<TileRequest>> {
|
) -> Result<bool, SendError<TileRequest>> {
|
||||||
if !self.tile_cache.is_layers_missing(coords, layers) {
|
if !self.tile_cache.is_layers_missing(coords, layers) {
|
||||||
return Ok(());
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut tile_request_state) = self.tile_request_state.try_lock() {
|
if let Ok(mut tile_request_state) = self.tile_request_state.try_lock() {
|
||||||
@ -372,9 +372,11 @@ impl Scheduler {
|
|||||||
self.schedule_method.schedule(self, future_fn).unwrap();
|
self.schedule_method.schedule(self, future_fn).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tile_cache(&self) -> &TileCache {
|
pub fn get_tile_cache(&self) -> &TileCache {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use cgmath::prelude::*;
|
use cgmath::prelude::*;
|
||||||
use cgmath::{Matrix4, Point2, Point3, Vector2, Vector3, Vector4};
|
use cgmath::{AbsDiffEq, Matrix4, Point2, Point3, Vector2, Vector3, Vector4};
|
||||||
|
|
||||||
use crate::render::shaders::ShaderCamera;
|
use crate::render::shaders::ShaderCamera;
|
||||||
use crate::util::math::{bounds_from_points, Aabb2, Aabb3, Plane};
|
use crate::util::math::{bounds_from_points, Aabb2, Aabb3, Plane};
|
||||||
@ -73,6 +73,15 @@ pub struct Camera {
|
|||||||
pub height: f64,
|
pub height: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for Camera {}
|
||||||
|
impl PartialEq for Camera {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.position.abs_diff_eq(&other.position, 0.05)
|
||||||
|
&& self.yaw.abs_diff_eq(&other.yaw, 0.05)
|
||||||
|
&& self.pitch.abs_diff_eq(&other.pitch, 0.05)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Camera {
|
impl Camera {
|
||||||
pub fn new<
|
pub fn new<
|
||||||
V: Into<cgmath::Point3<f64>>,
|
V: Into<cgmath::Point3<f64>>,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
||||||
|
use cgmath::AbsDiffEq;
|
||||||
use std::{cmp, iter};
|
use std::{cmp, iter};
|
||||||
|
|
||||||
use tracing;
|
use tracing;
|
||||||
@ -10,7 +11,7 @@ use winit::window::Window;
|
|||||||
|
|
||||||
use style_spec::Style;
|
use style_spec::Style;
|
||||||
|
|
||||||
use crate::coords::{ViewRegion, TILE_SIZE};
|
use crate::coords::{ViewRegion, Zoom, TILE_SIZE};
|
||||||
use crate::io::scheduler::Scheduler;
|
use crate::io::scheduler::Scheduler;
|
||||||
use crate::io::LayerTessellateMessage;
|
use crate::io::LayerTessellateMessage;
|
||||||
use crate::platform::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE};
|
use crate::platform::{COLOR_TEXTURE_FORMAT, MIN_BUFFER_SIZE};
|
||||||
@ -23,7 +24,7 @@ use crate::render::options::{
|
|||||||
};
|
};
|
||||||
use crate::render::tile_view_pattern::{TileInView, TileViewPattern};
|
use crate::render::tile_view_pattern::{TileInView, TileViewPattern};
|
||||||
use crate::tessellation::IndexDataType;
|
use crate::tessellation::IndexDataType;
|
||||||
use crate::util::FPSMeter;
|
use crate::util::{ChangeObserver, FPSMeter};
|
||||||
|
|
||||||
use super::piplines::*;
|
use super::piplines::*;
|
||||||
use super::shaders;
|
use super::shaders;
|
||||||
@ -66,14 +67,24 @@ pub struct RenderState {
|
|||||||
|
|
||||||
tile_view_pattern: TileViewPattern<Queue, Buffer>,
|
tile_view_pattern: TileViewPattern<Queue, Buffer>,
|
||||||
|
|
||||||
pub camera: camera::Camera,
|
pub camera: ChangeObserver<camera::Camera>,
|
||||||
pub perspective: camera::Perspective,
|
pub perspective: camera::Perspective,
|
||||||
pub zoom: f64,
|
zoom: ChangeObserver<Zoom>,
|
||||||
|
try_failed: bool,
|
||||||
|
|
||||||
style: Box<Style>,
|
style: Box<Style>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
|
pub fn zoom(&self) -> Zoom {
|
||||||
|
*self.zoom
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_zoom(&mut self, new_zoom: Zoom) {
|
||||||
|
*self.zoom = new_zoom;
|
||||||
|
log::info!("zoom: {}", new_zoom);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new(window: &Window, style: Box<Style>) -> Self {
|
pub async fn new(window: &Window, style: Box<Style>) -> Self {
|
||||||
let sample_count = 4;
|
let sample_count = 4;
|
||||||
|
|
||||||
@ -292,7 +303,7 @@ impl RenderState {
|
|||||||
sample_count,
|
sample_count,
|
||||||
globals_uniform_buffer,
|
globals_uniform_buffer,
|
||||||
fps_meter: FPSMeter::new(),
|
fps_meter: FPSMeter::new(),
|
||||||
camera,
|
camera: ChangeObserver::new(camera),
|
||||||
perspective: projection,
|
perspective: projection,
|
||||||
suspended: false, // Initially the app is not suspended
|
suspended: false, // Initially the app is not suspended
|
||||||
buffer_pool: BufferPool::new(
|
buffer_pool: BufferPool::new(
|
||||||
@ -305,7 +316,8 @@ impl RenderState {
|
|||||||
tile_view_buffer,
|
tile_view_buffer,
|
||||||
TILE_VIEW_BUFFER_SIZE,
|
TILE_VIEW_BUFFER_SIZE,
|
||||||
)),
|
)),
|
||||||
zoom: 0.0,
|
zoom: ChangeObserver::default(),
|
||||||
|
try_failed: false,
|
||||||
style,
|
style,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -356,12 +368,13 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_z(&self) -> u8 {
|
pub fn visible_z(&self) -> u8 {
|
||||||
self.zoom.floor() as u8
|
self.zoom.level()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request tiles which are currently in view
|
/// Request tiles which are currently in view
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn request_tiles_in_view(&self, view_region: &ViewRegion, scheduler: &mut Scheduler) {
|
fn request_tiles_in_view(&self, view_region: &ViewRegion, scheduler: &mut Scheduler) -> bool {
|
||||||
|
let mut try_failed = false;
|
||||||
let source_layers: HashSet<String> = self
|
let source_layers: HashSet<String> = self
|
||||||
.style
|
.style
|
||||||
.layers
|
.layers
|
||||||
@ -372,9 +385,10 @@ impl RenderState {
|
|||||||
for coords in view_region.iter() {
|
for coords in view_region.iter() {
|
||||||
if coords.build_quad_key().is_some() {
|
if coords.build_quad_key().is_some() {
|
||||||
// TODO: Make tesselation depend on style?
|
// TODO: Make tesselation depend on style?
|
||||||
scheduler.try_request_tile(&coords, &source_layers).unwrap();
|
try_failed = scheduler.try_request_tile(&coords, &source_layers).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update tile metadata for all required tiles on the GPU according to current zoom, camera and perspective
|
/// Update tile metadata for all required tiles on the GPU according to current zoom, camera and perspective
|
||||||
@ -383,16 +397,12 @@ impl RenderState {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn update_metadata(
|
fn update_metadata(
|
||||||
&mut self,
|
&mut self,
|
||||||
scheduler: &mut Scheduler,
|
_scheduler: &mut Scheduler,
|
||||||
view_region: &ViewRegion,
|
view_region: &ViewRegion,
|
||||||
view_proj: &ViewProjection,
|
view_proj: &ViewProjection,
|
||||||
) {
|
) {
|
||||||
self.tile_view_pattern.update_pattern(
|
self.tile_view_pattern
|
||||||
view_region,
|
.update_pattern(view_region, &self.buffer_pool, *self.zoom);
|
||||||
scheduler.get_tile_cache(),
|
|
||||||
&self.buffer_pool,
|
|
||||||
self.zoom,
|
|
||||||
);
|
|
||||||
self.tile_view_pattern
|
self.tile_view_pattern
|
||||||
.upload_pattern(&self.queue, view_proj);
|
.upload_pattern(&self.queue, view_proj);
|
||||||
|
|
||||||
@ -511,6 +521,12 @@ impl RenderState {
|
|||||||
buffer,
|
buffer,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
|
let allocate_feature_metadata = tracing::span!(
|
||||||
|
tracing::Level::TRACE,
|
||||||
|
"allocate_feature_metadata"
|
||||||
|
);
|
||||||
|
|
||||||
|
let guard = allocate_feature_metadata.enter();
|
||||||
let feature_metadata = layer_data
|
let feature_metadata = layer_data
|
||||||
.features()
|
.features()
|
||||||
.iter()
|
.iter()
|
||||||
@ -522,6 +538,7 @@ impl RenderState {
|
|||||||
.take(feature_indices[i] as usize)
|
.take(feature_indices[i] as usize)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
drop(guard);
|
||||||
|
|
||||||
tracing::trace!("Allocating geometry at {}", &coords);
|
tracing::trace!("Allocating geometry at {}", &coords);
|
||||||
self.buffer_pool.allocate_layer_geometry(
|
self.buffer_pool.allocate_layer_geometry(
|
||||||
@ -552,27 +569,36 @@ impl RenderState {
|
|||||||
let view_region = self
|
let view_region = self
|
||||||
.camera
|
.camera
|
||||||
.view_region_bounding_box(&view_proj.invert())
|
.view_region_bounding_box(&view_proj.invert())
|
||||||
.map(|bounding_box| ViewRegion::new(bounding_box, 1, self.zoom, visible_z));
|
.map(|bounding_box| ViewRegion::new(bounding_box, 0, *self.zoom, visible_z));
|
||||||
|
|
||||||
drop(_guard);
|
drop(_guard);
|
||||||
|
|
||||||
if let Some(view_region) = &view_region {
|
if let Some(view_region) = &view_region {
|
||||||
self.upload_tile_geometry(&view_proj, view_region, scheduler);
|
self.upload_tile_geometry(&view_proj, view_region, scheduler);
|
||||||
self.update_metadata(scheduler, view_region, &view_proj);
|
self.update_metadata(scheduler, view_region, &view_proj);
|
||||||
self.request_tiles_in_view(view_region, scheduler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Could we draw inspiration from StagingBelt (https://docs.rs/wgpu/latest/wgpu/util/struct.StagingBelt.html)?
|
// TODO: Could we draw inspiration from StagingBelt (https://docs.rs/wgpu/latest/wgpu/util/struct.StagingBelt.html)?
|
||||||
// TODO: What is StagingBelt for?
|
// TODO: What is StagingBelt for?
|
||||||
|
|
||||||
// Update globals
|
if self.camera.did_change() || self.zoom.did_change() || self.try_failed {
|
||||||
self.queue.write_buffer(
|
if let Some(view_region) = &view_region {
|
||||||
&self.globals_uniform_buffer,
|
// FIXME: We also need to request tiles from layers above if we are over the maximum zoom level
|
||||||
0,
|
self.try_failed = self.request_tiles_in_view(view_region, scheduler);
|
||||||
bytemuck::cast_slice(&[ShaderGlobals::new(
|
}
|
||||||
self.camera.create_camera_uniform(&self.perspective),
|
|
||||||
)]),
|
// Update globals
|
||||||
);
|
self.queue.write_buffer(
|
||||||
|
&self.globals_uniform_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[ShaderGlobals::new(
|
||||||
|
self.camera.create_camera_uniform(&self.perspective),
|
||||||
|
)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.camera.finished_observing();
|
||||||
|
self.zoom.finished_observing();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::coords::{ViewRegion, WorldTileCoords};
|
use crate::coords::{ViewRegion, WorldTileCoords, Zoom};
|
||||||
use crate::io::tile_cache::TileCache;
|
use crate::io::tile_cache::TileCache;
|
||||||
use crate::render::buffer_pool::{BackingBufferDescriptor, BufferPool, Queue};
|
use crate::render::buffer_pool::{BackingBufferDescriptor, BufferPool, Queue};
|
||||||
use crate::render::camera::ViewProjection;
|
use crate::render::camera::ViewProjection;
|
||||||
@ -28,6 +28,18 @@ pub struct TileShape {
|
|||||||
pub buffer_range: Range<wgpu::BufferAddress>,
|
pub buffer_range: Range<wgpu::BufferAddress>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TileShape {
|
||||||
|
fn new(coords: WorldTileCoords, zoom: Zoom, index: u64) -> Self {
|
||||||
|
const STRIDE: u64 = size_of::<ShaderTileMetadata>() as u64;
|
||||||
|
Self {
|
||||||
|
coords,
|
||||||
|
zoom_factor: zoom.scale_to_tile(&coords),
|
||||||
|
transform: coords.transform_for_zoom(zoom),
|
||||||
|
buffer_range: index as u64 * STRIDE..(index as u64 + 1) * STRIDE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TileInView {
|
pub struct TileInView {
|
||||||
pub shape: TileShape,
|
pub shape: TileShape,
|
||||||
|
|
||||||
@ -61,7 +73,6 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
|
|||||||
pub fn update_pattern(
|
pub fn update_pattern(
|
||||||
&mut self,
|
&mut self,
|
||||||
view_region: &ViewRegion,
|
view_region: &ViewRegion,
|
||||||
tile_cache: &TileCache,
|
|
||||||
buffer_pool: &BufferPool<
|
buffer_pool: &BufferPool<
|
||||||
wgpu::Queue,
|
wgpu::Queue,
|
||||||
Buffer,
|
Buffer,
|
||||||
@ -70,43 +81,31 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
|
|||||||
ShaderLayerMetadata,
|
ShaderLayerMetadata,
|
||||||
ShaderFeatureStyle,
|
ShaderFeatureStyle,
|
||||||
>,
|
>,
|
||||||
zoom: f64,
|
zoom: Zoom,
|
||||||
) {
|
) {
|
||||||
self.in_view.clear();
|
self.in_view.clear();
|
||||||
|
|
||||||
let stride = size_of::<ShaderTileMetadata>() as u64;
|
|
||||||
|
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
|
||||||
|
let pool_index = buffer_pool.index();
|
||||||
|
|
||||||
for coords in view_region.iter() {
|
for coords in view_region.iter() {
|
||||||
if coords.build_quad_key().is_none() {
|
if coords.build_quad_key().is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shape = TileShape {
|
let shape = TileShape::new(coords, zoom, index);
|
||||||
coords,
|
|
||||||
zoom_factor: 2.0_f64.powf(coords.z as f64 - zoom),
|
|
||||||
transform: coords.transform_for_zoom(zoom),
|
|
||||||
buffer_range: index as u64 * stride..(index as u64 + 1) * stride,
|
|
||||||
};
|
|
||||||
|
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|
||||||
let fallback = {
|
let fallback = {
|
||||||
if !buffer_pool.index().has_tile(&coords) {
|
if !pool_index.has_tile(&coords) {
|
||||||
if let Some(fallback_coords) =
|
if let Some(fallback_coords) = pool_index.get_tile_coords_fallback(&coords) {
|
||||||
buffer_pool.index().get_tile_coords_fallback(&coords)
|
|
||||||
{
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"Could not find data at {coords}. Falling back to {fallback_coords}"
|
"Could not find data at {coords}. Falling back to {fallback_coords}"
|
||||||
);
|
);
|
||||||
|
|
||||||
let shape = TileShape {
|
let shape = TileShape::new(fallback_coords, zoom, index);
|
||||||
coords: fallback_coords,
|
|
||||||
zoom_factor: 2.0_f64.powf(fallback_coords.z as f64 - zoom),
|
|
||||||
transform: fallback_coords.transform_for_zoom(zoom),
|
|
||||||
buffer_range: index as u64 * stride..(index as u64 + 1) * stride,
|
|
||||||
};
|
|
||||||
|
|
||||||
index += 1;
|
index += 1;
|
||||||
Some(shape)
|
Some(shape)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ pub mod math;
|
|||||||
|
|
||||||
use crate::coords::WorldTileCoords;
|
use crate::coords::WorldTileCoords;
|
||||||
pub use fps_meter::FPSMeter;
|
pub use fps_meter::FPSMeter;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
struct MinMaxBoundingBox {
|
struct MinMaxBoundingBox {
|
||||||
min_x: i32,
|
min_x: i32,
|
||||||
@ -49,3 +50,61 @@ impl MinMaxBoundingBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ChangeObserver<T> {
|
||||||
|
inner: T,
|
||||||
|
last_value: Option<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ChangeObserver<T> {
|
||||||
|
pub fn new(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: value,
|
||||||
|
last_value: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ChangeObserver<T>
|
||||||
|
where
|
||||||
|
T: Clone + Eq,
|
||||||
|
{
|
||||||
|
pub fn finished_observing(&mut self) {
|
||||||
|
self.last_value = Some(self.inner.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn did_change(&self) -> bool {
|
||||||
|
if let Some(last_value) = &self.last_value {
|
||||||
|
if !last_value.eq(&self.inner) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Default for ChangeObserver<T>
|
||||||
|
where
|
||||||
|
T: Default,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
ChangeObserver::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for ChangeObserver<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for ChangeObserver<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user