use crate::{ mesh::{ greedy::{self, GreedyConfig, GreedyMesh}, MeshGen, Meshable, }, render::{ self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline, TerrainPipeline, }, scene::math, }; use common::{ figure::Cell, vol::{BaseVol, ReadVol, SizedVol, Vox}, }; use core::ops::Range; use vek::*; type SpriteVertex = ::Vertex; type TerrainVertex = ::Vertex; type ParticleVertex = ::Vertex; impl<'a: 'b, 'b, V: 'a> Meshable> for V where V: BaseVol + ReadVol + SizedVol, /* TODO: Use VolIterator instead of manually iterating * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, * &'a V: BaseVol, */ { type Pipeline = TerrainPipeline; /// NOTE: The result provides the (roughly) computed bounds for the model, /// and the vertex range meshed for this model; we return this instead /// of the full opaque mesh so we can avoid allocating a separate mesh /// for each bone. /// /// Later, we can iterate through the bone array and correctly assign bone /// ids to all vertices in range for each segment. /// /// FIXME: A refactor of the figure cache to not just return an array of /// models (thus allowing us to knoe the bone index ahead of time) would /// avoid needing per-bone information at all. type Result = (math::Aabb, Range); type ShadowPipeline = ShadowPipeline; type Supplement = ( &'b mut GreedyMesh<'a>, &'b mut Mesh, Vec3, Vec3, ); type TranslucentPipeline = FigurePipeline; #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn generate_mesh( self, (greedy, opaque_mesh, offs, scale): Self::Supplement, ) -> MeshGen, Self> { let max_size = greedy.max_size(); // NOTE: Required because we steal two bits from the normal in the shadow uint // in order to store the bone index. The two bits are instead taken out // of the atlas coordinates, which is why we "only" allow 1 << 15 per // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 15); let lower_bound = self.lower_bound(); let upper_bound = self.upper_bound(); assert!( lower_bound.x <= upper_bound.x && lower_bound.y <= upper_bound.y && lower_bound.z <= upper_bound.z ); // NOTE: Figure sizes should be no more than 512 along each axis. let greedy_size = upper_bound - lower_bound + 1; assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512); // NOTE: Cast to usize is safe because of previous check, since all values fit // into u16 which is safe to cast to usize. let greedy_size = greedy_size.as_::(); let greedy_size_cross = greedy_size; let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { 1.0 } else { 0.0 } }; let get_color = |vol: &mut V, pos: Vec3| { vol.get(pos) .ok() .and_then(|vox| vox.get_color()) .unwrap_or(Rgb::zero()) }; let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true); let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { should_draw_greedy(pos, delta, uv, |vox| { vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty()) }) }; let create_opaque = |atlas_pos, pos, norm| { TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, 0) }; let start = opaque_mesh.vertices().len(); greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, greedy_size_cross, get_light, get_color, get_opacity, should_draw, push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { opaque_mesh.push_quad(greedy::create_quad( atlas_origin, dim, origin, draw_dim, norm, meta, |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), )); }, }); let bounds = math::Aabb { // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. min: math::Vec3::from((lower_bound.as_::() + offs) * scale), max: math::Vec3::from((upper_bound.as_::() + offs) * scale), } .made_valid(); let vertex_range = start..opaque_mesh.vertices().len(); ( Mesh::new(), Mesh::new(), Mesh::new(), (bounds, vertex_range), ) } } impl<'a: 'b, 'b, V: 'a> Meshable> for V where V: BaseVol + ReadVol + SizedVol, /* TODO: Use VolIterator instead of manually iterating * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, * &'a V: BaseVol, */ { type Pipeline = SpritePipeline; type Result = (); type ShadowPipeline = ShadowPipeline; type Supplement = (&'b mut GreedyMesh<'a>, &'b mut Mesh, bool); type TranslucentPipeline = SpritePipeline; #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn generate_mesh( self, (greedy, opaque_mesh, vertical_stripes): Self::Supplement, ) -> MeshGen, Self> { let max_size = greedy.max_size(); // NOTE: Required because we steal two bits from the normal in the shadow uint // in order to store the bone index. The two bits are instead taken out // of the atlas coordinates, which is why we "only" allow 1 << 15 per // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 16); let lower_bound = self.lower_bound(); let upper_bound = self.upper_bound(); assert!( lower_bound.x <= upper_bound.x && lower_bound.y <= upper_bound.y && lower_bound.z <= upper_bound.z ); let greedy_size = upper_bound - lower_bound + 1; assert!( greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, "Sprite size out of bounds: {:?} ≤ (15, 15, 63)", greedy_size - 1 ); // NOTE: Cast to usize is safe because of previous check, since all values fit // into u16 which is safe to cast to usize. let greedy_size = greedy_size.as_::(); let greedy_size_cross = greedy_size; let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { 1.0 } else { 0.0 } }; let get_color = |vol: &mut V, pos: Vec3| { vol.get(pos) .ok() .and_then(|vox| vox.get_color()) .unwrap_or(Rgb::zero()) }; let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true); let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| { vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty()) }) }; let create_opaque = |atlas_pos, pos: Vec3, norm, _meta| SpriteVertex::new(atlas_pos, pos, norm); greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, greedy_size_cross, get_light, get_color, get_opacity, should_draw, push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| { opaque_mesh.push_quad(greedy::create_quad( atlas_origin, dim, origin, draw_dim, norm, meta, |atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta), )); }, }); (Mesh::new(), Mesh::new(), Mesh::new(), ()) } } impl<'a: 'b, 'b, V: 'a> Meshable> for V where V: BaseVol + ReadVol + SizedVol, /* TODO: Use VolIterator instead of manually iterating * &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>, * &'a V: BaseVol, */ { type Pipeline = ParticlePipeline; type Result = (); type ShadowPipeline = ShadowPipeline; type Supplement = &'b mut GreedyMesh<'a>; type TranslucentPipeline = ParticlePipeline; #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn generate_mesh( self, greedy: Self::Supplement, ) -> MeshGen, Self> { let max_size = greedy.max_size(); // NOTE: Required because we steal two bits from the normal in the shadow uint // in order to store the bone index. The two bits are instead taken out // of the atlas coordinates, which is why we "only" allow 1 << 15 per // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 16); let lower_bound = self.lower_bound(); let upper_bound = self.upper_bound(); assert!( lower_bound.x <= upper_bound.x && lower_bound.y <= upper_bound.y && lower_bound.z <= upper_bound.z ); let greedy_size = upper_bound - lower_bound + 1; assert!( greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, "Particle size out of bounds: {:?} ≤ (15, 15, 63)", greedy_size - 1 ); // NOTE: Cast to usize is safe because of previous check, since all values fit // into u16 which is safe to cast to usize. let greedy_size = greedy_size.as_::(); let greedy_size_cross = greedy_size; let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { 1.0 } else { 0.0 } }; let get_color = |vol: &mut V, pos: Vec3| { vol.get(pos) .ok() .and_then(|vox| vox.get_color()) .unwrap_or(Rgb::zero()) }; let get_opacity = |vol: &mut V, pos: Vec3| vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true); let should_draw = |vol: &mut V, pos: Vec3, delta: Vec3, uv| { should_draw_greedy(pos, delta, uv, |vox| { vol.get(vox).map(|vox| *vox).unwrap_or(Vox::empty()) }) }; let create_opaque = |_atlas_pos, pos: Vec3, norm| ParticleVertex::new(pos, norm); let mut opaque_mesh = Mesh::new(); greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, greedy_size_cross, get_light, get_color, get_opacity, should_draw, push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &()| { opaque_mesh.push_quad(greedy::create_quad( atlas_origin, dim, origin, draw_dim, norm, meta, |atlas_pos, pos, norm, &_meta| create_opaque(atlas_pos, pos, norm), )); }, }); (opaque_mesh, Mesh::new(), Mesh::new(), ()) } } fn should_draw_greedy( pos: Vec3, delta: Vec3, _uv: Vec2>, flat_get: impl Fn(Vec3) -> Cell, ) -> Option<(bool, /* u8 */ ())> { let from = flat_get(pos - delta); let to = flat_get(pos); let from_opaque = !from.is_empty(); if from_opaque != to.is_empty() { None } else { // If going from transparent to opaque, backward facing; otherwise, forward // facing. Some((from_opaque, ())) } } fn should_draw_greedy_ao( vertical_stripes: bool, pos: Vec3, delta: Vec3, _uv: Vec2>, flat_get: impl Fn(Vec3) -> Cell, ) -> Option<(bool, bool)> { let from = flat_get(pos - delta); let to = flat_get(pos); let from_opaque = !from.is_empty(); if from_opaque != to.is_empty() { None } else { let faces_forward = from_opaque; let ao = !vertical_stripes || (pos.z & 1) != 0; // If going from transparent to opaque, backward facing; otherwise, forward // facing. Some((faces_forward, ao)) } }