Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions crates/engine/assets/shaders/world.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,7 @@ fn vs_lod(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> V
if a.x + a.z < a.y + a.w {
c = (c + 1u) % 4u;
}
let lo = lod_pos(w0, w2) - vec3(0.0, 0.125, 0.0);
let size = lod_size(w2, w3);
let local = lo + CORNERS[dir * 4u + c] * size;
let local = lod_pos(w0, w2) + lod_corner(w0, w2, w3, CORNERS[dir * 4u + c]) - vec3(0.0, 0.125, 0.0);
let pos = finish(local, origins[ii]);
var out: VOut;
out.clip = scene.view_proj * pos;
Expand All @@ -372,7 +370,7 @@ fn vs_lod(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> V
out.blend = fr.blend;
out.glow = 0xffffffffu;
out.slot = 0u;
out.light = vec4(1.0, 0.0, 0.0, 0.0);
out.light = vec4(1.0, lod_light(w0, w2, w3));
let kind = lod_tint(w0);
var tint = vec3(1.0);
if kind != 0u {
Expand Down
319 changes: 284 additions & 35 deletions crates/engine/src/client/lod.rs

Large diffs are not rendered by default.

14 changes: 8 additions & 6 deletions crates/engine/src/client/mesher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,7 @@ fn draw_block(
for dir in 0..6 {
let [dx, dy, dz] = NORMALS[dir];
let ni = pidx(x + dx, y + dy, z + dz);
let ns = n.states[ni];
let ninfo = &c.states[ns as usize];
if ctm.hides(ninfo.flags, ns) {
continue;
}
if ninfo.block == info.block && info.layer != RenderLayer::Opaque && info.flags & bf::CULL_SAME != 0 {
if face_hidden(c, ctm, s, n.states[ni]) {
continue;
}
let (ao, light) = corner_data(c, n, ctm, opaque, x, y, z, dir);
Expand Down Expand Up @@ -236,6 +231,13 @@ pub const DIR_RANK: [u8; 6] = [3, 0, 4, 1, 5, 2];

/// A face of a model's box as it is drawn, in the world and as an item alike: its corners in the
/// block, turned and tilted, and the part of its picture (16 across). `None`: it has no such face.
/// Whether a cube's face toward `neighbor` doesn't show: it is opaque (a pack's rule doesn't open
/// it), or the same block, which culls its own (glass).
pub fn face_hidden(c: &Content, ctm: &super::ctm::Ctm, state: u32, neighbor: u32) -> bool {
let (info, n) = (&c.states[state as usize], &c.states[neighbor as usize]);
ctm.hides(n.flags, neighbor) || n.block == info.block && info.layer != RenderLayer::Opaque && info.flags & bf::CULL_SAME != 0
}

pub fn box_face(bx: &crate::content::ModelBox, dir: usize) -> Option<([[f32; 3]; 4], [f32; 4])> {
if bx.uv[dir][0] <= weft_abi::UV_NO_FACE[0] {
return None;
Expand Down
72 changes: 61 additions & 11 deletions crates/engine/src/gpu_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,21 @@ shared! {
Z_SHIFT = 11,
DIR_SHIFT = 22,
TINT_SHIFT = 25,
/// A part of a block (`lod_box`): word 3 holds its x and z edges (5 bits each, 1/16),
/// word 2 its y edges, or with `EXTRUDE` it goes up the whole height.
EXTRUDE_SHIFT = 30,
PARTIAL_SHIFT = 31,
HALF_SHIFT = 16,
HALF_MASK = 0xFFFF,
/// A face's width and depth; its height has 10 bits.
SIZE_BITS = 6,
HEIGHT_MASK = 1023,
EDGE_MASK = 31,
/// Block light, 3 bits a channel, where neither a face's size nor a box's edges go: red and
/// green at the top of word 2, blue's low bits under word 3's AO, its high bit in word 0.
LIGHT_RG_SHIFT = 26,
LIGHT_B_SHIFT = 22,
LIGHT_B_HIGH_SHIFT = 29,
}
/// Tint kinds of faces and quads (0 none); mods' kinds follow the engine's.
tint {
Expand Down Expand Up @@ -310,18 +323,34 @@ pub fn quad_uv(uv: [f32; 4], tint: u32) -> (u32, [u32; 4]) {
}

/// A distant terrain record (`lod_face`): the `dir` face of a box at `pos` in the tile, `size`
/// blocks.
/// blocks, lit by block light `light` (0..15 a channel).
#[inline]
pub fn lod_record(dir: u32, pos: [i32; 3], size: [i32; 3], tex: u32, tint: u32, biome: u32) -> [u32; 4] {
let h = lod_face::HALF_SHIFT;
pub fn lod_record(dir: u32, pos: [i32; 3], size: [i32; 3], tex: u32, tint: u32, biome: u32, light: [u8; 3]) -> [u32; 4] {
use lod_face as l;
let (h, wide) = (l::HALF_SHIFT, (1 << l::SIZE_BITS) - 1);
let [r, g, b] = light.map(|c| c.min(15) as u32 / 2);
[
pos[0] as u32 | (pos[2] as u32) << lod_face::Z_SHIFT | dir << lod_face::DIR_SHIFT | tint << lod_face::TINT_SHIFT,
pos[0] as u32 | (pos[2] as u32) << l::Z_SHIFT | dir << l::DIR_SHIFT | tint << l::TINT_SHIFT | (b >> 2) << l::LIGHT_B_HIGH_SHIFT,
tex | biome << face::BIOME_SHIFT,
(pos[1] as u32 & lod_face::HALF_MASK) | (size[1] as u32) << h,
size[0] as u32 | (size[2] as u32) << h,
(pos[1] as u32 & l::HALF_MASK) | (size[1].max(0) as u32).min(l::HEIGHT_MASK) << h | (r | g << 3) << l::LIGHT_RG_SHIFT,
(size[0] as u32).min(wide) | (size[2] as u32).min(wide) << h | (b & 3) << l::LIGHT_B_SHIFT,
]
}

/// `r` (a one-block record) as the part of its block from `min` to `max` in 1/16; `extrude`: up
/// the whole height it was given.
#[inline]
pub fn lod_box(mut r: [u32; 4], min: [u32; 3], max: [u32; 3], extrude: bool) -> [u32; 4] {
use lod_face as l;
r[0] |= 1 << l::PARTIAL_SHIFT | (extrude as u32) << l::EXTRUDE_SHIFT;
if !extrude {
r[2] = r[2] & !(l::HEIGHT_MASK << l::HALF_SHIFT) | (min[1] | max[1] << 5) << l::HALF_SHIFT;
}
let keep = r[3] & (3 << l::LIGHT_B_SHIFT | 0xFF << 24);
r[3] = keep | min[0] | max[0] << 5 | min[2] << 10 | max[2] << 15;
r
}

/// The WGSL side of the records above: `CORNERS` and their decoders.
fn records() -> String {
let corners: Vec<String> = CORNERS
Expand Down Expand Up @@ -370,8 +399,24 @@ fn records() -> String {
"fn lod_pos(w0: u32, w2: u32) -> vec3<f32> {\n",
" return vec3<f32>(f32(w0 & LOD_FACE_POS_MASK), f32(bitcast<i32>(w2 << LOD_FACE_HALF_SHIFT) >> LOD_FACE_HALF_SHIFT), f32((w0 >> LOD_FACE_Z_SHIFT) & LOD_FACE_POS_MASK));\n",
"}\n",
"fn lod_size(w2: u32, w3: u32) -> vec3<f32> {\n",
" return vec3<f32>(f32(w3 & LOD_FACE_HALF_MASK), f32(w2 >> LOD_FACE_HALF_SHIFT), f32((w3 >> LOD_FACE_HALF_SHIFT) & 255u));\n",
"fn lod_corner(w0: u32, w2: u32, w3: u32, corner: vec3<f32>) -> vec3<f32> {\n",
" let height = f32((w2 >> LOD_FACE_HALF_SHIFT) & LOD_FACE_HEIGHT_MASK);\n",
" if (w0 >> LOD_FACE_PARTIAL_SHIFT) == 0u {\n",
" let s = (1u << LOD_FACE_SIZE_BITS) - 1u;\n",
" return corner * vec3<f32>(f32(w3 & s), height, f32((w3 >> LOD_FACE_HALF_SHIFT) & s));\n",
" }\n",
" let e = vec4<f32>(vec4<u32>(w3, w3 >> 5u, w3 >> 10u, w3 >> 15u) & vec4(LOD_FACE_EDGE_MASK)) / 16.0;\n",
" var y = vec2<f32>(vec2<u32>(w2 >> LOD_FACE_HALF_SHIFT, w2 >> (LOD_FACE_HALF_SHIFT + 5u)) & vec2(LOD_FACE_EDGE_MASK)) / 16.0;\n",
" if ((w0 >> LOD_FACE_EXTRUDE_SHIFT) & 1u) != 0u {\n",
" y = vec2<f32>(0.0, height);\n",
" }\n",
" let mn = vec3<f32>(e.x, y.x, e.z);\n",
" return mn + corner * (vec3<f32>(e.y, y.y, e.w) - mn);\n",
"}\n",
"fn lod_light(w0: u32, w2: u32, w3: u32) -> vec3<f32> {\n",
" let rg = w2 >> LOD_FACE_LIGHT_RG_SHIFT;\n",
" let b = ((w3 >> LOD_FACE_LIGHT_B_SHIFT) & 3u) | ((w0 >> LOD_FACE_LIGHT_B_HIGH_SHIFT) & 1u) << 2u;\n",
" return vec3<f32>(f32(rg & 7u), f32((rg >> 3u) & 7u), f32(b)) * 2.0 / 15.0;\n",
"}\n",
"fn lod_ao(w3: u32) -> vec4<u32> {\n",
" let a = w3 >> 24u;\n",
Expand Down Expand Up @@ -1044,15 +1089,20 @@ mod tests {
assert_eq!(corner_light([200, 3, 15, 16]), 15 | 3 << 4 | 15 << 8 | 15 << 12);
let q = quad_pos([-2.0, 0.5, 61.9375]);
assert_eq!([q & quad::POS_MASK, (q >> quad::POS_BITS) & quad::POS_MASK, q >> (2 * quad::POS_BITS)], [0, 40, 1023]);
let l = lod_record(4, [2047, -300, 5], [16, 7, 32], 9, 3, 2);
let l = lod_record(4, [2047, -300, 5], [16, 7, 32], 9, 3, 2, [15, 4, 13]);
assert_eq!(l[0] & lod_face::POS_MASK, 2047);
assert_eq!((l[0] >> lod_face::Z_SHIFT) & lod_face::POS_MASK, 5);
assert_eq!((l[0] >> lod_face::DIR_SHIFT) & face::DIR_MASK, 4);
assert_eq!(((l[2] << 16) as i32) >> 16, -300);
assert_eq!([l[3] & 0xFFFF, l[2] >> 16, l[3] >> 16], [16, 7, 32]);
let wide = (1 << lod_face::SIZE_BITS) - 1;
assert_eq!([l[3] & wide, (l[2] >> 16) & lod_face::HEIGHT_MASK, (l[3] >> 16) & wide], [16, 7, 32]);
// A box keeps the light and AO, and its position.
let b = lod_box([l[0], l[1], l[2], l[3] | 0xAB << 24], [1, 2, 3], [15, 14, 13], false);
assert_eq!([b[0] & !(0b11 << lod_face::EXTRUDE_SHIFT), b[2] >> lod_face::LIGHT_RG_SHIFT, b[3] >> 24], [l[0], 7 | 2 << 3, 0xAB]);
assert_eq!((b[3] >> lod_face::LIGHT_B_SHIFT) & 3 | (b[0] >> lod_face::LIGHT_B_HIGH_SHIFT & 1) << 2, 6);
let src = format!(
"{}{}const F0: u32 = {}u;\nconst Q: u32 = {}u;\nconst L0: u32 = {}u;\nconst L2: u32 = {}u;\nconst L3: u32 = {}u;\n\
@compute @workgroup_size(1) fn main() {{ _ = face_pos(F0); _ = face_dir(F0); _ = face_ao(F0); _ = quad_pos(Q); _ = lod_pos(L0, L2); _ = lod_size(L2, L3); _ = corner_light(1u, 2u, 3u); _ = quad_uv(Q); _ = quad_uv_fine(Q, Q, Q, Q, Q); _ = lod_dir(L0); _ = lod_tint(L0); }}\n",
@compute @workgroup_size(1) fn main() {{ _ = face_pos(F0); _ = face_dir(F0); _ = face_ao(F0); _ = quad_pos(Q); _ = lod_pos(L0, L2); _ = lod_corner(L0, L2, L3, vec3(1.0)); _ = lod_light(L0, L2, L3); _ = corner_light(1u, 2u, 3u); _ = quad_uv(Q); _ = quad_uv_fine(Q, Q, Q, Q, Q); _ = lod_dir(L0); _ = lod_tint(L0); }}\n",
wgsl(),
records(),
f[0],
Expand Down
24 changes: 23 additions & 1 deletion crates/engine/src/net/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use glam::{DVec3, IVec3};
use weft_abi::{ItemStack, ParticleBurst};

use super::{ToClient, ToServer};
use crate::server::lod_tops::{LodColumn, Span};
use crate::world::{Cube, GameMode, Pose};

#[derive(Default)]
Expand Down Expand Up @@ -165,6 +166,27 @@ macro_rules! tuples {
}
tuples!((0 A, 1 B), (0 A, 1 B, 2 C), (0 A, 1 B, 2 C, 3 D), (0 A, 1 B, 2 C, 3 D, 4 E), (0 A, 1 B, 2 C, 3 D, 4 E, 5 F), (0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G));

impl Wire for Span {
fn put(&self, w: &mut W) {
(self.bottom, self.top, self.block, self.light).put(w);
}
fn get(r: &mut R) -> Option<Self> {
let (bottom, top, block, light) = Wire::get(r)?;
Some(Span { bottom, top, block, light })
}
}

impl Wire for LodColumn {
fn put(&self, w: &mut W) {
(self.x, self.z, self.y, self.top, self.under, self.light).put(w);
self.spans.put(w);
}
fn get(r: &mut R) -> Option<Self> {
let (x, z, y, top, under, light) = Wire::get(r)?;
Some(LodColumn { x, z, y, top, under, light, spans: Wire::get(r)? })
}
}

impl Wire for IVec3 {
fn put(&self, w: &mut W) {
self.to_array().put(w);
Expand Down Expand Up @@ -296,7 +318,7 @@ impl Wire for ParticleBurst {
macro_rules! items {
($($t:ty),*) => {$(impl Item for $t {})*};
}
items!(u32, u64, i32, f32, f64, bool, String, IVec3, DVec3, ItemStack, Arc<Cube>, Msg, ParticleBurst);
items!(u32, u64, i32, f32, f64, bool, String, IVec3, DVec3, ItemStack, Arc<Cube>, Msg, ParticleBurst, Span, LodColumn);

pub fn encode_client(m: &ToClient) -> Vec<u8> {
let mut w = W::default();
Expand Down
7 changes: 4 additions & 3 deletions crates/engine/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use glam::{DVec3, IVec3};

use weft_abi::ItemStack;

use crate::server::lod_tops::LodColumn;
use crate::world::Cube;
use codec::Wire;

Expand Down Expand Up @@ -294,9 +295,9 @@ messages! {
/// Area edits of the dimension (`AreaFill::spec`): `all` replaces what the client has,
/// otherwise they come after it. Clients make them on what they generate.
Edits { dim: u32, specs: Vec<String>, all: bool },
/// Columns of dimension `dim` whose top isn't the terrain's, for distant terrain: (x, z,
/// height, top, under), `top` `u32::MAX` for one that is again; `all`: every one there is.
LodColumns { dim: u32, all: bool, cols: Vec<(i32, i32, i32, u32, u32)> },
/// Columns of dimension `dim` that differ from the terrain, for distant terrain (`top`
/// `u32::MAX` for one that doesn't any more); `all`: every one there is.
LodColumns { dim: u32, all: bool, cols: Vec<LodColumn> },
/// Of the world, or of body `frame`.
/// `extras`: what else is in cells, after `changes` (which take it away).
Blocks { dim: u32, frame: u64, changes: Vec<(IVec3, u32)>, extras: Vec<(IVec3, Vec<u32>)> },
Expand Down
4 changes: 2 additions & 2 deletions crates/engine/src/saves.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,7 +517,7 @@ impl Storage {
Some((cube?, data[4 + len..].to_vec()))
}

pub fn save(&mut self, c: &Content, dim: u32, pos: IVec3, cube: &Cube, entities: &[u8]) {
pub fn save(&mut self, c: &Content, dim: u32, pos: IVec3, cube: &Cube, around: &crate::server::lod_tops::Around, entities: &[u8]) {
if !c.dims[dim as usize].saved {
return;
}
Expand Down Expand Up @@ -555,7 +555,7 @@ impl Storage {
}
self.saved[dim as usize].insert(pos);
let seed = self.meta.seed;
self.lod[dim as usize].saved(c, dim, seed, pos, &cube);
self.lod[dim as usize].saved(c, dim, seed, pos, &cube, around);
}

pub fn flush(&mut self) {
Expand Down
12 changes: 12 additions & 0 deletions crates/engine/src/server/dim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,18 @@ impl Dim {

/// Drops cubes `keep` rejects; returns the ones that must be saved (modified or holding
/// entities) together with the entities that lived in them, which leave the world.
/// The cubes beside the one at `pos` for its distant light (`lod_tops::Around`): loaded, or
/// as `gone` has them.
pub fn around(&self, pos: IVec3, gone: impl Fn(IVec3) -> Option<Arc<Cube>>) -> crate::server::lod_tops::Around {
[IVec3::NEG_X, IVec3::X, IVec3::NEG_Z, IVec3::Z].map(|d| {
self.space
.cubes
.get(&(pos + d))
.cloned()
.or_else(|| gone(pos + d))
})
}

pub fn retain_cubes(&mut self, c: &Content, keep: impl Fn(IVec3) -> bool) -> Vec<(IVec3, Arc<Cube>, Vec<u64>)> {
let mut out = Vec::new();
let gone: FxHashSet<IVec3> = self
Expand Down
8 changes: 6 additions & 2 deletions crates/engine/src/server/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,16 @@ impl Server {
.any(|&(v, lo, hi)| v.column(pos) && pos.y >= lo && pos.y <= hi)
|| tickets.iter().any(|t| t.contains(pos))
});
for (pos, cube, inside) in removed {
let around: Vec<_> = removed
.iter()
.map(|x| r.around(x.0, |p| removed.iter().find(|o| o.0 == p).map(|o| o.1.clone())))
.collect();
for ((pos, cube, inside), around) in removed.into_iter().zip(around) {
if let Some(s) = &storage {
// Written in the background; loads queue behind it there.
let (s, mut bytes, dim) = (s.clone(), serialize(c, r, &inside), r.id);
persist::append_ticks(&mut bytes, &r.ticks_in(pos));
crate::background().spawn(move || s.lock().save(c, dim, pos, &cube, &bytes));
crate::background().spawn(move || s.lock().save(c, dim, pos, &cube, &around, &bytes));
}
for id in inside {
r.entities.remove(id);
Expand Down
Loading
Loading