Substantial improvements to meshing time.

This mostly come out of optimizing BlocksOfInterest to (empirically)
minimize redundant computations, use a more efficient RNG, use a faster
verion of iter_changed, and optimize water block handling (theoretically
the iter_changed difference might mean we missed some water blocks, but
in practice it's unlikely to matter for fast-moving rivers).

Also did some microoptimizations of meshing etc. that seem to result in
pretty good improvements in practice, and also added another set of
optimizations to improve tree performance (special casing "easy" segment
approaches, which got a few percent, and inlining block_from_structure
for tree leaves and branches, which got us considerably more; I think
the total improvement is around 5%).
This commit is contained in:
Joshua Yanovski 2022-07-31 01:28:37 -07:00
parent 901aa8e1b4
commit 2a61c7790b
15 changed files with 593 additions and 265 deletions

View File

@ -280,7 +280,7 @@ pub struct CharacterList {
pub loading: bool, pub loading: bool,
} }
const TOTAL_PENDING_CHUNKS_LIMIT: usize = 1024; const TOTAL_PENDING_CHUNKS_LIMIT: usize = 2048;
impl Client { impl Client {
pub async fn new( pub async fn new(
@ -1872,7 +1872,7 @@ impl Client {
for key in keys.iter() { for key in keys.iter() {
if self.state.terrain().get_key(*key).is_none() { if self.state.terrain().get_key(*key).is_none() {
if !skip_mode && !self.pending_chunks.contains_key(key) { if !skip_mode && !self.pending_chunks.contains_key(key) {
const CURRENT_TICK_PENDING_CHUNKS_LIMIT: usize = 8 * 4; const CURRENT_TICK_PENDING_CHUNKS_LIMIT: usize = 8 * 5;
if self.pending_chunks.len() < TOTAL_PENDING_CHUNKS_LIMIT if self.pending_chunks.len() < TOTAL_PENDING_CHUNKS_LIMIT
&& current_tick_send_chunk_requests && current_tick_send_chunk_requests
< CURRENT_TICK_PENDING_CHUNKS_LIMIT < CURRENT_TICK_PENDING_CHUNKS_LIMIT

View File

@ -118,6 +118,18 @@ impl BlockKind {
| BlockKind::Sand | BlockKind::Sand
) )
} }
#[inline(always)]
/// NOTE: Do not call unless you are handling the case where the block is a sprite elsewhere,
/// as it will give incorrect results in this case!
pub const fn get_glow_raw(&self) -> Option<u8> {
match self {
BlockKind::Lava => Some(24),
BlockKind::GlowingRock | BlockKind::GlowingWeakRock => Some(10),
BlockKind::GlowingMushroom => Some(20),
_ => None,
}
}
} }
/// XXX(@Sharp): If you feel like significantly modifying how Block works, you *MUST* also update /// XXX(@Sharp): If you feel like significantly modifying how Block works, you *MUST* also update
@ -209,69 +221,41 @@ impl Block {
} }
} }
#[inline] #[inline(always)]
/// NOTE: Do not call unless you already know this block is not filled!
pub fn get_sprite_raw(&self) -> Option<SpriteKind> {
SpriteKind::from_u8(self.attr[0])
}
#[inline(always)]
pub fn get_sprite(&self) -> Option<SpriteKind> { pub fn get_sprite(&self) -> Option<SpriteKind> {
if !self.is_filled() { if !self.is_filled() {
SpriteKind::from_u8(self.attr[0]) self.get_sprite_raw()
} else { } else {
None None
} }
} }
#[inline(always)]
/// NOTE: Do not call unless you already know this block is a sprite and already called
/// has_ori on it!
pub const fn get_ori_raw(&self) -> u8 {
self.attr[1] & 0b111
}
#[inline] #[inline]
pub fn get_ori(&self) -> Option<u8> { pub fn get_ori(&self) -> Option<u8> {
if self.get_sprite()?.has_ori() { if self.get_sprite()?.has_ori() {
// TODO: Formalise this a bit better // TODO: Formalise this a bit better
Some(self.attr[1] & 0b111) Some(self.get_ori_raw())
} else { } else {
None None
} }
} }
#[inline] #[inline(always)]
pub fn get_glow(&self) -> Option<u8> { pub fn get_glow(&self) -> Option<u8> {
match self.kind() { self.get_glow_raw().or_else(|| self.get_sprite().and_then(|sprite| sprite.get_glow()))
BlockKind::Lava => Some(24),
BlockKind::GlowingRock | BlockKind::GlowingWeakRock => Some(10),
BlockKind::GlowingMushroom => Some(20),
_ => match self.get_sprite()? {
SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24),
SpriteKind::Ember => Some(20),
SpriteKind::WallLamp
| SpriteKind::WallLampSmall
| SpriteKind::WallSconce
| SpriteKind::FireBowlGround
| SpriteKind::ChristmasOrnament
| SpriteKind::CliffDecorBlock
| SpriteKind::Orb => Some(16),
SpriteKind::Velorite
| SpriteKind::VeloriteFrag
| SpriteKind::CavernGrassBlueShort
| SpriteKind::CavernGrassBlueMedium
| SpriteKind::CavernGrassBlueLong
| SpriteKind::CavernLillypadBlue
| SpriteKind::CavernMycelBlue
| SpriteKind::CeilingMushroom => Some(6),
SpriteKind::CaveMushroom
| SpriteKind::CookingPot
| SpriteKind::CrystalHigh
| SpriteKind::CrystalLow => Some(10),
SpriteKind::Amethyst
| SpriteKind::Ruby
| SpriteKind::Sapphire
| SpriteKind::Diamond
| SpriteKind::Emerald
| SpriteKind::Topaz
| SpriteKind::AmethystSmall
| SpriteKind::TopazSmall
| SpriteKind::DiamondSmall
| SpriteKind::RubySmall
| SpriteKind::EmeraldSmall
| SpriteKind::SapphireSmall => Some(3),
SpriteKind::Lantern => Some(24),
_ => None,
},
}
} }
// minimum block, attenuation // minimum block, attenuation
@ -397,7 +381,7 @@ impl Block {
} }
} }
#[inline] #[inline(always)]
pub fn kind(&self) -> BlockKind { self.kind } pub fn kind(&self) -> BlockKind { self.kind }
/// If this block is a fluid, replace its sprite. /// If this block is a fluid, replace its sprite.

View File

@ -195,15 +195,18 @@ impl<V, Storage: core::ops::DerefMut<Target=Vec<V>>, S: RectVolSize, M: Clone> C
/// Iterate through the voxels in this chunk, attempting to avoid those that /// Iterate through the voxels in this chunk, attempting to avoid those that
/// are unchanged (i.e: match the `below` and `above` voxels). This is /// are unchanged (i.e: match the `below` and `above` voxels). This is
/// generally useful for performance reasons. /// generally useful for performance reasons.
pub fn iter_changed(&self) -> impl Iterator<Item = (Vec3<i32>, &V)> + '_ { pub fn iter_changed(&self) -> impl Iterator<Item = (/*Vec3<i32>*/u32, &V)> + '_ {
self.sub_chunks self.sub_chunks
.iter() .iter()
.enumerate() .enumerate()
.filter(|(_, sc)| sc.num_groups() > 0) .filter(|(_, sc)| sc.num_groups() > 0)
.flat_map(move |(i, sc)| { .flat_map(move |(i, sc)| {
let z_offset = self.z_offset + i as i32 * SubChunkSize::<V, Storage, S>::SIZE.z as i32; /* let z_offset = self.z_offset + i as i32 * SubChunkSize::<V, Storage, S>::SIZE.z as i32;
sc.vol_iter(Vec3::zero(), SubChunkSize::<V, Storage, S>::SIZE.map(|e| e as i32)) let z_delta = Vec3::unit_z() * z_offset; */
.map(move |(pos, vox)| (pos + Vec3::unit_z() * z_offset, vox)) let z_delta = i as u32 * SubChunk::<V, Storage, S, M>::VOLUME;
sc.iter_changed()
/* sc.vol_iter(Vec3::zero(), SubChunkSize::<V, Storage, S>::SIZE.map(|e| e as i32)) */
.map(move |(pos, vox)| (pos + z_delta, vox))
}) })
} }

View File

@ -32,7 +32,7 @@ pub struct TerrainChunkSize;
/// Base two logarithm of the number of blocks along either horizontal axis of /// Base two logarithm of the number of blocks along either horizontal axis of
/// a chunk. /// a chunk.
/// ///
/// NOTE: (1 << CHUNK_SIZE_LG) is guaranteed to fit in a u32. /// NOTE: (1 << TERRAIN_CHUNK_BLOCKS_LG) is guaranteed to fit in a u32.
/// ///
/// NOTE: A lot of code assumes that the two dimensions are equal, so we make it /// NOTE: A lot of code assumes that the two dimensions are equal, so we make it
/// explicit here. /// explicit here.

View File

@ -453,6 +453,47 @@ impl SpriteKind {
} }
} }
#[inline]
pub fn get_glow(&self) -> Option<u8> {
match self {
SpriteKind::StreetLamp | SpriteKind::StreetLampTall => Some(24),
SpriteKind::Ember => Some(20),
SpriteKind::WallLamp
| SpriteKind::WallLampSmall
| SpriteKind::WallSconce
| SpriteKind::FireBowlGround
| SpriteKind::ChristmasOrnament
| SpriteKind::CliffDecorBlock
| SpriteKind::Orb => Some(16),
SpriteKind::Velorite
| SpriteKind::VeloriteFrag
| SpriteKind::CavernGrassBlueShort
| SpriteKind::CavernGrassBlueMedium
| SpriteKind::CavernGrassBlueLong
| SpriteKind::CavernLillypadBlue
| SpriteKind::CavernMycelBlue
| SpriteKind::CeilingMushroom => Some(6),
SpriteKind::CaveMushroom
| SpriteKind::CookingPot
| SpriteKind::CrystalHigh
| SpriteKind::CrystalLow => Some(10),
SpriteKind::Amethyst
| SpriteKind::Ruby
| SpriteKind::Sapphire
| SpriteKind::Diamond
| SpriteKind::Emerald
| SpriteKind::Topaz
| SpriteKind::AmethystSmall
| SpriteKind::TopazSmall
| SpriteKind::DiamondSmall
| SpriteKind::RubySmall
| SpriteKind::EmeraldSmall
| SpriteKind::SapphireSmall => Some(3),
SpriteKind::Lantern => Some(24),
_ => None,
}
}
#[inline] #[inline]
pub fn has_ori(&self) -> bool { pub fn has_ori(&self) -> bool {
matches!( matches!(

View File

@ -194,6 +194,45 @@ impl<V, S: core::ops::DerefMut<Target=Vec<V>> + VolSize<V>, M> Chunk<V, S, M> {
}); });
} }
/// Iterate as fast as possible over all changed (non-default) blocks.
pub fn iter_changed(&self) -> impl Iterator<Item = (/* Vec3<i32> */u32, &V)> + '_
{
let vox = &self.vox;
self.indices
.iter()
.enumerate()
.flat_map(|(grp_idx, &base)| {
/* let grp_idx = grp_idx as i32;
let x = grp_idx & (Chunk::<V, S, M>::GROUP_COUNT.x as i32 - 1);
let y = (grp_idx >> 3) & (Chunk::<V, S, M>::GROUP_COUNT.y as i32 - 1);
let z = (grp_idx >> 6) & (Chunk::<V, S, M>::GROUP_COUNT.z as i32 - 1);
let grp_off = Vec3::new(
x * Chunk::<V, S, M>::GROUP_SIZE.x as i32,
y * Chunk::<V, S, M>::GROUP_SIZE.y as i32,
z * Chunk::<V, S, M>::GROUP_SIZE.z as i32,
); */
let grp_idx = grp_idx as u32;
let grp_off = grp_idx * Self::GROUP_VOLUME/* as usize */;
let start = usize::from(base) * Self::GROUP_VOLUME as usize;
let end = start + Self::GROUP_VOLUME as usize;
vox.get(start..end)
.into_iter()
.flatten()
.enumerate()
.map(move |(rel_idx, block)| {
// Construct the relative position.
/* let rel_idx = rel_idx as i32;
let x = rel_idx & (Chunk::<V, S, M>::GROUP_SIZE.x as i32 - 1);
let y = (rel_idx >> 2) & (Chunk::<V, S, M>::GROUP_SIZE.y as i32 - 1);
let z = (rel_idx >> 4) & (Chunk::<V, S, M>::GROUP_SIZE.z as i32 - 1);
let rel_off = Vec3::new(x, y, z); */
let rel_off = rel_idx as u32;
(grp_off | rel_off, block)
})
})
}
/// Compress this subchunk by frequency. /// Compress this subchunk by frequency.
pub fn defragment(&mut self) pub fn defragment(&mut self)
where where

View File

@ -8,6 +8,7 @@
bool_to_option, bool_to_option,
drain_filter, drain_filter,
once_cell, once_cell,
stmt_expr_attributes,
trait_alias, trait_alias,
option_get_or_insert_default, option_get_or_insert_default,
map_try_insert, map_try_insert,

View File

@ -601,14 +601,23 @@ fn greedy_mesh_cross_section<M: PartialEq>(
// mask represents which faces are either set while the other is unset, or unset // mask represents which faces are either set while the other is unset, or unset
// while the other is set. // while the other is set.
let mut mask = (0..dims.y * dims.x).map(|_| None).collect::<Vec<_>>(); let mut mask = (0..dims.y * dims.x).map(|_| None).collect::<Vec<_>>();
let mut mask = &mut mask[0..dims.y * dims.x];
(0..dims.z + 1).for_each(|d| { (0..dims.z + 1).for_each(|d| {
// Compute mask // Compute mask
mask.iter_mut().enumerate().for_each(|(posi, mask)| { let mut posi = 0;
(0..dims.y).for_each(|j| {
(0..dims.x).for_each(|i| {
// NOTE: Safe because dims.z actually fits in a u16.
mask[posi] = draw_face(Vec3::new(i as i32, j as i32, d as i32));
posi += 1;
});
});
/* mask.iter_mut().enumerate().for_each(|(posi, mask)| {
let i = posi % dims.x; let i = posi % dims.x;
let j = posi / dims.x; let j = posi / dims.x;
// NOTE: Safe because dims.z actually fits in a u16. // NOTE: Safe because dims.z actually fits in a u16.
*mask = draw_face(Vec3::new(i as i32, j as i32, d as i32)); *mask = draw_face(Vec3::new(i as i32, j as i32, d as i32));
}); }); */
(0..dims.y).for_each(|j| { (0..dims.y).for_each(|j| {
let mut i = 0; let mut i = 0;

View File

@ -227,6 +227,7 @@ fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
#[inline(always)]
pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static>( pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + 'static>(
vol: &'a VolGrid2d<V>, vol: &'a VolGrid2d<V>,
(range, max_texture_size, _boi): (Aabb<i32>, Vec2<u16>, &'a BlocksOfInterest), (range, max_texture_size, _boi): (Aabb<i32>, Vec2<u16>, &'a BlocksOfInterest),
@ -279,17 +280,18 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
let mut opaque_limits = None::<Limits>; let mut opaque_limits = None::<Limits>;
let mut fluid_limits = None::<Limits>; let mut fluid_limits = None::<Limits>;
let mut air_limits = None::<Limits>; let mut air_limits = None::<Limits>;
let mut flat;
let flat_get = { let flat_get = {
span!(_guard, "copy to flat array"); span!(_guard, "copy to flat array");
let (w, h, d) = range.size().into_tuple(); let (w, h, d) = range.size().into_tuple();
// z can range from -1..range.size().d + 1 // z can range from -1..range.size().d + 1
let d = d + 2; let d = d + 2;
let flat = { /*let flat = */{
let mut volume = vol.cached(); let mut volume = vol.cached();
const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty); const AIR: Block = Block::air(common::terrain::sprite::SpriteKind::Empty);
// TODO: Once we can manage it sensibly, consider using something like // TODO: Once we can manage it sensibly, consider using something like
// Option<Block> instead of just assuming air. // Option<Block> instead of just assuming air.
let mut flat = vec![AIR; (w * h * d) as usize]; /*let mut */flat = vec![AIR; (w * h * d) as usize];
let mut i = 0; let mut i = 0;
for x in 0..range.size().w { for x in 0..range.size().w {
for y in 0..range.size().h { for y in 0..range.size().h {
@ -320,16 +322,19 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
} }
} }
} }
flat /* flat */
}; };
move |Vec3 { x, y, z }| { let hd = h * d;
let flat = &flat[0..(w * hd) as usize];
#[inline(always)] move |Vec3 { x, y, z }| {
// z can range from -1..range.size().d + 1 // z can range from -1..range.size().d + 1
let z = z + 1; let z = z + 1;
match flat.get((x * h * d + y * d + z) as usize).copied() { flat[(x * hd + y * d + z) as usize]
/* match flat.get((x * hd + y * d + z) as usize).copied() {
Some(b) => b, Some(b) => b,
None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h), None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
} } */
} }
}; };
@ -370,28 +375,28 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z); let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z);
let draw_delta = Vec3::new(1, 1, z_start); let draw_delta = Vec3::new(1, 1, z_start);
let get_light = |_: &mut (), pos: Vec3<i32>| { let get_light = #[inline(always)] |_: &mut (), pos: Vec3<i32>| {
if flat_get(pos).is_opaque() { if flat_get(pos).is_opaque() {
0.0 0.0
} else { } else {
light(pos + range.min) light(pos + range.min)
} }
}; };
let get_ao = |_: &mut (), pos: Vec3<i32>| { let get_ao = #[inline(always)] |_: &mut (), pos: Vec3<i32>| {
if flat_get(pos).is_opaque() { 0.0 } else { 1.0 } if flat_get(pos).is_opaque() { 0.0 } else { 1.0 }
}; };
let get_glow = |_: &mut (), pos: Vec3<i32>| glow(pos + range.min); let get_glow = #[inline(always)] |_: &mut (), pos: Vec3<i32>| glow(pos + range.min);
let get_color = let get_color =
|_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or_else(Rgb::zero); #[inline(always)] |_: &mut (), pos: Vec3<i32>| flat_get(pos).get_color().unwrap_or_else(Rgb::zero);
let get_opacity = |_: &mut (), pos: Vec3<i32>| !flat_get(pos).is_opaque(); let get_opacity = #[inline(always)] |_: &mut (), pos: Vec3<i32>| !flat_get(pos).is_opaque();
let should_draw = |_: &mut (), pos: Vec3<i32>, delta: Vec3<i32>, _uv| { let should_draw = #[inline(always)] |_: &mut (), pos: Vec3<i32>, delta: Vec3<i32>, _uv| {
should_draw_greedy(pos, delta, &flat_get) should_draw_greedy(pos, delta, #[inline(always)] |pos| flat_get(pos))
}; };
// NOTE: Conversion to f32 is fine since this i32 is actually in bounds for u16. // NOTE: Conversion to f32 is fine since this i32 is actually in bounds for u16.
let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32); let mesh_delta = Vec3::new(0.0, 0.0, (z_start + range.min.z) as f32);
let create_opaque = let create_opaque =
|atlas_pos, pos, norm, meta| TerrainVertex::new(atlas_pos, pos + mesh_delta, norm, meta); #[inline(always)] |atlas_pos, pos, norm, meta| TerrainVertex::new(atlas_pos, pos + mesh_delta, norm, meta);
let create_transparent = |_atlas_pos, pos, norm| FluidVertex::new(pos + mesh_delta, norm); let create_transparent = #[inline(always)] |_atlas_pos, pos, norm| FluidVertex::new(pos + mesh_delta, norm);
let mut greedy = let mut greedy =
GreedyMesh::<guillotiere::SimpleAtlasAllocator>::new(max_size, greedy::general_config()); GreedyMesh::<guillotiere::SimpleAtlasAllocator>::new(max_size, greedy::general_config());
@ -407,7 +412,7 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
get_glow, get_glow,
get_opacity, get_opacity,
should_draw, should_draw,
push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta { push_quad: #[inline(always)] |atlas_origin, dim, origin, draw_dim, norm, meta: &FaceKind| match meta {
FaceKind::Opaque(meta) => { FaceKind::Opaque(meta) => {
opaque_mesh.push_quad(greedy::create_quad( opaque_mesh.push_quad(greedy::create_quad(
atlas_origin, atlas_origin,
@ -431,7 +436,7 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
)); ));
}, },
}, },
make_face_texel: |data: &mut (), pos, light, glow, ao| { make_face_texel: #[inline(always)] |data: &mut (), pos, light, glow, ao| {
TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao) TerrainVertex::make_col_light(light, glow, get_color(data, pos), ao)
}, },
}); });
@ -458,6 +463,7 @@ pub fn generate_mesh<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug + '
/// NOTE: Make sure to reflect any changes to how meshing is performanced in /// NOTE: Make sure to reflect any changes to how meshing is performanced in
/// [scene::terrain::Terrain::skip_remesh]. /// [scene::terrain::Terrain::skip_remesh].
#[inline(always)]
fn should_draw_greedy( fn should_draw_greedy(
pos: Vec3<i32>, pos: Vec3<i32>,
delta: Vec3<i32>, delta: Vec3<i32>,
@ -470,7 +476,7 @@ fn should_draw_greedy(
if from_filled == to.is_filled() { if from_filled == to.is_filled() {
// Check the interface of liquid and non-tangible non-liquid (e.g. air). // Check the interface of liquid and non-tangible non-liquid (e.g. air).
let from_liquid = from.is_liquid(); let from_liquid = from.is_liquid();
if from_liquid == to.is_liquid() || from.is_filled() || to.is_filled() { if from_liquid == to.is_liquid() || /*from.is_filled() || to.is_filled()*/from_filled {
None None
} else { } else {
// While liquid is not culled, we still try to keep a consistent orientation as // While liquid is not culled, we still try to keep a consistent orientation as

View File

@ -237,7 +237,14 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
sprite_config: &SpriteSpec, sprite_config: &SpriteSpec,
) -> MeshWorkerResponse { ) -> MeshWorkerResponse {
span!(_guard, "mesh_worker"); span!(_guard, "mesh_worker");
let blocks_of_interest = BlocksOfInterest::from_chunk(&chunk); let (blocks_of_interest, sprite_kinds) = BlocksOfInterest::from_chunk(&chunk)/*default()*/;
let mut range = range;
/* range.min += Vec3::from(V::RECT_SIZE.as_::<i32>()) - 1;
range.max -= Vec3::from(V::RECT_SIZE.as_::<i32>()) - 1; */
range.min.z = chunk.get_min_z() - 2;
range.max.z = chunk.get_max_z() + 2;
let z_bounds = (range.min.z, range.max.z);
let mesh; let mesh;
let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh { let (light_map, glow_map) = if let Some((light_map, glow_map)) = &skip_remesh {
@ -274,13 +281,14 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
prof_span!("extract sprite_instances"); prof_span!("extract sprite_instances");
let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| Vec::new()); let mut instances = [(); SPRITE_LOD_LEVELS].map(|()| Vec::new());
for x in 0..V::RECT_SIZE.x as i32 { for (rel_pos, (sprite, ori)) in /*chunk.iter_changed()*/sprite_kinds {
/* for x in 0..V::RECT_SIZE.x as i32 {
for y in 0..V::RECT_SIZE.y as i32 { for y in 0..V::RECT_SIZE.y as i32 {
for z in z_bounds.0 as i32..z_bounds.1 as i32 + 1 { for z in z_bounds.0 as i32..z_bounds.1 as i32 + 1 {*/
let rel_pos = Vec3::new(x, y, z); /* let rel_pos = Vec3::new(x, y, z); */
let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos; let wpos = Vec3::from(pos * V::RECT_SIZE.map(|e: u32| e as i32)) + rel_pos;
let block = if let Ok(block) = volume.get(wpos) { /* let block = if let Ok(block) = volume.get(wpos) {
block block
} else { } else {
continue; continue;
@ -289,13 +297,13 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
sprite sprite
} else { } else {
continue; continue;
}; }; */
if let Some(cfg) = sprite_config.get(sprite) { if let Some(cfg) = sprite_config.get(sprite) {
let seed = wpos.x as u64 * 3 let seed = wpos.x as u64 * 3
+ wpos.y as u64 * 7 + wpos.y as u64 * 7
+ wpos.x as u64 * wpos.y as u64; // Awful PRNG + wpos.x as u64 * wpos.y as u64; // Awful PRNG
let ori = (block.get_ori().unwrap_or((seed % 4) as u8 * 2)) & 0b111; let ori = (ori.unwrap_or((seed % 4) as u8 * 2)) & 0b111;
let variation = seed as usize % cfg.variations.len(); let variation = seed as usize % cfg.variations.len();
let key = (sprite, variation); let key = (sprite, variation);
// NOTE: Safe because we called sprite_config_for already. // NOTE: Safe because we called sprite_config_for already.
@ -334,8 +342,9 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug + '
} }
} }
} }
} /* }
} }
} */
} }
instances instances

View File

@ -1,8 +1,7 @@
use crate::hud::CraftingTab; use crate::hud::CraftingTab;
use common::terrain::{BlockKind, SpriteKind, TerrainChunk}; use common::terrain::{BlockKind, SpriteKind, TerrainChunk, TERRAIN_CHUNK_BLOCKS_LG};
use common_base::span; use common_base::span;
use rand::prelude::*; use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
use vek::*; use vek::*;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -56,7 +55,7 @@ pub struct BlocksOfInterest {
} }
impl BlocksOfInterest { impl BlocksOfInterest {
pub fn from_chunk(chunk: &TerrainChunk) -> Self { pub fn from_chunk(chunk: &TerrainChunk) -> (Self, Vec<(Vec3<i32>, (SpriteKind, Option<u8>))>) {
span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk"); span!(_guard, "from_chunk", "BlocksOfInterest::from_chunk");
let mut leaves = Vec::new(); let mut leaves = Vec::new();
let mut drip = Vec::new(); let mut drip = Vec::new();
@ -80,100 +79,191 @@ impl BlocksOfInterest {
let mut cricket2 = Vec::new(); let mut cricket2 = Vec::new();
let mut cricket3 = Vec::new(); let mut cricket3 = Vec::new();
let mut frogs = Vec::new(); let mut frogs = Vec::new();
let mut sprite_kinds = Vec::new();
let mut rng = ChaCha8Rng::from_seed(thread_rng().gen()); let mut rng = SmallRng::from_seed(thread_rng().gen());
let river_speed_sq = chunk.meta().river_velocity().magnitude_squared(); let river_speed_sq = chunk.meta().river_velocity().magnitude_squared();
let z_offset = chunk.get_min_z();
chunk.iter_changed().for_each(|(pos, block)| { const LEAF_BITS: u32 = 4;
match block.kind() { const DRIP_BITS: u32 = 3;
BlockKind::Leaves if rng.gen_range(0..16) == 0 => leaves.push(pos), const SNOW_BITS: u32 = 4;
BlockKind::WeakRock if rng.gen_range(0..6) == 0 => drip.push(pos), const LAVA_BITS: u32 = 2;
BlockKind::Grass => { const GRASS_BITS: u32 = 4;
if rng.gen_range(0..16) == 0 { const CRICKET_BITS: u32 = 13;
grass.push(pos); const FROG_BITS: u32 = 4;
}
match rng.gen_range(0..8192) { const LEAF_MASK: u64 = (1 << LEAF_BITS) - 1;
1 => cricket1.push(pos), const DRIP_MASK: u64 = (1 << DRIP_BITS) - 1;
2 => cricket2.push(pos), const SNOW_MASK: u64 = (1 << SNOW_BITS) - 1;
3 => cricket3.push(pos), const LAVA_MASK: u64 = (1 << LAVA_BITS) - 1;
_ => {}, // NOTE: Grass and cricket bits are merged together to save a call to the rng.
} const CRICKET_MASK: u64 = ((1 << CRICKET_BITS) - 1);
}, const GRASS_MASK: u64 = ((1 << GRASS_BITS) - 1) << CRICKET_BITS;
// Assign a river speed to water blocks depending on river velocity const FROG_MASK: u64 = (1 << FROG_BITS) - 1;
BlockKind::Water if river_speed_sq > 0.9_f32.powi(2) => fast_river.push(pos),
BlockKind::Water if river_speed_sq > 0.3_f32.powi(2) => slow_river.push(pos), // NOTE: Z chunk total height cannot exceed 2^14, so x+y+z fits in 24 bits. Therefore we
BlockKind::Snow if rng.gen_range(0..16) == 0 => snow.push(pos), // know -1 is never a valid height, so it's okay to use as a representative of "no river".
BlockKind::Lava if rng.gen_range(0..5) == 0 => fires.push(pos + Vec3::unit_z()), let mut river_data = [-1i16; (1 << TERRAIN_CHUNK_BLOCKS_LG * 2)];
BlockKind::Snow | BlockKind::Ice if rng.gen_range(0..16) == 0 => snow.push(pos),
_ => match block.get_sprite() { let river = if river_speed_sq > 0.9_f32.powi(2) {
Some(SpriteKind::Ember) => { Some(&mut fast_river)
} else if river_speed_sq > 0.3_f32.powi(2) {
Some(&mut slow_river)
} else {
None
};
chunk.iter_changed().for_each(|(index, block)| {
// FIXME: Before merge, make this properly generic.
#[inline(always)]
fn make_pos_raw(index: u32) -> Vec3<i32> {
let grp = index >> 6;
let rel = index & 63;
let x = ((grp & 7) << 2) | (rel & 3);
let y = (((grp >> 3) & 7) << 2) | ((rel >> 2) & 3);
let z = ((grp >> 6) << 2) | (rel >> 4);
Vec3::new(x as i32, y as i32, z as i32)
};
let make_pos = #[inline(always)] |index: u32| {
let mut pos = make_pos_raw(index);
pos.z += z_offset;
pos
};
let mut do_sprite = |sprite: SpriteKind, rng: &mut SmallRng| {
let pos = make_pos(index);
match sprite {
SpriteKind::Ember => {
fires.push(pos); fires.push(pos);
smokers.push(SmokerProperties::new(pos, FireplaceType::House)); smokers.push(SmokerProperties::new(pos, FireplaceType::House));
}, },
// Offset positions to account for block height. // Offset positions to account for block height.
// TODO: Is this a good idea? // TODO: Is this a good idea?
Some(SpriteKind::StreetLamp) => fire_bowls.push(pos + Vec3::unit_z() * 2), SpriteKind::StreetLamp => fire_bowls.push(pos + Vec3::unit_z() * 2),
Some(SpriteKind::FireBowlGround) => fire_bowls.push(pos + Vec3::unit_z()), SpriteKind::FireBowlGround => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::StreetLampTall) => fire_bowls.push(pos + Vec3::unit_z() * 4), SpriteKind::StreetLampTall => fire_bowls.push(pos + Vec3::unit_z() * 4),
Some(SpriteKind::WallSconce) => fire_bowls.push(pos + Vec3::unit_z()), SpriteKind::WallSconce => fire_bowls.push(pos + Vec3::unit_z()),
Some(SpriteKind::Beehive) => beehives.push(pos), SpriteKind::Beehive => beehives.push(pos),
Some(SpriteKind::CrystalHigh) => fireflies.push(pos), SpriteKind::CrystalHigh => fireflies.push(pos),
Some(SpriteKind::Reed) => { SpriteKind::Reed => {
reeds.push(pos); reeds.push(pos);
fireflies.push(pos); fireflies.push(pos);
if rng.gen_range(0..12) == 0 { if rng.next_u64() & FROG_MASK == 0 {
frogs.push(pos); frogs.push(pos);
} }
}, },
Some(SpriteKind::CaveMushroom) => fireflies.push(pos), SpriteKind::CaveMushroom => fireflies.push(pos),
Some(SpriteKind::PinkFlower) => flowers.push(pos), SpriteKind::PinkFlower => flowers.push(pos),
Some(SpriteKind::PurpleFlower) => flowers.push(pos), SpriteKind::PurpleFlower => flowers.push(pos),
Some(SpriteKind::RedFlower) => flowers.push(pos), SpriteKind::RedFlower => flowers.push(pos),
Some(SpriteKind::WhiteFlower) => flowers.push(pos), SpriteKind::WhiteFlower => flowers.push(pos),
Some(SpriteKind::YellowFlower) => flowers.push(pos), SpriteKind::YellowFlower => flowers.push(pos),
Some(SpriteKind::Sunflower) => flowers.push(pos), SpriteKind::Sunflower => flowers.push(pos),
Some(SpriteKind::CraftingBench) => { SpriteKind::CraftingBench => {
interactables.push((pos, Interaction::Craft(CraftingTab::All))) interactables.push((pos, Interaction::Craft(CraftingTab::All)))
}, },
Some(SpriteKind::SmokeDummy) => { SpriteKind::SmokeDummy => {
smokers.push(SmokerProperties::new(pos, FireplaceType::Workshop)); smokers.push(SmokerProperties::new(pos, FireplaceType::Workshop));
}, },
Some(SpriteKind::Forge) => interactables SpriteKind::Forge => interactables
.push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
Some(SpriteKind::TanningRack) => interactables SpriteKind::TanningRack => interactables
.push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))), .push((pos, Interaction::Craft(CraftingTab::ProcessedMaterial))),
Some(SpriteKind::SpinningWheel) => { SpriteKind::SpinningWheel => {
interactables.push((pos, Interaction::Craft(CraftingTab::All))) interactables.push((pos, Interaction::Craft(CraftingTab::All)))
}, },
Some(SpriteKind::Loom) => { SpriteKind::Loom => {
interactables.push((pos, Interaction::Craft(CraftingTab::All))) interactables.push((pos, Interaction::Craft(CraftingTab::All)))
}, },
Some(SpriteKind::Cauldron) => { SpriteKind::Cauldron => {
fires.push(pos); fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Potion))) interactables.push((pos, Interaction::Craft(CraftingTab::Potion)))
}, },
Some(SpriteKind::Anvil) => { SpriteKind::Anvil => {
interactables.push((pos, Interaction::Craft(CraftingTab::Weapon))) interactables.push((pos, Interaction::Craft(CraftingTab::Weapon)))
}, },
Some(SpriteKind::CookingPot) => { SpriteKind::CookingPot => {
fires.push(pos); fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Food))) interactables.push((pos, Interaction::Craft(CraftingTab::Food)))
}, },
Some(SpriteKind::DismantlingBench) => { SpriteKind::DismantlingBench => {
fires.push(pos); fires.push(pos);
interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle))) interactables.push((pos, Interaction::Craft(CraftingTab::Dismantle)))
}, },
_ => {}, _ => {},
}
sprite_kinds.push((pos, (sprite, sprite.has_ori().then(|| block.get_ori_raw()))));
if sprite.is_collectible() {
interactables.push((pos, Interaction::Collect));
}
sprite.get_glow()
};
let mut has_sprite = false;
let glow = match block.kind() {
kind @ BlockKind::Leaves => {
if rng.next_u64() & LEAF_MASK == 0 { leaves.push(make_pos(index)); }
kind.get_glow_raw()
}, },
} kind @ BlockKind::WeakRock => {
if block.is_collectible() { if rng.next_u64() & DRIP_MASK == 0 { drip.push(make_pos(index)); }
interactables.push((pos, Interaction::Collect)); kind.get_glow_raw()
} },
if let Some(glow) = block.get_glow() { kind @ BlockKind::Grass => {
let bits = rng.next_u64();
if bits & GRASS_MASK == 0 {
grass.push(make_pos(index));
}
match bits & CRICKET_MASK {
1 => cricket1.push(make_pos(index)),
2 => cricket2.push(make_pos(index)),
3 => cricket3.push(make_pos(index)),
_ => {},
}
kind.get_glow_raw()
},
// Assign a river speed to water blocks depending on river velocity
kind @ BlockKind::Water => {
if river.is_some() {
// Remember only the top river blocks. Since we always go from low to high z, this assignment
// can be unconditional.
let mut pos = make_pos_raw(index);
river_data[((pos.y << TERRAIN_CHUNK_BLOCKS_LG) | pos.x) as usize] = pos.z as i16;
}
if let Some(sprite) = block.get_sprite_raw() {
has_sprite = true;
do_sprite(sprite, &mut rng)
} else {
kind.get_glow_raw()
}
}
kind @ BlockKind::Lava => {
if rng.next_u64() & LAVA_MASK == 0 { fires.push(make_pos(index) + Vec3::unit_z()) }
kind.get_glow_raw()
},
kind @ BlockKind::Snow | kind @ BlockKind::Ice => {
if rng.next_u64() & SNOW_MASK == 0 { snow.push(make_pos(index)); }
kind.get_glow_raw()
},
kind @ BlockKind::Air => if let Some(sprite) = block.get_sprite_raw() {
has_sprite = true;
do_sprite(sprite, &mut rng)
} else {
kind.get_glow_raw()
},
kind @ BlockKind::Rock | kind @ BlockKind::GlowingRock | kind @ BlockKind::GlowingWeakRock |
kind @ BlockKind::GlowingMushroom |
kind @ BlockKind::Earth | kind @ BlockKind::Sand | kind @ BlockKind::Wood | kind @ BlockKind::Misc => {
kind.get_glow_raw()
}
};
if let Some(glow) = glow {
let pos = make_pos(index);
// Currently, we count filled blocks as 'minor' lights, and sprites as // Currently, we count filled blocks as 'minor' lights, and sprites as
// non-minor. // non-minor.
if block.get_sprite().is_none() { if has_sprite {
minor_lights.push((pos, glow)); minor_lights.push((pos, glow));
} else { } else {
lights.push((pos, glow)); lights.push((pos, glow));
@ -181,6 +271,23 @@ impl BlocksOfInterest {
} }
}); });
// Convert river grid to vector.
const X_MASK: usize = (1 << TERRAIN_CHUNK_BLOCKS_LG) - 1;
if let Some(river) = river {
river.extend(
river_data.into_iter().enumerate()
// Avoid blocks with no water
.filter(|&(_, river_block)| river_block != -1)
.map(|(index, river_block)|
Vec3::new(
(index & X_MASK) as i32,
(index >> TERRAIN_CHUNK_BLOCKS_LG) as i32,
z_offset + i32::from(river_block),
))
);
}
// TODO: Come up with a better way to prune many light sources: grouping them // TODO: Come up with a better way to prune many light sources: grouping them
// into larger lights with k-means clustering, perhaps? // into larger lights with k-means clustering, perhaps?
const MAX_MINOR_LIGHTS: usize = 64; const MAX_MINOR_LIGHTS: usize = 64;
@ -190,7 +297,7 @@ impl BlocksOfInterest {
.copied(), .copied(),
); );
Self { (Self {
leaves, leaves,
drip, drip,
grass, grass,
@ -212,6 +319,6 @@ impl BlocksOfInterest {
lights, lights,
temperature: chunk.meta().temp(), temperature: chunk.meta().temp(),
humidity: chunk.meta().humidity(), humidity: chunk.meta().humidity(),
} }, sprite_kinds)
} }
} }

View File

@ -181,6 +181,7 @@ impl<'a> ZCache<'a> {
} }
} }
#[inline(always)]
pub fn block_from_structure( pub fn block_from_structure(
index: IndexRef, index: IndexRef,
sblock: StructureBlock, sblock: StructureBlock,

View File

@ -2,9 +2,9 @@ use crate::{
all::*, all::*,
block::block_from_structure, block::block_from_structure,
column::ColumnGen, column::ColumnGen,
site2::{self, PrimitiveTransform}, site2::{self, Fill, Filler, FillFn, Painter, PrimitiveTransform},
layer::cave::tunnel_bounds_at, layer::cave::tunnel_bounds_at,
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser}, util::{gen_cache::StructureGenCache, RandomField, RandomPerm, Sampler, UnitChooser},
Canvas, CanvasInfo, ColumnSample, Canvas, CanvasInfo, ColumnSample,
}; };
use common::{ use common::{
@ -323,122 +323,193 @@ pub fn apply_trees_to(
(s.get_bounds(), [(0.0004, SpriteKind::Beehive)].as_ref()) (s.get_bounds(), [(0.0004, SpriteKind::Beehive)].as_ref())
}, },
&TreeModel::Procedural(ref t, leaf_block) => { &TreeModel::Procedural(ref t, leaf_block) => {
#[inline(always)]
fn draw_tree<'a, F: Filler>(
t: &ProceduralTree,
leaf_block: impl Fill + Copy,
trunk_block: impl Fill + Copy,
wpos: Vec3<i32>,
painter: &Painter<'a>,
filler: &mut FillFn<'a, '_, F>
) {
let leaf_vertical_scale = t.config.leaf_vertical_scale.recip();
let branch_child_radius_lerp = t.config.branch_child_radius_lerp;
// NOTE: Technically block_from_structure isn't correct here, because it could
// lerp with position; in practice, it almost never does, and most of the other
// expensive parameters are unused.
/* let trunk_block = if let Some(block) = block_from_structure(
info.index(),
trunk_block,
wpos,
tree.pos.xy(),
tree.seed,
&col,
Block::air,
calendar,
) {
block
} else {
return;
};
let leaf_block = if let Some(block) = block_from_structure(
info.index(),
leaf_block,
wpos,
tree.pos.xy(),
tree.seed,
&col,
Block::air,
calendar,
) {
block
} else {
return;
}; */
t.walk(|branch, parent| {
let aabr = Aabr {
min: wpos.xy() + branch.get_aabb().min.xy().as_(),
max: wpos.xy() + branch.get_aabb().max.xy().as_(),
};
if aabr.collides_with_aabr(filler.render_aabr().as_()) {
let start =
wpos.as_::<f32>() + branch.get_line().start/*.as_()*//* - 0.5*/;
let end =
wpos.as_::<f32>() + branch.get_line().end/*.as_()*//* - 0.5*/;
let wood_radius = branch.get_wood_radius();
let leaf_radius = branch.get_leaf_radius();
let parent_wood_radius = if branch_child_radius_lerp {
parent.get_wood_radius()
} else {
wood_radius
};
let leaf_eats_wood = leaf_radius > wood_radius;
let leaf_eats_parent_wood = leaf_radius > parent_wood_radius;
if !leaf_eats_wood || !leaf_eats_parent_wood {
// Render the trunk, since it's not swallowed by its leaf.
painter
.line_two_radius(
start,
end,
parent_wood_radius,
wood_radius,
1.0,
)
.fill(/*filler.block(trunk_block)*/trunk_block, filler);
}
if leaf_eats_wood || leaf_eats_parent_wood {
// Render the leaf, since it's not *completely* swallowed
// by the trunk.
painter
.line_two_radius(
start,
end,
leaf_radius,
leaf_radius,
leaf_vertical_scale,
)
.fill(/*filler.block(leaf_block)*/leaf_block, filler);
}
true
} else {
false
}
});
// Draw the roots.
t.roots.iter().for_each(|root| {
painter
.line(
wpos/*.as_::<f32>()*/ + root.line.start.as_()/* - 0.5*/,
wpos/*.as_::<f32>()*/ + root.line.end.as_()/* - 0.5*/,
root.radius,
)
.fill(/*filler.block(leaf_block)*/trunk_block, filler);
});
}
let bounds = t.get_bounds().map(|e| e as i32); let bounds = t.get_bounds().map(|e| e as i32);
let trunk_block = t.config.trunk_block; let trunk_block = t.config.trunk_block;
let leaf_vertical_scale = t.config.leaf_vertical_scale.recip();
let branch_child_radius_lerp = t.config.branch_child_radius_lerp;
// NOTE: Technically block_from_structure isn't correct here, because it could
// lerp with position; in practice, it almost never does, and most of the other
// expensive parameters are unused.
/* let trunk_block = if let Some(block) = block_from_structure(
info.index(),
trunk_block,
wpos,
tree.pos.xy(),
tree.seed,
&col,
Block::air,
calendar,
) {
block
} else {
return;
};
let leaf_block = if let Some(block) = block_from_structure(
info.index(),
leaf_block,
wpos,
tree.pos.xy(),
tree.seed,
&col,
Block::air,
calendar,
) {
block
} else {
return;
}; */
site2::render_collect( site2::render_collect(
&arena, &arena,
info, info,
render_area, render_area,
canvas, canvas,
|painter, filler| { |painter, filler| {
let trunk_block = filler.block_from_structure( let leaf_block = /* filler.block_from_structure(
trunk_block,
tree.pos.xy(),
tree.seed,
&col,
);
let leaf_block = filler.block_from_structure(
leaf_block, leaf_block,
tree.pos.xy(), tree.pos.xy(),
tree.seed, tree.seed,
&col, &col,
); ) */{
t.walk(|branch, parent| { let structure_pos = tree.pos.xy();
let aabr = Aabr { let structure_seed = tree.seed;
min: wpos.xy() + branch.get_aabb().min.xy().as_(), let field = RandomField::new(structure_seed);
max: wpos.xy() + branch.get_aabb().max.xy().as_(),
}; let lerp = ((field.get(Vec3::from(structure_pos)).rem_euclid(256)) as f32 / 255.0) * 0.8;
if aabr.collides_with_aabr(filler.render_aabr().as_()) {
let start = let ranges = leaf_block
wpos.as_::<f32>() + branch.get_line().start/*.as_()*//* - 0.5*/; .elim_case_pure(&info.index().colors.block.structure_blocks)
let end = .as_ref()
wpos.as_::<f32>() + branch.get_line().end/*.as_()*//* - 0.5*/; .map(Vec::as_slice)
let wood_radius = branch.get_wood_radius(); .unwrap_or(&[]);
let leaf_radius = branch.get_leaf_radius(); let range = if ranges.is_empty() {
let parent_wood_radius = if branch_child_radius_lerp { // Error occurred, but this ideally shouldn't happen.
parent.get_wood_radius() return;
} else {
wood_radius
};
let leaf_eats_wood = leaf_radius > wood_radius;
let leaf_eats_parent_wood = leaf_radius > parent_wood_radius;
if !leaf_eats_wood || !leaf_eats_parent_wood {
// Render the trunk, since it's not swallowed by its leaf.
painter
.line_two_radius(
start,
end,
parent_wood_radius,
wood_radius,
1.0,
)
.fill(/*filler.block(trunk_block)*/trunk_block, filler);
}
if leaf_eats_wood || leaf_eats_parent_wood {
// Render the leaf, since it's not *completely* swallowed
// by the trunk.
painter
.line_two_radius(
start,
end,
leaf_radius,
leaf_radius,
leaf_vertical_scale,
)
.fill(/*filler.block(leaf_block)*/leaf_block, filler);
}
true
} else { } else {
false &ranges[
} RandomPerm::new(structure_seed).get(structure_seed) as usize % ranges.len()
}); ]
// Draw the roots. };
t.roots.iter().for_each(|root| { let start = Rgb::<u8>::from(range.start).map(f32::from);
painter let end = Rgb::<u8>::from(range.end).map(f32::from);
.line( let is_christmas = calendar.map_or(false, |c| c.is_event(CalendarEvent::Christmas));
wpos/*.as_::<f32>()*/ + root.line.start.as_()/* - 0.5*/, filler.sampling(move |pos| {
wpos/*.as_::<f32>()*/ + root.line.end.as_()/* - 0.5*/, Some(
root.radius, if is_christmas && field.chance(pos + structure_pos, 0.025)
{
Block::new(BlockKind::GlowingWeakRock, Rgb::new(255, 0, 0))
} else {
let lerp = lerp
+ ((field.get(pos + i32::MAX / 2).rem_euclid(256)) as f32 / 255.0) * 0.2;
Block::new(
BlockKind::Leaves,
Rgb::<f32>::lerp(start, end, lerp).as_::<u8>(),
)
}
) )
.fill(/*filler.block(leaf_block)*/trunk_block, filler); })
}); };
}, /* let trunk_block = /* filler.block_from_structure(
); trunk_block,
tree.pos.xy(),
tree.seed,
&col,
); */*/match trunk_block {
StructureBlock::Filled(kind, color) => {
let trunk_block = filler.block(Block::new(kind, color));
draw_tree(t, leaf_block, trunk_block, wpos, painter, filler);
/* filler.block(Block::new(kind, color)) */
},
StructureBlock::BirchWood => {
let structure_pos = tree.pos.xy();
let field = RandomField::new(tree.seed);
let trunk_block = filler.sampling(move |pos| {
let wpos = pos + structure_pos;
if field.chance(
(wpos + Vec3::new(wpos.z, wpos.z, 0) / 2)
/ Vec3::new(1 + wpos.z % 2, 1 + (wpos.z + 1) % 2, 1),
0.25,
) && wpos.z % 2 == 0
{
Some(Block::new(BlockKind::Wood, Rgb::new(70, 35, 25)))
} else {
Some(Block::new(BlockKind::Wood, Rgb::new(220, 170, 160)))
}
});
draw_tree(t, leaf_block, trunk_block, wpos, painter, filler);
},
_ => unimplemented!("Only birch and filled trunk blocks are currently supported."),
}
});
(bounds, t.config.hanging_sprites) (bounds, t.config.hanging_sprites)
}, },
}; };

View File

@ -156,7 +156,7 @@ pub fn sample_pos(
} else { } else {
Lerp::lerp( Lerp::lerp(
sample.sub_surface_color, sample.sub_surface_color,
sample.surface_color, if sample.snow_cover { Rgb::new(210.0, 210.0, 255.0) / 255.0 } else { sample.surface_color },
((wposz as f32 - (alt - grass_depth)) / grass_depth).sqrt(), ((wposz as f32 - (alt - grass_depth)) / grass_depth).sqrt(),
) )
.map(|e| e as f64) .map(|e| e as f64)

View File

@ -660,6 +660,7 @@ impl<'a, 'b, F: Filler> FillFn<'a, 'b, F> {
}) })
} }
#[inline(always)]
pub fn block_from_structure(&self, sb: StructureBlock, structure_pos: Vec2<i32>, seed: u32, col_sample: &'b ColumnSample) -> impl Fill + Copy + 'b pub fn block_from_structure(&self, sb: StructureBlock, structure_pos: Vec2<i32>, seed: u32, col_sample: &'b ColumnSample) -> impl Fill + Copy + 'b
{ {
/* let col_sample = /*if let Some(col_sample) = */self.canvas_info.col(self.canvas_info.wpos)/* { /* let col_sample = /*if let Some(col_sample) = */self.canvas_info.col(self.canvas_info.wpos)/* {
@ -2856,27 +2857,83 @@ impl Painter<'_> {
// TODO: Optimize further? // TODO: Optimize further?
let aabb = Self::get_bounds(cache, tree, prim); let aabb = Self::get_bounds(cache, tree, prim);
/*if !(aabb.size().w > 8 || aabb.size().h > 8 || aabb.size().d > 16) */{ /*if !(aabb.size().w > 8 || aabb.size().h > 8 || aabb.size().d > 16) */{
let mut do_segment = || {
let distance = segment.end - segment.start; let distance = segment.end - segment.start;
let distance_proj = distance / distance.magnitude_squared(); let distance_proj = distance / distance.magnitude_squared();
let segment_start = segment.start - 0.5; let segment_start = segment.start - 0.5;
return aabb_iter( if r0 == r1 {
aabb.as_(), let radius_2 = r0 * r0;
mat, if z_scale == 1.0 {
mask, return aabb_iter(
// |_| true, aabb.as_(),
|pos| { mat,
let pos = pos.as_::<f32>(); mask,
let length = pos - segment_start; // |_| true,
let t = length.dot(distance_proj).clamped(0.0, 1.0); |pos| {
let mut diff = distance * t - length; let pos = pos.as_::<f32>();
diff.z *= z_scale; let length = pos - segment_start;
let radius = Lerp::lerp_unclamped(r0, r1, t); let t = length.dot(distance_proj).clamped(0.0, 1.0);
diff.magnitude_squared() < radius * radius let diff = distance * t - length;
}, diff.magnitude_squared() < radius_2
hit, },
); hit,
return );
} else {
return aabb_iter(
aabb.as_(),
mat,
mask,
// |_| true,
|pos| {
let pos = pos.as_::<f32>();
let length = pos - segment_start;
let t = length.dot(distance_proj).clamped(0.0, 1.0);
let mut diff = distance * t - length;
diff.z *= z_scale;
diff.magnitude_squared() < radius_2
},
hit,
);
}
} else if z_scale == 1.0 {
return aabb_iter(
aabb.as_(),
mat,
mask,
// |_| true,
|pos| {
let pos = pos.as_::<f32>();
let length = pos - segment_start;
let t = length.dot(distance_proj).clamped(0.0, 1.0);
let diff = distance * t - length;
let radius = Lerp::lerp_unclamped(r0, r1, t);
diff.magnitude_squared() < radius * radius
},
hit,
);
} else {
return aabb_iter(
aabb.as_(),
mat,
mask,
// |_| true,
|pos| {
let pos = pos.as_::<f32>();
let length = pos - segment_start;
let t = length.dot(distance_proj).clamped(0.0, 1.0);
let mut diff = distance * t - length;
diff.z *= z_scale;
let radius = Lerp::lerp_unclamped(r0, r1, t);
diff.magnitude_squared() < radius * radius
},
hit,
);
}
};
// NOTE: This hurts gnarling fortresses by about 1.5% but helps cliff towns by
// about 3%, compared to no closure. We deem the latter more important.
do_segment();
return;
} }
// NOTE: vol(frustum) = 1/3 h(A(r₀) + A(r₁) + √(A(r₀)A(r₁))) // NOTE: vol(frustum) = 1/3 h(A(r₀) + A(r₁) + √(A(r₀)A(r₁)))
// = 1/3 h (r₀² + r₁² + √(r₀²r₁²)) // = 1/3 h (r₀² + r₁² + √(r₀²r₁²))