+mod atlas;
+mod mesh;
+
use super::{media::MediaMgr, state::State, util::MatrixUniform};
+use atlas::create_atlas;
use cgmath::{prelude::*, Matrix4, Point3, Vector3};
+use mesh::create_mesh;
use mt_net::{MapBlock, NodeDef};
-use rand::Rng;
-use std::{collections::HashMap, ops::Range};
+use serde::{Deserialize, Serialize};
+use std::{collections::HashMap, sync::Arc};
use wgpu::util::DeviceExt;
+#[derive(Serialize, Deserialize, PartialEq, Eq, Copy, Clone, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum LeavesMode {
+ Opaque,
+ Simple,
+ Fancy,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
+pub struct MapRenderSettings {
+ pub leaves: LeavesMode,
+ pub opaque_liquids: bool,
+}
+
+impl Default for MapRenderSettings {
+ fn default() -> Self {
+ Self {
+ leaves: LeavesMode::Fancy,
+ opaque_liquids: false,
+ }
+ }
+}
+
+struct AtlasSlice {
+ cube_tex_coords: [[[f32; 2]; 6]; 6],
+}
+
+struct MeshMakeInfo {
+ // i optimized the shit out of these
+ textures: Vec<AtlasSlice>,
+ nodes: [Option<Box<NodeDef>>; u16::MAX as usize + 1],
+}
+
pub struct MapRender {
pipeline: wgpu::RenderPipeline,
- textures: HashMap<String, [Range<f32>; 2]>,
- nodes: HashMap<u16, NodeDef>,
atlas: wgpu::BindGroup,
model: wgpu::BindGroupLayout,
- blocks: HashMap<[i16; 3], BlockMesh>,
+ blocks: HashMap<[i16; 3], BlockModel>,
+ mesh_make_info: Arc<MeshMakeInfo>,
+ mesh_data_buffer: usize,
}
#[repr(C)]
struct BlockMesh {
vertex_buffer: wgpu::Buffer,
num_vertices: u32,
- model: MatrixUniform,
+}
+
+impl BlockMesh {
+ fn new(state: &State, vertices: &[Vertex]) -> Option<Self> {
+ if vertices.is_empty() {
+ return None;
+ }
+
+ Some(BlockMesh {
+ vertex_buffer: state
+ .device
+ .create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("mapblock.vertex_buffer"),
+ contents: bytemuck::cast_slice(vertices),
+ usage: wgpu::BufferUsages::VERTEX,
+ }),
+ num_vertices: vertices.len() as u32,
+ })
+ }
+
+ fn render<'a>(&'a self, pass: &mut wgpu::RenderPass<'a>, transform: &'a MatrixUniform) {
+ pass.set_bind_group(2, &transform.bind_group, &[]);
+ pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
+ pass.draw(0..self.num_vertices, 0..1);
+ }
+}
+
+struct BlockModel {
+ mesh: Option<BlockMesh>,
+ mesh_blend: Option<BlockMesh>,
+ transform: MatrixUniform,
+}
+
+fn block_float_pos(pos: Vector3<i16>) -> Vector3<f32> {
+ pos.cast::<f32>().unwrap() * 16.0
}
impl MapRender {
pass.set_bind_group(0, &self.atlas, &[]);
pass.set_bind_group(1, &state.camera_uniform.bind_group, &[]);
- for mesh in self.blocks.values() {
- pass.set_bind_group(2, &mesh.model.bind_group, &[]);
- pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
- pass.draw(0..mesh.num_vertices, 0..1);
+ struct BlendEntry<'a> {
+ dist: f32,
+ index: usize,
+ mesh: &'a BlockMesh,
+ transform: &'a MatrixUniform,
}
- }
- pub fn add_block(&mut self, state: &mut State, pos: Point3<i16>, block: Box<MapBlock>) {
- let mut vertices = Vec::with_capacity(10000);
- for (index, content) in block.param_0.iter().enumerate() {
- let def = match self.nodes.get(content) {
- Some(x) => x,
- None => continue,
- };
-
- use lerp::Lerp;
- use mt_net::{DrawType, Param1Type};
- use std::array::from_fn as array;
-
- match def.draw_type {
- DrawType::Cube | DrawType::AllFaces | DrawType::AllFacesOpt => {
- let light = match def.param1_type {
- Param1Type::Light => {
- println!("{}", block.param_1[index]);
-
- block.param_1[index] as f32 / 15.0
- }
- _ => 1.0,
- };
-
- let pos: [i16; 3] = array(|i| ((index >> (4 * i)) & 0xf) as i16);
- for (f, face) in CUBE.iter().enumerate() {
- let dir = FACE_DIR[f];
- let npos: [i16; 3] = array(|i| dir[i] + pos[i]);
- if npos.iter().all(|x| (0..16).contains(x)) {
- let nindex = npos[0] | (npos[1] << 4) | (npos[2] << 8);
-
- if let Some(ndef) = self.nodes.get(&block.param_0[nindex as usize]) {
- if ndef.draw_type == DrawType::Cube {
- continue;
- }
- }
- }
-
- let tile = &def.tiles[f];
- let rect = self.textures.get(&tile.texture).unwrap();
-
- for vertex in face.iter() {
- /*println!(
- "{:?} {:?} {:?} {:?}",
- (vertex.1[0], vertex.1[1]),
- (rect[0].start, rect[1].start),
- (rect[0].end, rect[1].end),
- (
- vertex.1[0].lerp(rect[0].start, rect[0].end),
- vertex.1[1].lerp(rect[1].start, rect[1].end)
- )
- );*/
- vertices.push(Vertex {
- pos: array(|i| pos[i] as f32 - 8.5 + vertex.0[i]),
- tex_coords: array(|i| rect[i].start.lerp(rect[i].end, vertex.1[i])),
- light,
- })
- }
- }
- }
- DrawType::None => {}
- _ => {
- // TODO
- }
+ let mut blend = Vec::new();
+
+ for (index, (pos, model)) in self.blocks.iter().enumerate() {
+ if let Some(mesh) = &model.mesh {
+ mesh.render(pass, &model.transform);
+ }
+
+ if let Some(mesh) = &model.mesh_blend {
+ blend.push(BlendEntry {
+ index,
+ dist: (block_float_pos(Vector3::from(*pos))
+ - Vector3::from(state.camera.position))
+ .magnitude(),
+ mesh,
+ transform: &model.transform,
+ });
}
}
+ blend.sort_unstable_by(|a, b| {
+ a.dist
+ .partial_cmp(&b.dist)
+ .unwrap_or(std::cmp::Ordering::Equal)
+ .then_with(|| a.index.cmp(&b.index))
+ });
+
+ for entry in blend {
+ entry.mesh.render(pass, entry.transform);
+ }
+ }
+
+ pub fn add_block(&mut self, state: &mut State, pos: Point3<i16>, block: Box<MapBlock>) {
+ let (pos, data) = create_mesh(
+ &mut self.mesh_data_buffer,
+ self.mesh_make_info.clone(),
+ &Default::default(),
+ pos,
+ block,
+ );
+
self.blocks.insert(
pos.into(),
- BlockMesh {
- vertex_buffer: state
- .device
- .create_buffer_init(&wgpu::util::BufferInitDescriptor {
- label: Some("mapblock.vertex_buffer"),
- contents: bytemuck::cast_slice(&vertices),
- usage: wgpu::BufferUsages::VERTEX,
- }),
- num_vertices: vertices.len() as u32,
- model: MatrixUniform::new(
+ BlockModel {
+ mesh: BlockMesh::new(state, &data.vertices),
+ mesh_blend: BlockMesh::new(state, &data.vertices_blend),
+ transform: MatrixUniform::new(
&state.device,
&self.model,
Matrix4::from_translation(
- pos.cast::<f32>().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5),
+ block_float_pos(pos.to_vec()) + Vector3::new(8.5, 8.5, 8.5),
),
"mapblock",
false,
);
}
- pub fn new(state: &mut State, media: &MediaMgr, nodes: HashMap<u16, NodeDef>) -> Self {
- let mut rng = rand::thread_rng();
- let mut atlas_map = HashMap::new();
- let mut atlas_alloc = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1));
-
- for node in nodes.values() {
- let tiles = node
- .tiles
- .iter()
- .chain(node.overlay_tiles.iter())
- .chain(node.special_tiles.iter());
-
- let load_texture = |texture: &str| {
- let payload = media
- .get(texture)
- .ok_or_else(|| format!("texture not found: {texture}"))?;
-
- image::load_from_memory(payload)
- .or_else(|_| {
- image::load_from_memory_with_format(payload, image::ImageFormat::Tga)
- })
- .map_err(|e| format!("failed to load texture {texture}: {e}"))
- .map(|x| image::imageops::flip_vertical(&x))
- };
-
- let mut make_texture = |texture: &str| {
- texture
- .split('^')
- .map(|part| match load_texture(part) {
- Ok(v) => v,
- Err(e) => {
- if !texture.is_empty() && !texture.contains('[') {
- eprintln!("{e}");
- }
-
- let mut img = image::RgbImage::new(1, 1);
- rng.fill(&mut img.get_pixel_mut(0, 0).0);
-
- image::DynamicImage::from(img).to_rgba8()
- }
- })
- .reduce(|mut base, top| {
- image::imageops::overlay(&mut base, &top, 0, 0);
- base
- })
- .unwrap()
- };
-
- for tile in tiles {
- atlas_map.entry(tile.texture.clone()).or_insert_with(|| {
- let img = make_texture(&tile.texture);
-
- let dimensions = img.dimensions();
- let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32);
-
- loop {
- match atlas_alloc.allocate(size) {
- None => {
- let mut atlas_size = atlas_alloc.size();
- atlas_size.width *= 2;
- atlas_size.height *= 2;
- atlas_alloc.grow(atlas_size);
- }
- Some(v) => return (img, v),
- }
- }
- });
- }
- }
-
- let atlas_size = atlas_alloc.size();
- let mut atlas = image::RgbaImage::new(atlas_size.width as u32, atlas_size.height as u32);
-
- let textures = atlas_map
- .into_iter()
- .map(|(name, (img, rect))| {
- let w = atlas_size.width as f32;
- let h = atlas_size.height as f32;
+ pub fn new(state: &mut State, media: &MediaMgr, mut nodes: HashMap<u16, NodeDef>) -> Self {
+ let (atlas_img, atlas_slices) = create_atlas(&mut nodes, media);
- let x = (rect.min.x as f32 / w)..(rect.max.x as f32 / w);
- let y = (rect.min.y as f32 / h)..(rect.max.y as f32 / h);
-
- use image::GenericImage;
- atlas
- .copy_from(&img, rect.min.x as u32, rect.min.y as u32)
- .unwrap();
-
- (name, [x, y])
- })
- .collect();
-
- let size = wgpu::Extent3d {
- width: atlas_size.width as u32,
- height: atlas_size.height as u32,
+ let atlas_size = wgpu::Extent3d {
+ width: atlas_img.width(),
+ height: atlas_img.height(),
depth_or_array_layers: 1,
};
let atlas_texture = state.device.create_texture(&wgpu::TextureDescriptor {
- size,
+ size: atlas_size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
- &atlas,
+ &atlas_img,
wgpu::ImageDataLayout {
offset: 0,
- bytes_per_row: std::num::NonZeroU32::new(4 * atlas_size.width as u32),
- rows_per_image: std::num::NonZeroU32::new(atlas_size.height as u32),
+ bytes_per_row: std::num::NonZeroU32::new(4 * atlas_img.width()),
+ rows_per_image: std::num::NonZeroU32::new(atlas_img.height()),
},
- size,
+ atlas_size,
);
let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
Self {
pipeline,
- nodes,
- textures,
+ mesh_make_info: Arc::new(MeshMakeInfo {
+ nodes: std::array::from_fn(|i| nodes.get(&(i as u16)).cloned().map(Box::new)),
+ textures: atlas_slices,
+ }),
+ mesh_data_buffer: 16384,
atlas: atlas_bind_group,
model: model_bind_group_layout,
blocks: HashMap::new(),
([-0.5, 0.5, -0.5], [ 0.0, 1.0]),
],
];
-
-#[rustfmt::skip]
-const FACE_DIR: [[i16; 3]; 6] = [
- [ 0, 1, 0],
- [ 0, -1, 0],
- [ 1, 0, 0],
- [-1, 0, 0],
- [ 0, 0, 1],
- [ 0, 0, -1],
-];
--- /dev/null
+use super::{super::media::MediaMgr, AtlasSlice, CUBE};
+use mt_net::NodeDef;
+use rand::Rng;
+use std::collections::HashMap;
+
+pub(super) fn create_atlas(
+ nodes: &mut HashMap<u16, NodeDef>,
+ media: &MediaMgr,
+) -> (image::RgbaImage, Vec<AtlasSlice>) {
+ let mut rng = rand::thread_rng();
+ let mut allocator = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1));
+ let mut textures = Vec::new();
+
+ for node in nodes.values_mut() {
+ let tiles = std::iter::empty()
+ .chain(node.tiles.iter_mut())
+ .chain(node.overlay_tiles.iter_mut())
+ .chain(node.special_tiles.iter_mut());
+
+ let load_texture = |texture: &str| {
+ let payload = media
+ .get(texture)
+ .ok_or_else(|| format!("texture not found: {texture}"))?;
+
+ image::load_from_memory(payload)
+ .or_else(|_| image::load_from_memory_with_format(payload, image::ImageFormat::Tga))
+ .map_err(|e| format!("failed to load texture {texture}: {e}"))
+ .map(|x| image::imageops::flip_vertical(&x))
+ };
+
+ let mut make_texture = |texture: &str| {
+ texture
+ .split('^')
+ .map(|part| match load_texture(part) {
+ Ok(v) => v,
+ Err(e) => {
+ if !texture.is_empty() && !texture.contains('[') {
+ eprintln!("{e}");
+ }
+
+ let mut img = image::RgbImage::new(1, 1);
+ rng.fill(&mut img.get_pixel_mut(0, 0).0);
+
+ image::DynamicImage::from(img).to_rgba8()
+ }
+ })
+ .reduce(|mut base, top| {
+ image::imageops::overlay(&mut base, &top, 0, 0);
+ base
+ })
+ .unwrap()
+ };
+
+ let mut id_map = HashMap::new();
+
+ for tile in tiles {
+ tile.texture.custom = *id_map.entry(tile.texture.name.clone()).or_insert_with(|| {
+ let img = make_texture(&tile.texture.name);
+
+ let dimensions = img.dimensions();
+ let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32);
+
+ loop {
+ match allocator.allocate(size) {
+ None => {
+ let mut atlas_size = allocator.size();
+ atlas_size.width *= 2;
+ atlas_size.height *= 2;
+ allocator.grow(atlas_size);
+ }
+ Some(rect) => {
+ let id = textures.len();
+ textures.push((img, rect));
+ return id;
+ }
+ }
+ }
+ })
+ }
+ }
+
+ let size = allocator.size();
+ let mut atlas = image::RgbaImage::new(size.width as u32, size.height as u32);
+
+ let slices = textures
+ .into_iter()
+ .map(|(img, rect)| {
+ let w = size.width as f32;
+ let h = size.height as f32;
+
+ let x = (rect.min.x as f32 / w)..(rect.max.x as f32 / w);
+ let y = (rect.min.y as f32 / h)..(rect.max.y as f32 / h);
+
+ use image::GenericImage;
+ atlas
+ .copy_from(&img, rect.min.x as u32, rect.min.y as u32)
+ .unwrap();
+
+ use lerp::Lerp;
+ use std::array::from_fn as array;
+
+ let rect = [x, y];
+ let cube_tex_coords =
+ array(|f| array(|v| array(|i| rect[i].start.lerp(rect[i].end, CUBE[f][v].1[i]))));
+
+ AtlasSlice { cube_tex_coords }
+ })
+ .collect();
+
+ (atlas, slices)
+}
--- /dev/null
+use super::{LeavesMode, MapRenderSettings, MeshMakeInfo, Vertex, CUBE};
+use cgmath::Point3;
+use mt_net::MapBlock;
+use std::ops::Deref;
+use std::sync::Arc;
+
+#[derive(Clone)]
+pub(super) struct MeshData {
+ pub vertices: Vec<Vertex>,
+ pub vertices_blend: Vec<Vertex>,
+}
+
+impl MeshData {
+ pub fn new(cap: usize) -> Self {
+ Self {
+ vertices: Vec::with_capacity(cap),
+ vertices_blend: Vec::with_capacity(cap),
+ }
+ }
+
+ pub fn cap(&self) -> usize {
+ std::cmp::max(self.vertices.capacity(), self.vertices_blend.capacity())
+ }
+}
+
+pub(super) fn create_mesh(
+ buffer_cap: &mut usize,
+ mkinfo: Arc<MeshMakeInfo>,
+ settings: &MapRenderSettings,
+ pos: Point3<i16>,
+ block: Box<MapBlock>,
+) -> (Point3<i16>, MeshData) {
+ let mkinfo = mkinfo.deref();
+ let mut buffer = MeshData::new(*buffer_cap);
+
+ for (index, content) in block.param_0.iter().enumerate() {
+ let def = match &mkinfo.nodes[*content as usize] {
+ Some(x) => x,
+ None => continue,
+ };
+
+ use mt_net::{DrawType, Param1Type};
+ use std::array::from_fn as array;
+
+ let mut tiles = &def.tiles;
+ let mut draw_type = def.draw_type;
+
+ match draw_type {
+ DrawType::AllFacesOpt => {
+ draw_type = match settings.leaves {
+ LeavesMode::Opaque => DrawType::Cube,
+ LeavesMode::Simple => {
+ tiles = &def.special_tiles;
+
+ DrawType::GlassLike
+ }
+ LeavesMode::Fancy => DrawType::AllFaces,
+ };
+ }
+ DrawType::None => continue,
+ _ => {}
+ }
+
+ let light = match def.param1_type {
+ Param1Type::Light => block.param_1[index] as f32 / 15.0,
+ _ => 1.0,
+ };
+
+ let pos: [i16; 3] = array(|i| ((index >> (4 * i)) & 0xf) as i16);
+
+ for (f, face) in CUBE.iter().enumerate() {
+ let dir = FACE_DIR[f];
+ let npos: [i16; 3] = array(|i| dir[i] + pos[i]);
+ if npos.iter().all(|x| (0..16).contains(x)) {
+ let nindex = npos[0] | (npos[1] << 4) | (npos[2] << 8);
+
+ if let Some(ndef) = &mkinfo.nodes[block.param_0[nindex as usize] as usize] {
+ if ndef.draw_type == DrawType::Cube {
+ continue;
+ }
+ }
+ }
+
+ let tile = &tiles[f];
+ let texture = mkinfo.textures[tile.texture.custom].cube_tex_coords[f];
+
+ let mut add_vertex = |vertex: (usize, &([f32; 3], [f32; 2]))| {
+ /*println!(
+ "{:?} {:?} {:?} {:?}",
+ (vertex.1[0], vertex.1[1]),
+ (rect[0].start, rect[1].start),
+ (rect[0].end, rect[1].end),
+ (
+ vertex.1[0].lerp(rect[0].start, rect[0].end),
+ vertex.1[1].lerp(rect[1].start, rect[1].end)
+ )
+ );*/
+
+ buffer.vertices.push(Vertex {
+ pos: array(|i| pos[i] as f32 - 8.5 + vertex.1 .0[i]),
+ tex_coords: texture[vertex.0],
+ light,
+ });
+ };
+
+ face.iter().enumerate().for_each(&mut add_vertex);
+ /*if !backface_cull {
+ face.iter().enumerate().rev().for_each(&mut add_vertex);
+ }*/
+ }
+ }
+
+ *buffer_cap = buffer.cap();
+ (pos, buffer)
+}
+
+#[rustfmt::skip]
+const FACE_DIR: [[i16; 3]; 6] = [
+ [ 0, 1, 0],
+ [ 0, -1, 0],
+ [ 1, 0, 0],
+ [-1, 0, 0],
+ [ 0, 0, 1],
+ [ 0, 0, -1],
+];