use crate::buffer::GpuBuffer;
use crate::map::EnvelopeSampling;
use crate::shared::Corner;
use std::mem;
use twmap::{Tile, TileFlags, TilesLayer};
use vek::az::UnwrappedAs;
use vek::{Aabr, Rgba, Vec2};
use wgpu::util::{DeviceExt, TextureDataOrder};
use wgpu::{
vertex_attr_array, Device, Extent3d, Queue, Texture, TextureDescriptor, TextureDimension,
TextureFormat, TextureUsages, TextureView, TextureViewDescriptor, VertexAttribute,
VertexBufferLayout, VertexStepMode,
};
const LABEL: Option<&str> = Some("Tilemap Data");
#[derive(Debug, Default, Copy, Clone, bytemuck::Zeroable, bytemuck::Pod)]
#[repr(C)]
pub struct TilemapCorner {
pub clip_pos: Vec2<f32>,
pub world_pos: Vec2<f32>,
pub color: Rgba<f32>,
pub tilemap_bounds: Vec2<f32>,
pub mipmap_level: f32,
}
impl TilemapCorner {
pub const ATTRIBUTES: [VertexAttribute; 3] =
vertex_attr_array![0 => Float32x4, 1 => Float32x4, 2 => Float32x3];
pub fn vertex_buffer_layout() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: mem::size_of::<Self>().unwrapped_as(),
step_mode: VertexStepMode::Vertex,
attributes: &Self::ATTRIBUTES,
}
}
}
pub struct GpuTilemapData {
pub tilemap: Texture,
pub color: Rgba<f32>,
pub color_env: Option<u16>,
pub color_env_offset: i32,
pub tile_texture_size: f32,
pub tilemap_bounds: Vec2<f32>,
pub bounding_box: Aabr<f32>,
}
fn tile_to_gpu(tile: &Tile) -> [u8; 2] {
let id = tile.id;
let mut uv_transform_index = tile.flags;
uv_transform_index.set(TileFlags::OPAQUE, tile.flags.contains(TileFlags::ROTATE));
[id, uv_transform_index.bits() & 0b111]
}
pub fn bounding_box(layer: &TilesLayer) -> Aabr<f32> {
let tiles = layer.tiles.unwrap_ref();
let rows: Vec<_> = tiles.rows().into_iter().collect();
let top = match rows
.iter()
.position(|row| row.iter().any(|tile| tile.id != 0))
{
Some(0) => f32::NEG_INFINITY,
Some(pos) => pos as f32,
None => return Aabr::new_empty(Vec2::zero()), };
let bottom = match rows
.iter()
.rev()
.position(|row| row.iter().any(|tile| tile.id != 0))
.unwrap()
{
0 => f32::INFINITY,
pos => (rows.len() - pos) as f32,
};
let columns: Vec<_> = tiles.columns().into_iter().collect();
let left = match columns
.iter()
.position(|column| column.iter().any(|tile| tile.id != 0))
.unwrap()
{
0 => f32::NEG_INFINITY,
pos => pos as f32,
};
let right = match columns
.iter()
.rev()
.position(|column| column.iter().any(|tile| tile.id != 0))
.unwrap()
{
0 => f32::INFINITY,
pos => (columns.len() - pos) as f32,
};
Aabr {
min: Vec2::new(left, top),
max: Vec2::new(right, bottom),
}
}
impl GpuTilemapData {
pub fn view(&self) -> TextureView {
self.tilemap.create_view(&TextureViewDescriptor::default())
}
pub fn upload(
layer: &TilesLayer,
images: &[twmap::Image],
device: &Device,
queue: &Queue,
) -> Self {
let tiles = layer.tiles.unwrap_ref();
let (height, width) = tiles.dim();
let data: Vec<[u8; 2]> = tiles.iter().map(tile_to_gpu).collect();
let tilemap = device.create_texture_with_data(
queue,
&TextureDescriptor {
label: LABEL,
size: Extent3d {
width: width.unwrapped_as(),
height: height.unwrapped_as(),
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rg8Uint,
usage: TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
TextureDataOrder::LayerMajor,
bytemuck::cast_slice(&data),
);
let tile_texture_size = match layer.image {
None => 1.,
Some(index) => images[index as usize].size().w as f32 / 16.,
};
let tilemap_bounds = Vec2::new(width, height).az::<f32>() - Vec2::one();
let color = layer.color.az::<f32>() / u8::MAX as f32;
Self {
tilemap,
color,
color_env: layer.color_env,
color_env_offset: layer.color_env_offset,
tile_texture_size,
tilemap_bounds,
bounding_box: bounding_box(layer),
}
}
pub fn update(
&self,
vertices: &GpuBuffer<[TilemapCorner; 4]>,
group_projection: &Aabr<f32>,
tiles_on_screen: Vec2<f32>,
render_target_size: Vec2<u32>,
envelopes: &[twmap::Envelope],
client_time: i64,
server_time: i64,
queue: &Queue,
) {
let clipped_map_position = group_projection.intersection(self.bounding_box);
let viewport_map_range = group_projection.max - group_projection.min;
let map_to_clip_pos = |map_pos: Vec2<f32>| {
let rel_map_pos = map_pos - group_projection.min;
let normalized_map_pos = rel_map_pos / viewport_map_range;
(normalized_map_pos - 0.5) * 2. * Vec2::new(1., -1.)
};
let clipped_viewport_position = Aabr {
min: map_to_clip_pos(clipped_map_position.min),
max: map_to_clip_pos(clipped_map_position.max),
};
let color = match self.color_env {
None => self.color,
Some(index) => {
self.color
* envelopes[index as usize].sample(
client_time,
server_time,
self.color_env_offset,
)
}
};
let tile_pixels = tiles_on_screen * self.tile_texture_size;
let pixel_ratio = tile_pixels / render_target_size.az::<f32>();
let uncapped_mipmap_level = pixel_ratio.x.min(pixel_ratio.y).log2();
let mipmap_level = uncapped_mipmap_level.clamp(0., self.tile_texture_size.log2());
let new_vertices = [
Corner::TopLeft,
Corner::TopRight,
Corner::BottomLeft,
Corner::BottomRight,
]
.map(|corner| TilemapCorner {
clip_pos: corner.select_corner(&clipped_viewport_position),
world_pos: corner.select_corner(&clipped_map_position),
color,
tilemap_bounds: self.tilemap_bounds,
mipmap_level,
});
vertices.update(&new_vertices, queue);
}
}