Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load flat tiles if no glb is available #54

Merged
merged 6 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bevy_screen_diagnostics = { git = "https://github.com/oli-obk/bevy_screen_diagno
globe-rs = "0.1.8"
directories = "5.0.1"
async-fs = "2.1.0"
bevy_web_asset = { git = "https://github.com/oli-obk/bevy_web_asset.git", branch = "android" }
bevy_web_asset = { git = "https://github.com/oli-obk/bevy_web_asset.git", branch = "user-agent" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3.22", default-features = false, features = [
Expand Down
27 changes: 16 additions & 11 deletions src/geopos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ impl GeoPos {
x: ((self.lon + 180.) / 360. * pow_zoom),

// y: Latitude, (Breitengrad) Nort/South "index"
y: ((1.
- ((self.lat * PI / 180.).tan() + 1. / (self.lat * PI / 180.).cos()).ln() / PI)
y: ((1. - (self.lat.to_radians().tan() + 1. / self.lat.to_radians().cos()).ln() / PI)
/ 2.
* pow_zoom),
// The Nort/South y tile name part is not linear, the tiles gets stretched to the poles
Expand All @@ -47,8 +46,8 @@ impl GeoPos {

pub fn to_cartesian(self) -> Vec3 {
let geo = GeographicPoint::new(
self.lon as f64 / 180.0 * std::f64::consts::PI,
self.lat as f64 / 180.0 * std::f64::consts::PI,
(self.lon as f64).to_radians(),
(self.lat as f64).to_radians(),
EARTH_RADIUS as f64,
);
let cart = CartesianPoint::from_geographic(&geo);
Expand All @@ -60,19 +59,25 @@ impl GeoPos {
let cart = CartesianPoint::new(pos.x, pos.y, pos.z);
let geo = GeographicPoint::from_cartesian(&cart);
GeoPos {
lat: geo.latitude() as f32 / PI * 180.0,
lon: geo.longitude() as f32 / PI * 180.0,
lat: (geo.latitude() as f32).to_degrees(),
lon: (geo.longitude() as f32).to_degrees(),
}
}

/// Tile width and height in meters
pub fn tile_size(self, zoom: u8) -> Vec2 {
let pow_zoom = 2_u32.pow(zoom.into()) as f32;
let tile_width = EQUATOR_METERS / pow_zoom;
let tile_height = tile_width * self.lat.cos();
Vec2::new(tile_width, tile_height.abs())
let coord = self.to_tile_coordinates(zoom);
let pos = self.to_cartesian();
let x = TileCoord(coord.0 + Vec2::X)
.to_geo_pos(zoom)
.to_cartesian()
.distance(pos);
let y = TileCoord(coord.0 + Vec2::Y)
.to_geo_pos(zoom)
.to_cartesian()
.distance(pos);
Vec2 { x, y }
}
}

pub const EARTH_RADIUS: f32 = 6378000.;
pub const EQUATOR_METERS: f32 = 40_075_016.686;
16 changes: 8 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ pub fn main() {
app.add_plugins(HttpAssetReaderPlugin {
base_url: "gltiles.osm2world.org/glb/".into(),
});
app.add_plugins(bevy_web_asset::WebAssetPlugin {
user_agent: Some("osmeta 0.1.0".into()),
});
if xr {
#[cfg(all(feature = "xr", not(any(target_os = "macos", target_arch = "wasm32"))))]
app.add_plugins(xr::Plugin);
Expand All @@ -94,16 +97,11 @@ pub fn main() {
.run();
}

fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut diags: ResMut<ScreenDiagnostics>,
pos: Res<StartingPosition>,
) {
fn setup(mut commands: Commands, mut diags: ResMut<ScreenDiagnostics>, pos: Res<StartingPosition>) {
diags.modify("fps").aggregate(Aggregate::Average);

commands.spawn((
TileMap::new(&mut meshes),
TileMap::default(),
SpatialBundle {
transform: Transform::from_translation(-pos.0),
..default()
Expand Down Expand Up @@ -152,6 +150,7 @@ fn load_next_tile(
Without<FlyCam>,
),
>,
mut meshes: ResMut<Assets<Mesh>>,
diagnostics: Res<DiagnosticsStore>,
mut fog: Query<&mut FogSettings>,
) {
Expand All @@ -171,7 +170,7 @@ fn load_next_tile(
} else if fps > 59.5 {
sky.scale = Vec3::splat(sky.scale.x * 1.01)
}
sky.scale = Vec3::splat(sky.scale.x.clamp(1000.0, 3000.0));
sky.scale = Vec3::splat(sky.scale.x.clamp(1000.0, 10000.0));
fog.single_mut().falloff = FogFalloff::from_visibility_colors(
sky.scale.x, // distance in world units up to which objects retain visibility (>= 5% contrast)
Color::rgb(0.35, 0.5, 0.66), // atmospheric extinction color (after light is lost due to absorption by atmospheric particles)
Expand All @@ -189,6 +188,7 @@ fn load_next_tile(
id,
&mut commands,
&server,
&mut meshes,
// FIXME: Maybe use https://crates.io/crates/big_space in order to be able to remove
// the translation from the tilemap and instead just use its real coordinates.
origin,
Expand Down
137 changes: 88 additions & 49 deletions src/tilemap.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use std::{collections::BTreeMap, f32::consts::PI};

use bevy::{gltf::Gltf, prelude::*};
use bevy::{
gltf::Gltf,
prelude::*,
render::{mesh::Indices, render_resource::PrimitiveTopology},
};

use crate::geopos::GeoPos;

Expand All @@ -10,8 +14,6 @@ pub struct TileMap {
tiles: BTreeMap<u32, BTreeMap<u32, Entity>>,
/// The tile currently being loaded.
loading: Option<(UVec2, Handle<Gltf>)>,
/// Dummy square to show while a scene is loading
dummy: Handle<Mesh>,
}

pub const TILE_ZOOM: u8 = 15;
Expand All @@ -25,6 +27,7 @@ impl TileMap {
tilemap_id: Entity,
commands: &mut Commands,
server: &AssetServer,
meshes: &mut Assets<Mesh>,
origin: TileCoord,
radius: Vec2,
) {
Expand Down Expand Up @@ -62,7 +65,7 @@ impl TileMap {
}
}
if let Some(best_pos) = best_pos {
self.load(tilemap_id, commands, server, best_pos);
self.load(tilemap_id, commands, server, meshes, best_pos);
}
}

Expand All @@ -88,6 +91,7 @@ impl TileMap {
tilemap_id: Entity,
commands: &mut Commands,
server: &AssetServer,
meshes: &mut Assets<Mesh>,
pos: UVec2,
) {
if self.loading.is_some() {
Expand All @@ -103,15 +107,9 @@ impl TileMap {
.or_default()
.entry(pos.y)
.or_insert_with(|| {
let transform = Self::test_transform(pos);

let id = commands
.spawn(PbrBundle {
mesh: self.dummy.clone(),
transform,
..default()
})
.id();
let mesh = meshes.add(flat_tile(pos).1);

let id = commands.spawn(PbrBundle { mesh, ..default() }).id();
commands.entity(tilemap_id).add_child(id);
id
});
Expand All @@ -121,6 +119,8 @@ impl TileMap {
mut commands: Commands,
server: Res<AssetServer>,
scenes: ResMut<Assets<Gltf>>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut tilemap: Query<(Entity, &mut Self)>,
) {
for (id, mut tilemap) in &mut tilemap {
Expand All @@ -131,7 +131,7 @@ impl TileMap {
NotLoaded | Loading => {
tilemap.loading = Some((pos, scene));
}
Loaded => {
state @ (Loaded | Failed) => {
// FIXME: implement caching of downloaded assets by implementing something like
// https://github.com/bevyengine/bevy/blob/main/examples/asset/processing/asset_processing.rs

Expand All @@ -141,53 +141,58 @@ impl TileMap {
continue;
};

let transform = Self::test_transform(pos);
let scene = scenes.get(scene).unwrap().scenes[0].clone();
let tile = commands
.spawn((
SceneBundle {
scene, // "models/17430_11371.glb#Scene0"
transform,
let tile = match state {
NotLoaded | Loading => unreachable!(),
Loaded => {
let transform = Self::test_transform(pos);
let scene = scenes.get(scene).unwrap().scenes[0].clone();
commands
.spawn((
SceneBundle {
scene, // "models/17430_11371.glb#Scene0"
transform,
..default()
},
Tile,
))
.id()
}
Failed => {
warn!("failed to load tile {pos} from network, switching to flat tile");

let (coord, mesh) = flat_tile(pos);
let mesh = meshes.add(mesh);
let image: Handle<Image> = server.load(format!(
"https://a.tile.openstreetmap.org/{TILE_ZOOM}/{}/{}.png",
coord.0.x, coord.0.y
));
let material = materials.add(StandardMaterial {
base_color_texture: Some(image),
perceptual_roughness: 1.0,
..default()
},
Tile,
))
.id();
});
commands
.spawn(PbrBundle {
mesh,
material,
..default()
})
.id()
}
};
commands.entity(id).add_child(tile);
let dummy = std::mem::replace(entity, tile);
if let Some(mut entity) = commands.get_entity(dummy) {
entity.despawn();
}
}
Failed => {
error!("failed to load tile {pos} from network");
}
}
}
}
}

pub fn new(meshes: &mut Assets<Mesh>) -> Self {
// FIXME: compute dummy tile size on the fly
let half = 814.5 / 2.0;
Self {
dummy: meshes.add(
shape::Box {
min_x: -half,
max_x: half,
min_y: 0.0,
max_y: 1.0,
min_z: -half,
max_z: half,
}
.into(),
),
..default()
}
}

fn test_transform(pos: UVec2) -> Transform {
let coord = TileCoord(pos.as_vec2());
let coord = TileCoord(pos.as_vec2() + 0.5);
let pos = coord.to_geo_pos(TILE_ZOOM).to_cartesian();
let next = TileCoord(Vec2 {
x: coord.0.x,
Expand All @@ -199,6 +204,40 @@ impl TileMap {
}
}

// Compute a square mesh at the position for the given tile.
fn flat_tile(pos: UVec2) -> (TileCoord, Mesh) {
let coord = TileCoord(pos.as_vec2());

// Four corners of the tile in cartesian coordinates relative to the
// planet's center.
let a = coord.to_geo_pos(TILE_ZOOM).to_cartesian();
let b = TileCoord(pos.as_vec2() + Vec2::X)
.to_geo_pos(TILE_ZOOM)
.to_cartesian();
let c = TileCoord(pos.as_vec2() + 1.)
.to_geo_pos(TILE_ZOOM)
.to_cartesian();
let d = TileCoord(pos.as_vec2() + Vec2::Y)
.to_geo_pos(TILE_ZOOM)
.to_cartesian();

// Normals on a sphere are just the position on the sphere normalized.
let normal = a.normalize();

let positions = vec![a.to_array(), b.to_array(), c.to_array(), d.to_array()];
let normals = vec![normal; 4];
let uvs = vec![Vec2::ZERO, Vec2::X, Vec2::splat(1.0), Vec2::Y];

let indices = Indices::U32(vec![0, 3, 2, 2, 1, 0]);

let mesh = Mesh::new(PrimitiveTopology::TriangleList)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
.with_indices(Some(indices));
(coord, mesh)
}

/// A coordinate in the OWM tile coordinate system. Allows for positions within a tile. ???
#[derive(Debug, Copy, Clone)]
pub struct TileCoord(pub Vec2);
Expand All @@ -209,7 +248,7 @@ impl TileCoord {

let lon = self.0.x / pow_zoom * 360.0 - 180.0;
let lat_rad = (PI * (1. - 2. * self.0.y / pow_zoom)).sinh().atan();
let lat = lat_rad * 180.0 / PI;
let lat = lat_rad.to_degrees();
GeoPos { lat, lon }
}
}