mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Far terrain: shaded water and canopies, swamp grass, no floor pillars or water walls
- Skylight goes straight down generated columns: water and leaves take a level a block, one span per level as a real chunk's light has them. Full light to the bottom lit sea beds as if in the open, and coarse cells merging water and sand showed that light as white dots across the sea. Solid blocks pass it on: under them far terrain shows cliffs and overhangs, which vanilla lights sideways (they went black). - Kelp and seagrass in saved and generated chunks are the water they hold (loaded chunks: #4144). - Grass takes vanilla's colour at each cell (BiomeColors' grass with its swamp and dark forest modifiers), not the biome's flat one. - Buried gaps are filled only where no open air reaches them through neighbouring gaps (a flood fill): the air under a forest canopy was filled with the floor block, pillars up to the leaves. - The seal far tiles put where near terrain begins is opaque only: near water carries on into far water, and the seal showed through it as a light wall. - A section beside a chunk not loaded meshes against its own edge column, not lit air: the outermost chunks' water drew a light sheet toward it (fluids show both sides). Seed 1234, LOD Distance 512: 9.27M far quads become 11.22M, the trunks and forest floor under canopies. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
fb9db82d1b
commit
ee7d443c8f
4 files changed
+298
-79
No files matched your search
@@ -74,10 +74,18 @@ impl Mesher {
|
||||
covered_bounds,
|
||||
neighbors,
|
||||
finer,
|
||||
|span, direction| self.material(span, direction, context),
|
||||
|span, direction, at| self.material(span, direction, at, context),
|
||||
)
|
||||
}
|
||||
fn material(&mut self, span: &Span, direction: Direction, context: &WorkerContext) -> Material {
|
||||
/// `at`: the block column the face covers, for the grass colours that
|
||||
/// change across a biome (swamps).
|
||||
fn material(
|
||||
&mut self,
|
||||
span: &Span,
|
||||
direction: Direction,
|
||||
at: [i32; 2],
|
||||
context: &WorkerContext,
|
||||
) -> Material {
|
||||
let styles = *self
|
||||
.styles
|
||||
.entry((span.state, direction as u8))
|
||||
@@ -179,7 +187,11 @@ impl Mesher {
|
||||
face.map(|face| {
|
||||
let rgb = match face.tint {
|
||||
Tint::None => 0xffffff,
|
||||
Tint::Grass => biome.grass,
|
||||
Tint::Grass => {
|
||||
context
|
||||
.biomes
|
||||
.grass(span.biome, f64::from(at[0]), f64::from(at[1]))
|
||||
}
|
||||
Tint::Foliage => biome.foliage,
|
||||
Tint::Dry => biome.dry_foliage,
|
||||
Tint::Water => biome.water,
|
||||
@@ -256,7 +268,7 @@ fn mesh_columns(
|
||||
columns: &[Column],
|
||||
covered: &FxHashSet<(i32, i32)>,
|
||||
covered_bounds: Option<[i32; 4]>,
|
||||
material: impl FnMut(&Span, Direction) -> Material,
|
||||
material: impl FnMut(&Span, Direction, [i32; 2]) -> Material,
|
||||
) -> SectionMesh {
|
||||
mesh_columns_with_neighbors(key, columns, covered, covered_bounds, &[], material)
|
||||
}
|
||||
@@ -267,7 +279,7 @@ fn mesh_columns_with_neighbors(
|
||||
covered: &FxHashSet<(i32, i32)>,
|
||||
covered_bounds: Option<[i32; 4]>,
|
||||
neighbors: &[Column],
|
||||
material: impl FnMut(&Span, Direction) -> Material,
|
||||
material: impl FnMut(&Span, Direction, [i32; 2]) -> Material,
|
||||
) -> SectionMesh {
|
||||
assert!(neighbors.is_empty() || neighbors.len() == 4 * CELLS);
|
||||
let stride = key.size() / CELLS as i32;
|
||||
@@ -293,7 +305,7 @@ fn mesh_columns_with_edges(
|
||||
covered_bounds: Option<[i32; 4]>,
|
||||
neighbors: &[Vec<EdgeColumn>; 4],
|
||||
finer: &[TileKey],
|
||||
mut material: impl FnMut(&Span, Direction) -> Material,
|
||||
mut material: impl FnMut(&Span, Direction, [i32; 2]) -> Material,
|
||||
) -> SectionMesh {
|
||||
assert_eq!(columns.len(), CELLS * CELLS);
|
||||
let mut count = CELLS * CELLS;
|
||||
@@ -325,17 +337,31 @@ fn mesh_columns_with_edges(
|
||||
neighbors,
|
||||
stride,
|
||||
|x, z| cell_step(x, z) == stride,
|
||||
|span| material(span, Direction::Up).fluid != 0,
|
||||
|span| material(span, Direction::Up, [ox, oz]).fluid != 0,
|
||||
);
|
||||
// A cell's faces take the colours at its centre. The tile's neighbours'
|
||||
// columns only hide faces, so their colours never show.
|
||||
let centre = |i: usize| {
|
||||
if i < CELLS * CELLS {
|
||||
[
|
||||
ox + (i % CELLS) as i32 * stride + stride / 2,
|
||||
oz + (i / CELLS) as i32 * stride + stride / 2,
|
||||
]
|
||||
} else {
|
||||
[ox, oz]
|
||||
}
|
||||
};
|
||||
let prepared: Vec<Vec<Segment<'_>>> = filled
|
||||
.iter()
|
||||
.map(|column| &**column)
|
||||
.chain(neighbors.iter().flatten().map(|edge| &edge.column))
|
||||
.map(|column| {
|
||||
.enumerate()
|
||||
.map(|(index, column)| {
|
||||
let at = centre(index);
|
||||
let materials: Vec<_> = column
|
||||
.spans
|
||||
.iter()
|
||||
.map(|s| material(s, Direction::Up))
|
||||
.map(|s| material(s, Direction::Up, at))
|
||||
.collect();
|
||||
column
|
||||
.spans
|
||||
@@ -425,7 +451,7 @@ fn mesh_columns_with_edges(
|
||||
u1: x1,
|
||||
v1: z1,
|
||||
},
|
||||
material(segment.span, Direction::Down),
|
||||
material(segment.span, Direction::Down, centre(z * CELLS + x)),
|
||||
);
|
||||
}
|
||||
for (direction, nx, nz, edge, nw, plane, u0, u1) in [
|
||||
@@ -470,21 +496,25 @@ fn mesh_columns_with_edges(
|
||||
x1,
|
||||
),
|
||||
] {
|
||||
let side = material(segment.span, direction);
|
||||
let side = material(segment.span, direction, centre(z * CELLS + x));
|
||||
// Near meshes may have culled against the real neighbor.
|
||||
// Keep a finite seal where their full-detail area begins.
|
||||
// Keep a finite seal where their full-detail area begins:
|
||||
// opaque only, as the near water carries on into the far
|
||||
// water and a seal shows through it as a wall.
|
||||
if is_covered(nw[0], nw[1]) {
|
||||
add_side(
|
||||
&mut faces,
|
||||
direction,
|
||||
plane,
|
||||
u0,
|
||||
u1,
|
||||
segment,
|
||||
&[],
|
||||
side,
|
||||
own_sky,
|
||||
);
|
||||
if segment.material.opaque {
|
||||
add_side(
|
||||
&mut faces,
|
||||
direction,
|
||||
plane,
|
||||
u0,
|
||||
u1,
|
||||
segment,
|
||||
&[],
|
||||
side,
|
||||
own_sky,
|
||||
);
|
||||
}
|
||||
} else if !edge {
|
||||
add_side(
|
||||
&mut faces, direction, plane, u0, u1, segment, column, side,
|
||||
@@ -597,7 +627,7 @@ mod refinement_tests {
|
||||
None,
|
||||
&std::array::from_fn(|_| Vec::new()),
|
||||
&[fine],
|
||||
|_, _| Material {
|
||||
|_, _, _| Material {
|
||||
faces: [
|
||||
Some(Paint {
|
||||
layer: 1,
|
||||
@@ -646,13 +676,15 @@ mod refinement_tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// The tile's columns with the gaps no neighbour's open air reaches filled
|
||||
/// with the material below them, or the fluid above them (kelp and seagrass
|
||||
/// leave gaps in water): a cave seen from nowhere adds no faces. A
|
||||
/// gap is open when a neighbouring column's top lies below the gap's top, so
|
||||
/// that column's open air looks into it. Unknown neighbours, and cells that
|
||||
/// `whole` rejects (they meet full-detail or finer terrain), count as open;
|
||||
/// those cells keep their gaps.
|
||||
/// The tile's columns with the gaps no open air reaches filled with the
|
||||
/// material below them, or the fluid above them (kelp and seagrass leave gaps
|
||||
/// in water): a cave seen from nowhere adds no faces. A gap is open when a
|
||||
/// neighbouring column's top lies below the gap's top, so that column's open
|
||||
/// air looks into it, when a gap of the tile's neighbour meets it, or when it
|
||||
/// meets an open gap of a neighbouring column: the air under a canopy reaches
|
||||
/// the sky through any clearing, where filling it raised pillars of the floor
|
||||
/// block. Unknown neighbours, and cells that `whole` rejects (they meet
|
||||
/// full-detail or finer terrain), count as open; those cells keep their gaps.
|
||||
fn fill_buried_gaps<'a>(
|
||||
columns: &'a [Column],
|
||||
neighbors: &[Vec<EdgeColumn>; 4],
|
||||
@@ -669,6 +701,18 @@ fn fill_buried_gaps<'a>(
|
||||
.find(|span| span.min < span.max)
|
||||
.map_or(i16::MIN, |span| span.max)
|
||||
};
|
||||
// Each column's gaps: the index of the span above, the gap's bottom and top.
|
||||
let gaps_of = |column: &Column| -> Vec<(usize, i16, i16)> {
|
||||
column
|
||||
.spans
|
||||
.windows(2)
|
||||
.enumerate()
|
||||
.filter(|(_, pair)| pair[0].max < pair[1].min)
|
||||
.map(|(k, pair)| (k + 1, pair[0].max, pair[1].min))
|
||||
.collect()
|
||||
};
|
||||
let meets =
|
||||
|(lo, hi): (i16, i16), (other_lo, other_hi): (i16, i16)| lo < other_hi && other_lo < hi;
|
||||
let tops: Vec<_> = (0..CELLS * CELLS)
|
||||
.map(|i| {
|
||||
if whole(i % CELLS, i / CELLS) {
|
||||
@@ -678,57 +722,110 @@ fn fill_buried_gaps<'a>(
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let edge = |side: usize, cell: usize| {
|
||||
let gaps: Vec<_> = (0..CELLS * CELLS)
|
||||
.map(|i| {
|
||||
if whole(i % CELLS, i / CELLS) {
|
||||
gaps_of(&columns[i])
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let touching = |side: usize, cell: usize| {
|
||||
let (start, end) = (cell as i32 * stride, (cell as i32 + 1) * stride);
|
||||
neighbors[side]
|
||||
.iter()
|
||||
.filter(|edge| edge.start < end && start < edge.end)
|
||||
.filter(move |edge| edge.start < end && start < edge.end)
|
||||
};
|
||||
let edge = |side: usize, cell: usize| {
|
||||
touching(side, cell)
|
||||
.map(|edge| top(&edge.column))
|
||||
.min()
|
||||
.unwrap_or(i16::MIN)
|
||||
};
|
||||
// A gap of the tile's neighbour may be open through tiles this one can't
|
||||
// see: it counts as open.
|
||||
let edge_gap = |side: usize, cell: usize, gap: (i16, i16)| {
|
||||
touching(side, cell).any(|edge| {
|
||||
gaps_of(&edge.column)
|
||||
.iter()
|
||||
.any(|&(_, lo, hi)| meets((lo, hi), gap))
|
||||
})
|
||||
};
|
||||
let around = |i: usize| {
|
||||
let (x, z) = (i % CELLS, i / CELLS);
|
||||
[
|
||||
(x > 0).then(|| i - 1),
|
||||
(x + 1 < CELLS).then(|| i + 1),
|
||||
(z > 0).then(|| i - CELLS),
|
||||
(z + 1 < CELLS).then(|| i + CELLS),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
};
|
||||
let mut open: Vec<Vec<bool>> = gaps.iter().map(|g| vec![false; g.len()]).collect();
|
||||
let mut reached = Vec::new();
|
||||
for i in 0..CELLS * CELLS {
|
||||
let (x, z) = (i % CELLS, i / CELLS);
|
||||
let lowest = [
|
||||
if x > 0 { tops[i - 1] } else { edge(0, z) },
|
||||
if x + 1 < CELLS {
|
||||
tops[i + 1]
|
||||
} else {
|
||||
edge(1, z)
|
||||
},
|
||||
if z > 0 { tops[i - CELLS] } else { edge(2, x) },
|
||||
if z + 1 < CELLS {
|
||||
tops[i + CELLS]
|
||||
} else {
|
||||
edge(3, x)
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.min()
|
||||
.unwrap_or(i16::MIN);
|
||||
for (g, &(_, lo, hi)) in gaps[i].iter().enumerate() {
|
||||
let gap = (lo, hi);
|
||||
if hi > lowest
|
||||
|| (x == 0 && edge_gap(0, z, gap))
|
||||
|| (x + 1 == CELLS && edge_gap(1, z, gap))
|
||||
|| (z == 0 && edge_gap(2, x, gap))
|
||||
|| (z + 1 == CELLS && edge_gap(3, x, gap))
|
||||
{
|
||||
open[i][g] = true;
|
||||
reached.push((i, g));
|
||||
}
|
||||
}
|
||||
}
|
||||
while let Some((i, g)) = reached.pop() {
|
||||
let (_, lo, hi) = gaps[i][g];
|
||||
for j in around(i) {
|
||||
for (h, &(_, other_lo, other_hi)) in gaps[j].iter().enumerate() {
|
||||
if !open[j][h] && meets((lo, hi), (other_lo, other_hi)) {
|
||||
open[j][h] = true;
|
||||
reached.push((j, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
columns
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, column)| {
|
||||
let (x, z) = (i % CELLS, i / CELLS);
|
||||
if column.spans.len() < 2 || !whole(x, z) {
|
||||
return Cow::Borrowed(column);
|
||||
}
|
||||
let open = [
|
||||
if x > 0 { tops[i - 1] } else { edge(0, z) },
|
||||
if x + 1 < CELLS {
|
||||
tops[i + 1]
|
||||
} else {
|
||||
edge(1, z)
|
||||
},
|
||||
if z > 0 { tops[i - CELLS] } else { edge(2, x) },
|
||||
if z + 1 < CELLS {
|
||||
tops[i + CELLS]
|
||||
} else {
|
||||
edge(3, x)
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.min()
|
||||
.unwrap_or(i16::MIN);
|
||||
let buried = |below: &Span, above: &Span| below.max < above.min && above.min <= open;
|
||||
if !column
|
||||
.spans
|
||||
.windows(2)
|
||||
.any(|pair| buried(&pair[0], &pair[1]))
|
||||
{
|
||||
if open[i].iter().all(|&open| open) {
|
||||
return Cow::Borrowed(column);
|
||||
}
|
||||
let mut filled = Column {
|
||||
surface: column.surface,
|
||||
spans: Vec::with_capacity(column.spans.len()),
|
||||
};
|
||||
for &span in &column.spans {
|
||||
for (k, &span) in column.spans.iter().enumerate() {
|
||||
let mut span = span;
|
||||
if let Some(below) = filled.spans.last_mut()
|
||||
&& buried(below, &span)
|
||||
{
|
||||
let buried = gaps[i]
|
||||
.iter()
|
||||
.zip(&open[i])
|
||||
.any(|(&(above, _, _), &open)| above == k && !open);
|
||||
if buried && let Some(below) = filled.spans.last_mut() {
|
||||
if fluid(&span) {
|
||||
span.min = below.max;
|
||||
} else {
|
||||
@@ -919,7 +1016,7 @@ mod tests {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
fn material(span: &Span, _: Direction) -> Material {
|
||||
fn material(span: &Span, _: Direction, _: [i32; 2]) -> Material {
|
||||
let water = span.state.0 == 2;
|
||||
Material {
|
||||
faces: [
|
||||
@@ -1117,6 +1214,42 @@ mod tests {
|
||||
assert!(cave[0].iter().all(|v| v.light == 0));
|
||||
}
|
||||
#[test]
|
||||
fn air_under_a_canopy_stays_open_through_a_clearing() {
|
||||
let column = |spans: Vec<Span>| Column {
|
||||
surface: None,
|
||||
spans,
|
||||
};
|
||||
// The tile's neighbours are solid hills: no open air from the sides.
|
||||
let hill = || EdgeColumn {
|
||||
start: 0,
|
||||
end: CELLS as i32,
|
||||
column: column(vec![span(0, 20, 1)]),
|
||||
};
|
||||
let neighbors = [vec![hill()], vec![hill()], vec![hill()], vec![hill()]];
|
||||
let fill = |columns: &[Column]| -> Vec<Vec<Span>> {
|
||||
fill_buried_gaps(columns, &neighbors, 1, |_, _| true, |_| false)
|
||||
.iter()
|
||||
.map(|filled| filled.spans.clone())
|
||||
.collect()
|
||||
};
|
||||
let mut forest = vec![column(vec![span(0, 5, 1), span(10, 14, 3)]); CELLS * CELLS];
|
||||
// A closed canopy hides the floor: filling it under there shows nowhere.
|
||||
assert!(fill(&forest).iter().all(|spans| spans[0].max == 10));
|
||||
// One clearing lets the sky in: the air under every tree stays air,
|
||||
// not a pillar of the floor block up to the leaves.
|
||||
forest[5 * CELLS + 5] = column(vec![span(0, 5, 1)]);
|
||||
let filled = fill(&forest);
|
||||
for (filled, column) in filled.iter().zip(&forest) {
|
||||
assert_eq!(filled, &column.spans);
|
||||
}
|
||||
// A cave two columns wide sealed in rock is still filled.
|
||||
let mut rock = vec![column(vec![span(0, 20, 1)]); CELLS * CELLS];
|
||||
for cave in [5 * CELLS + 5, 5 * CELLS + 6] {
|
||||
rock[cave] = column(vec![span(0, 8, 1), span(12, 20, 1)]);
|
||||
}
|
||||
assert!(fill(&rock).iter().all(|spans| spans == &[span(0, 20, 1)]));
|
||||
}
|
||||
#[test]
|
||||
fn caves_no_neighbor_sees_into_add_no_faces() {
|
||||
let mut columns = columns();
|
||||
for column in &mut columns {
|
||||
|
||||
@@ -662,6 +662,15 @@ impl Padded {
|
||||
c.get(x & 15, wy, z & 15)
|
||||
};
|
||||
light[i] = c.light(x & 15, wy, z & 15);
|
||||
} else if let Some(c) = region[1][1] {
|
||||
// A neighbour not loaded continues this chunk's edge
|
||||
// column. As lit air, the outermost chunks' water drew
|
||||
// a wall toward it (fluids show both sides), a light
|
||||
// sheet in the water where far terrain carries on.
|
||||
// Vanilla draws no chunk without its neighbours.
|
||||
let (ex, ez) = (x.clamp(0, 15), z.clamp(0, 15));
|
||||
blocks[i] = c.get(ex, wy, ez);
|
||||
light[i] = c.light(ex, wy, ez);
|
||||
} else {
|
||||
light[i] = 0xF0;
|
||||
}
|
||||
|
||||
@@ -4047,6 +4047,7 @@ fn decode_lod_surfaces(
|
||||
let Tag::List(sections) = root.tag.get("sections")? else {
|
||||
return None;
|
||||
};
|
||||
let water_state = assets.registry.default_state("minecraft:water");
|
||||
let mut sections: Vec<_> = sections
|
||||
.iter()
|
||||
.filter_map(|section| {
|
||||
@@ -4093,11 +4094,15 @@ fn decode_lod_surfaces(
|
||||
descriptor.name.as_str(),
|
||||
"minecraft:chorus_plant" | "minecraft:chorus_flower"
|
||||
);
|
||||
(
|
||||
state,
|
||||
fluid,
|
||||
fluid || properties.blocks_motion_in_heightmap() || chorus,
|
||||
)
|
||||
let solid = properties.blocks_motion_in_heightmap();
|
||||
// Kelp and seagrass: the water they hold. Left out, the
|
||||
// water stopped short where they reach the surface.
|
||||
match water_state {
|
||||
Some(water) if !fluid && !solid && properties.has_fluid() => {
|
||||
(water, true, true)
|
||||
}
|
||||
_ => (state, fluid, fluid || solid || chorus),
|
||||
}
|
||||
}))
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
|
||||
@@ -8,7 +8,10 @@ use crate::{
|
||||
use horizon_world::{
|
||||
heightmap::HeightmapState,
|
||||
registry::BlockStateId,
|
||||
worldgen::{decoration_order::vanilla_order, generator::GeneratorError},
|
||||
worldgen::{
|
||||
decoration_order::vanilla_order,
|
||||
generator::{GenBlockStates, GeneratorError},
|
||||
},
|
||||
};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
@@ -263,14 +266,15 @@ fn columns(chunk: &ProtoChunk, biome_map: &[u16], dimension: &str) -> Arc<[Colum
|
||||
let (min, height) = (chunk.terrain.min_y(), chunk.terrain.height());
|
||||
let max = min + height;
|
||||
let bottom = crate::chunk::ColumnLayout::of_level(min, height).min_y();
|
||||
let water = registry.default_state("minecraft:water");
|
||||
Arc::new(std::array::from_fn(|index| {
|
||||
let x = (index & 15) as i32;
|
||||
let z = (index >> 4) as i32;
|
||||
let mut column = Column::default();
|
||||
for y in min..max {
|
||||
let state = chunk.block(x, y, z);
|
||||
let mut state = chunk.block(x, y, z);
|
||||
let properties = states.get(state);
|
||||
let name = registry.state(state).map_or("", |s| s.name.as_str());
|
||||
let mut name = registry.state(state).map_or("", |s| s.name.as_str());
|
||||
if properties.is_air()
|
||||
|| (!properties.blocks_motion_in_heightmap()
|
||||
&& !properties.has_fluid()
|
||||
@@ -278,13 +282,26 @@ fn columns(chunk: &ProtoChunk, biome_map: &[u16], dimension: &str) -> Arc<[Colum
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Kelp and seagrass hold water: far off they are the water around
|
||||
// them, as saved chunks' far columns have them. As spans of their
|
||||
// own they hid the seabed with no top face of theirs, a hole
|
||||
// where the sky showed through (white dots across the sea).
|
||||
if !properties.blocks_motion_in_heightmap()
|
||||
&& properties.has_fluid()
|
||||
&& !matches!(name, "minecraft:water" | "minecraft:lava")
|
||||
&& let Some(water) = water
|
||||
{
|
||||
state = water;
|
||||
name = "minecraft:water";
|
||||
}
|
||||
let fluid = matches!(name, "minecraft:water" | "minecraft:lava");
|
||||
let biome = biome_map
|
||||
.get(chunk.biome_id_at(x, y, z).0 as usize)
|
||||
.copied()
|
||||
.unwrap_or(0);
|
||||
// shortcut: no propagated far lighting, use the normal lighting
|
||||
// shortcut: no propagated far block light, use the normal lighting
|
||||
// engine if accurate distant block-light propagation is required.
|
||||
// Skylight is `shade_sky`'s, once the column is complete.
|
||||
let light = if dimension == "minecraft:the_nether" {
|
||||
0
|
||||
} else {
|
||||
@@ -318,9 +335,49 @@ fn columns(chunk: &ProtoChunk, biome_map: &[u16], dimension: &str) -> Arc<[Colum
|
||||
});
|
||||
}
|
||||
}
|
||||
if dimension != "minecraft:the_nether" {
|
||||
shade_sky(&mut column, states);
|
||||
}
|
||||
column
|
||||
}))
|
||||
}
|
||||
/// Skylight as it reaches the top of each span straight down from the sky:
|
||||
/// fluids and leaves take a level per block, as vanilla's dampening does.
|
||||
/// Full skylight everywhere lit seabeds and the ground under canopies as if
|
||||
/// they stood in the open.
|
||||
// ponytail: straight down only. Solid blocks take none, though vanilla's
|
||||
// stop it: the faces under them that far terrain shows are cliffs and
|
||||
// overhangs, which vanilla lights sideways from the open air, and they went
|
||||
// black. The lighting engine for exact light.
|
||||
fn shade_sky(column: &mut Column, states: &GenBlockStates) {
|
||||
// Dimming blocks get a span a level, as a real chunk's light has them,
|
||||
// down to the dark: one lit span for a whole sea lit its bed, where
|
||||
// `Column::reduced` merges water and sand, as brightly as its surface.
|
||||
let mut sky = 15u32;
|
||||
let mut shaded = Vec::with_capacity(column.spans.len());
|
||||
for span in column.spans.iter().rev() {
|
||||
let lit = |min, max, sky: u32| Span {
|
||||
min,
|
||||
max,
|
||||
light: (span.light & !0xf0) | (sky << 4) as u16,
|
||||
..*span
|
||||
};
|
||||
let state = states.get(span.state);
|
||||
if state.motion && state.no_leaves {
|
||||
shaded.push(lit(span.min, span.max, sky));
|
||||
continue;
|
||||
}
|
||||
let mut top = span.max;
|
||||
while top > span.min {
|
||||
let bottom = if sky == 0 { span.min } else { top - 1 };
|
||||
shaded.push(lit(bottom, top, sky));
|
||||
sky = sky.saturating_sub((top - bottom) as u32);
|
||||
top = bottom;
|
||||
}
|
||||
}
|
||||
shaded.reverse();
|
||||
column.spans = shaded;
|
||||
}
|
||||
|
||||
pub(super) struct VanillaLodSampler {
|
||||
generator: Arc<VanillaTerrain>,
|
||||
@@ -502,13 +559,13 @@ impl VanillaLodSampler {
|
||||
},
|
||||
});
|
||||
}
|
||||
let light = if self.generator.dimension == "minecraft:the_nether" {
|
||||
0
|
||||
} else {
|
||||
240
|
||||
};
|
||||
let nether = self.generator.dimension == "minecraft:the_nether";
|
||||
let light = if nether { 0 } else { 240 };
|
||||
self.vegetation.apply(&mut column, x, z, width, light);
|
||||
self.islands.apply(&mut column, x, z, width, light);
|
||||
if !nether {
|
||||
shade_sky(&mut column, self.generator.generator.data().block_states());
|
||||
}
|
||||
column
|
||||
}
|
||||
}
|
||||
@@ -685,6 +742,21 @@ mod tests {
|
||||
};
|
||||
assert!(count("_leaves") > 0, "unexplored forest has a real canopy");
|
||||
assert!(count("_log") > 0, "canopy retains its trunks");
|
||||
let sky = |span: &Span| (span.light >> 4) & 15;
|
||||
assert!(
|
||||
generated
|
||||
.iter()
|
||||
.filter_map(|column| column.spans.last())
|
||||
.all(|top| sky(top) == 15),
|
||||
"the top of every column is in the open"
|
||||
);
|
||||
assert!(
|
||||
generated
|
||||
.iter()
|
||||
.flat_map(|column| &column.spans)
|
||||
.any(|span| (1..15).contains(&sky(span))),
|
||||
"ground under the canopy is shaded, not black"
|
||||
);
|
||||
assert!(generated.iter().all(|column| {
|
||||
column
|
||||
.spans
|
||||
|
||||
Reference in new issue
Block a user