diff --git a/Cargo.lock b/Cargo.lock index 23775326c5..f3809db5dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1572,6 +1572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ahash 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/server/src/lib.rs b/server/src/lib.rs index 2b04f88b62..9011d49d34 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -76,6 +76,7 @@ pub struct Tick(u64); pub struct Server { state: State, world: Arc, + map: Vec, postoffice: PostOffice, @@ -119,6 +120,7 @@ impl Server { ..WorldOpts::default() }, ); + let map = world.sim().get_map(); let spawn_point = { // NOTE: all of these `.map(|e| e as [type])` calls should compile into no-ops, @@ -178,6 +180,7 @@ impl Server { let this = Self { state, world: Arc::new(world), + map, postoffice: PostOffice::bind(settings.gameserver_address)?, @@ -1084,7 +1087,7 @@ impl Server { .create_entity_package(entity), server_info: self.server_info.clone(), time_of_day: *self.state.ecs().read_resource(), - world_map: (WORLD_SIZE.map(|e| e as u32), self.world.sim().get_map()), + world_map: (WORLD_SIZE.map(|e| e as u32), self.map.clone()), }); log::debug!("Done initial sync with client."); diff --git a/world/examples/water.rs b/world/examples/water.rs index 68a7d48aa3..ec781d9c22 100644 --- a/world/examples/water.rs +++ b/world/examples/water.rs @@ -3,7 +3,7 @@ use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use std::{f32, f64, path::PathBuf}; use vek::*; use veloren_world::{ - sim::{self, RiverKind, WorldOpts, WORLD_SIZE}, + sim::{self, MapConfig, MapDebug, RiverKind, WorldOpts, WORLD_SIZE}, util::Sampler, World, CONFIG, }; @@ -46,11 +46,11 @@ fn main() { }, ); - let sampler = world.sim(); - let mut win = minifb::Window::new("World Viewer", W, H, minifb::WindowOptions::default()).unwrap(); + let sampler = world.sim(); + let mut focus = Vec3::new(0.0, 0.0, CONFIG.sea_level as f64); // Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain makes // smaller differences in altitude appear larger. @@ -67,7 +67,6 @@ fn main() { // // "In world space the x-axis will be pointing east, the y-axis up and the z-axis will be pointing south" let mut light_direction = Vec3::new(-0.8, -1.0, 0.3); - let light_res = 3; let mut is_basement = false; let mut is_water = true; @@ -76,199 +75,33 @@ fn main() { let mut is_humidity = true; while win.is_open() { - let light = light_direction.normalized(); + let config = MapConfig { + dimensions: Vec2::new(W, H), + focus, + gain, + lgain, + scale, + light_direction, + + is_basement, + is_water, + is_shaded, + is_temperature, + is_humidity, + is_debug: true, + }; + let mut buf = vec![0; W * H]; - const QUADRANTS: usize = 4; - let mut quads = [[0u32; QUADRANTS]; QUADRANTS]; - let mut rivers = 0u32; - let mut lakes = 0u32; - let mut oceans = 0u32; - - // let water_light = (light_direction.z + 1.0) / 2.0 * 0.8 + 0.2; - let focus_rect = Vec2::from(focus); - let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64; - - for i in 0..W { - for j in 0..H { - let pos = - (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32); - /* let top_left = pos; - let top_right = focus + Vec2::new(i as i32 + light_res, j as i32) * scale; - let bottom_left = focus + Vec2::new(i as i32, j as i32 + light_res) * scale; */ - - let (alt, basement, water_alt, humidity, temperature, downhill, river_kind) = - sampler - .get(pos) - .map(|sample| { - ( - sample.alt, - sample.basement, - sample.water_alt, - sample.humidity, - sample.temp, - sample.downhill, - sample.river.river_kind, - ) - }) - .unwrap_or(( - CONFIG.sea_level, - CONFIG.sea_level, - CONFIG.sea_level, - 0.0, - 0.0, - None, - None, - )); - let humidity = humidity.min(1.0).max(0.0); - let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; - let pos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); - let downhill_pos = (downhill - .map(|downhill_pos| downhill_pos/*.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32)*/) - .unwrap_or(pos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) - - pos)/* * scale*/ - + pos; - let downhill_alt = sampler - .get_wpos(downhill_pos) - .map(|s| if is_basement { s.basement } else { s.alt }) - .unwrap_or(CONFIG.sea_level); - let alt = if is_basement { basement } else { alt }; - /* let alt_tl = sampler.get(top_left).map(|s| s.alt) - .unwrap_or(CONFIG.sea_level); - let alt_tr = sampler.get(top_right).map(|s| s.alt) - .unwrap_or(CONFIG.sea_level); - let alt_bl = sampler.get(bottom_left).map(|s| s.alt) - .unwrap_or(CONFIG.sea_level); */ - let cross_pos = pos - + ((downhill_pos - pos) - .map(|e| e as f32) - .rotated_z(f32::consts::FRAC_PI_2) - .map(|e| e as i32)); - let cross_alt = sampler - .get_wpos(cross_pos) - .map(|s| if is_basement { s.basement } else { s.alt }) - .unwrap_or(CONFIG.sea_level); - // Pointing downhill, forward - // (index--note that (0,0,1) is backward right-handed) - let forward_vec = Vec3::new( - (downhill_pos.x - pos.x) as f64, - (downhill_alt - alt) as f64 * lgain, - (downhill_pos.y - pos.y) as f64, - ); - // Pointing 90 degrees left (in horizontal xy) of downhill, up - // (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed) - let up_vec = Vec3::new( - (cross_pos.x - pos.x) as f64, - (cross_alt - alt) as f64 * lgain, - (cross_pos.y - pos.y) as f64, - ); - // Then cross points "to the right" (upwards) on a right-handed coordinate system. - // (right-handed coordinate system means (0, 0, 1.0) is "forward" into the screen). - let surface_normal = forward_vec.cross(up_vec).normalized(); - // f = (0, alt_bl - alt_tl, 1) [backward right-handed = (0,0,1)] - // u = (1, alt_tr - alt_tl, 0) [right (90 degrees CCW backward) = (1,0,0)] - // (f × u in right-handed coordinate system: pointing up) - // - // f × u = - // (a.y*b.z - a.z*b.y, - // a.z*b.x - a.x*b.z, - // a.x*b.y - a.y*b.x, - // ) - // = - // (-(alt_tr - alt_tl), - // 1, - // -(alt_bl - alt_tl), - // ) - // = - // (alt_tl - alt_tr, - // 1, - // alt_tl - alt_bl, - // ) - // - // let surface_normal = Vec3::new((alt_tl - alt_tr) as f64, 1.0, (alt_tl - alt_bl) as f64).normalized(); - let light = (surface_normal.dot(light) + 1.0) / 2.0; - let light = (light * 0.9) + 0.1; - - let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64; - let true_alt = (alt as f64 - focus.z) / gain as f64; - let water_depth = (true_water_alt - true_alt).min(1.0).max(0.0); - let water_alt = true_water_alt.min(1.0).max(0.0); - let alt = true_alt.min(1.0).max(0.0); - let quad = - |x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1); - if river_kind.is_none() || humidity != 0.0 { - quads[quad(humidity)][quad(temperature)] += 1; - } - match river_kind { - Some(RiverKind::River { .. }) => { - rivers += 1; - } - Some(RiverKind::Lake { .. }) => { - lakes += 1; - } - Some(RiverKind::Ocean { .. }) => { - oceans += 1; - } - None => {} - } - - buf[j * W + i] = match (river_kind, (is_water, true_alt >= true_sea_level)) { - (_, (false, _)) | (None, (_, true)) => { - let (r, g, b) = ( - (if is_shaded { alt } else { alt } - * if is_temperature { - temperature as f64 - } else if is_shaded { - alt - } else { - 0.0 - }) - .sqrt(), - if is_shaded { 0.2 + (alt * 0.8) } else { alt }, - (if is_shaded { alt } else { alt } - * if is_humidity { - humidity as f64 - } else if is_shaded { - alt - } else { - 0.0 - }) - .sqrt(), - ); - let light = if is_shaded { light } else { 1.0 }; - u32::from_le_bytes([ - (b * light * 255.0) as u8, - (g * light * 255.0) as u8, - (r * light * 255.0) as u8, - 255, - ]) - /* u32::from_le_bytes([ - (/*alt * *//*(1.0 - humidity)*/(alt * humidity).sqrt()/*temperature*/ * 255.0) as u8, - (/*alt*//*alt*//* * humidity*//*alt * 255.0*//*humidity*/alt * 255.0) as u8, - (/*alt*//*alt * *//*(1.0 - humidity)*/(alt * temperature).sqrt() * 255.0) as u8, - 255, - ]) */ - } - (Some(RiverKind::Ocean), _) => u32::from_le_bytes([ - ((64.0 - water_depth * 64.0) * 1.0) as u8, - ((32.0 - water_depth * 32.0) * 1.0) as u8, - 0, - 255, - ]), - (Some(RiverKind::River { .. }), _) => u32::from_le_bytes([ - 64 + (alt * 191.0) as u8, - 32 + (alt * 95.0) as u8, - 0, - 255, - ]), - (None, _) | (Some(RiverKind::Lake { .. }), _) => u32::from_le_bytes([ - (((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8, - (((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8, - 0, - 255, - ]), - }; - } - } + let MapDebug { + rivers, + lakes, + oceans, + quads, + } = config.generate(sampler, |pos, (r, g, b, a)| { + let i = pos.x; + let j = pos.y; + buf[j * W + i] = u32::from_le_bytes([b, g, r, a]); + }); let spd = 32.0; let lspd = 0.1; @@ -299,8 +132,8 @@ fn main() { } if win.get_mouse_down(minifb::MouseButton::Left) { if let Some((mx, my)) = win.get_mouse_pos(minifb::MouseMode::Clamp) { - let pos = - (focus_rect + (Vec2::new(mx as f64, my as f64) * scale)).map(|e| e as i32); + let pos = (Vec2::::from(focus) + (Vec2::new(mx as f64, my as f64) * scale)) + .map(|e| e as i32); println!( "Chunk position: {:?}", pos.map2(TerrainChunkSize::RECT_SIZE, |e, f| e * f as i32) diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs new file mode 100644 index 0000000000..672de33b93 --- /dev/null +++ b/world/src/sim/map.rs @@ -0,0 +1,315 @@ +use crate::{ + sim::{self, uniform_idx_as_vec2, RiverKind, WorldSim, WORLD_SIZE}, + util::Sampler, + CONFIG, +}; +use common::{terrain::TerrainChunkSize, vol::RectVolSize}; +use std::{f32, f64}; +use vek::*; + +pub struct MapConfig { + /// Dimensions of the window being written to. Defaults to WORLD_SIZE. + pub dimensions: Vec2, + /// x, y, and z of top left of map (defaults to (0.0, 0.0, CONFIG.sea_level)). + pub focus: Vec3, + /// Altitude is divided by gain and clamped to [0, 1]; thus, decreasing gain makes + /// smaller differences in altitude appear larger. + /// + /// Defaults to CONFIG.mountain_scale. + pub gain: f32, + /// lgain is used for shading purposes and refers to how much impact a change in the z + /// direction has on the perceived slope relative to the same change in x and y. + /// + /// Defaults to TerrainChunkSize::RECT_SIZE.x. + pub lgain: f64, + /// Scale is like gain, but for x and y rather than z. + /// + /// Defaults to WORLD_SIZE.x / dimensions.x (NOTE: fractional, not integer, division!). + pub scale: f64, + /// Vector that indicates which direction light is coming from, if shading is turned on. + /// + /// Right-handed coordinate system: light is going left, down, and "backwards" (i.e. on the + /// map, where we translate the y coordinate on the world map to z in the coordinate system, + /// the light comes from -y on the map and points towards +y on the map). In a right + /// handed coordinate system, the "camera" points towards -z, so positive z is backwards + /// "into" the camera. + /// + /// "In world space the x-axis will be pointing east, the y-axis up and the z-axis will be pointing south" + /// + /// Defaults to (-0.8, -1.0, 0.3). + pub light_direction: Vec3, + /// If true, only the basement (bedrock) is used for altitude; otherwise, the surface is used. + /// + /// Defaults to false. + pub is_basement: bool, + /// If true, water is rendered; otherwise, the surface without water is rendered, even if it + /// is underwater. + /// + /// Defaults to true. + pub is_water: bool, + /// If true, 3D lighting and shading are turned on. Otherwise, a plain altitude map is used. + /// + /// Defaults to true. + pub is_shaded: bool, + /// If true, the red component of the image is also used for temperature (redder is hotter). + /// Defaults to false. + pub is_temperature: bool, + /// If true, the blue component of the image is also used for humidity (bluer is wetter). + /// + /// Defaults to false. + pub is_humidity: bool, + /// Record debug information. + /// + /// Defaults to false. + pub is_debug: bool, +} + +pub const QUADRANTS: usize = 4; + +pub struct MapDebug { + pub quads: [[u32; QUADRANTS]; QUADRANTS], + pub rivers: u32, + pub lakes: u32, + pub oceans: u32, +} + +impl Default for MapConfig { + fn default() -> Self { + let dimensions = WORLD_SIZE; + Self { + dimensions, + focus: Vec3::new(0.0, 0.0, CONFIG.sea_level as f64), + gain: CONFIG.mountain_scale, + lgain: TerrainChunkSize::RECT_SIZE.x as f64, + scale: WORLD_SIZE.x as f64 / dimensions.x as f64, + light_direction: Vec3::new(-0.8, -1.0, 0.3), + + is_basement: false, + is_water: true, + is_shaded: true, + is_temperature: false, + is_humidity: false, + is_debug: false, + } + } +} + +impl MapConfig { + /// Generates a map image using the specified settings. Note that it will write from left to + /// write from (0, 0) to dimensions - 1, inclusive, with 4 1-byte color components provided + /// as (r, g, b, a). It is up to the caller to provide a function that translates this + /// information into the correct format for a buffer and writes to it. + pub fn generate( + &self, + sampler: &WorldSim, + mut write_pixel: impl FnMut(Vec2, (u8, u8, u8, u8)), + ) -> MapDebug { + let MapConfig { + dimensions, + focus, + gain, + lgain, + scale, + light_direction, + + is_basement, + is_water, + is_shaded, + is_temperature, + is_humidity, + is_debug, + } = *self; + + let light = light_direction.normalized(); + let mut quads = [[0u32; QUADRANTS]; QUADRANTS]; + let mut rivers = 0u32; + let mut lakes = 0u32; + let mut oceans = 0u32; + + // let water_light = (light_direction.z + 1.0) / 2.0 * 0.8 + 0.2; + let focus_rect = Vec2::from(focus); + let true_sea_level = (CONFIG.sea_level as f64 - focus.z) / gain as f64; + + (0..dimensions.y * dimensions.x) + .into_iter() + .for_each(|chunk_idx| { + let pos = uniform_idx_as_vec2(chunk_idx); + let i = pos.x as usize; + let j = pos.y as usize; + + let pos = + (focus_rect + Vec2::new(i as f64, j as f64) * scale).map(|e: f64| e as i32); + + let (alt, basement, water_alt, humidity, temperature, downhill, river_kind) = + sampler + .get(pos) + .map(|sample| { + ( + sample.alt, + sample.basement, + sample.water_alt, + sample.humidity, + sample.temp, + sample.downhill, + sample.river.river_kind, + ) + }) + .unwrap_or(( + CONFIG.sea_level, + CONFIG.sea_level, + CONFIG.sea_level, + 0.0, + 0.0, + None, + None, + )); + let humidity = humidity.min(1.0).max(0.0); + let temperature = temperature.min(1.0).max(-1.0) * 0.5 + 0.5; + let pos = pos * TerrainChunkSize::RECT_SIZE.map(|e| e as i32); + let downhill_pos = (downhill + .map(|downhill_pos| downhill_pos/*.map2(TerrainChunkSize::RECT_SIZE, |e, sz: u32| e / sz as i32)*/) + .unwrap_or(pos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)) + - pos)/* * scale*/ + + pos; + let downhill_alt = sampler + .get_wpos(downhill_pos) + .map(|s| if is_basement { s.basement } else { s.alt }) + .unwrap_or(CONFIG.sea_level); + let alt = if is_basement { basement } else { alt }; + let cross_pos = pos + + ((downhill_pos - pos) + .map(|e| e as f32) + .rotated_z(f32::consts::FRAC_PI_2) + .map(|e| e as i32)); + let cross_alt = sampler + .get_wpos(cross_pos) + .map(|s| if is_basement { s.basement } else { s.alt }) + .unwrap_or(CONFIG.sea_level); + // Pointing downhill, forward + // (index--note that (0,0,1) is backward right-handed) + let forward_vec = Vec3::new( + (downhill_pos.x - pos.x) as f64, + (downhill_alt - alt) as f64 * lgain, + (downhill_pos.y - pos.y) as f64, + ); + // Pointing 90 degrees left (in horizontal xy) of downhill, up + // (middle--note that (1,0,0), 90 degrees CCW backward, is right right-handed) + let up_vec = Vec3::new( + (cross_pos.x - pos.x) as f64, + (cross_alt - alt) as f64 * lgain, + (cross_pos.y - pos.y) as f64, + ); + // Then cross points "to the right" (upwards) on a right-handed coordinate system. + // (right-handed coordinate system means (0, 0, 1.0) is "forward" into the screen). + let surface_normal = forward_vec.cross(up_vec).normalized(); + // f = (0, alt_bl - alt_tl, 1) [backward right-handed = (0,0,1)] + // u = (1, alt_tr - alt_tl, 0) [right (90 degrees CCW backward) = (1,0,0)] + // (f × u in right-handed coordinate system: pointing up) + // + // f × u = + // (a.y*b.z - a.z*b.y, + // a.z*b.x - a.x*b.z, + // a.x*b.y - a.y*b.x, + // ) + // = + // (-(alt_tr - alt_tl), + // 1, + // -(alt_bl - alt_tl), + // ) + // = + // (alt_tl - alt_tr, + // 1, + // alt_tl - alt_bl, + // ) + // + // let surface_normal = Vec3::new((alt_tl - alt_tr) as f64, 1.0, (alt_tl - alt_bl) as f64).normalized(); + let light = (surface_normal.dot(light) + 1.0) / 2.0; + let light = (light * 0.9) + 0.1; + + let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64; + let true_alt = (alt as f64 - focus.z) / gain as f64; + let water_depth = (true_water_alt - true_alt).min(1.0).max(0.0); + let water_alt = true_water_alt.min(1.0).max(0.0); + let alt = true_alt.min(1.0).max(0.0); + if is_debug { + let quad = + |x: f32| ((x as f64 * QUADRANTS as f64).floor() as usize).min(QUADRANTS - 1); + if river_kind.is_none() || humidity != 0.0 { + quads[quad(humidity)][quad(temperature)] += 1; + } + match river_kind { + Some(RiverKind::River { .. }) => { + rivers += 1; + } + Some(RiverKind::Lake { .. }) => { + lakes += 1; + } + Some(RiverKind::Ocean { .. }) => { + oceans += 1; + } + None => {} + } + } + + let rgba = match (river_kind, (is_water, true_alt >= true_sea_level)) { + (_, (false, _)) | (None, (_, true)) => { + let (r, g, b) = ( + (if is_shaded { alt } else { alt } + * if is_temperature { + temperature as f64 + } else if is_shaded { + alt + } else { + 0.0 + }) + .sqrt(), + if is_shaded { 0.2 + (alt * 0.8) } else { alt }, + (if is_shaded { alt } else { alt } + * if is_humidity { + humidity as f64 + } else if is_shaded { + alt + } else { + 0.0 + }) + .sqrt(), + ); + let light = if is_shaded { light } else { 1.0 }; + ( + (r * light * 255.0) as u8, + (g * light * 255.0) as u8, + (b * light * 255.0) as u8, + 255, + ) + } + (Some(RiverKind::Ocean), _) => ( + 0, + ((32.0 - water_depth * 32.0) * 1.0) as u8, + ((64.0 - water_depth * 64.0) * 1.0) as u8, + 255, + ), + (Some(RiverKind::River { .. }), _) => ( + 0, + 32 + (alt * 95.0) as u8, + 64 + (alt * 191.0) as u8, + 255, + ), + (None, _) | (Some(RiverKind::Lake { .. }), _) => ( + 0, + (((32.0 + water_alt * 95.0) + (-water_depth * 32.0)) * 1.0) as u8, + (((64.0 + water_alt * 191.0) + (-water_depth * 64.0)) * 1.0) as u8, + 255, + ), + }; + + write_pixel(Vec2::new(i, j), rgba); + }); + + MapDebug { + quads, + rivers, + lakes, + oceans, + } + } +} diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index e42d605aa4..194c92a10c 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1,6 +1,7 @@ mod diffusion; mod erosion; mod location; +mod map; mod settlement; mod util; @@ -12,6 +13,7 @@ pub use self::erosion::{ mrec_downhill, Alt, RiverData, RiverKind, }; pub use self::location::Location; +pub use self::map::{MapConfig, MapDebug}; pub use self::settlement::Settlement; pub use self::util::{ cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors, @@ -1651,39 +1653,12 @@ impl WorldSim { /// Draw a map of the world based on chunk information. Returns a buffer of u32s. pub fn get_map(&self) -> Vec { - (0..WORLD_SIZE.x * WORLD_SIZE.y) - .into_par_iter() - .map(|chunk_idx| { - let pos = uniform_idx_as_vec2(chunk_idx); - - let (alt, water_alt, river_kind) = self - .get(pos) - .map(|sample| (sample.alt, sample.water_alt, sample.river.river_kind)) - .unwrap_or((CONFIG.sea_level, CONFIG.sea_level, None)); - let alt = ((alt - CONFIG.sea_level) / CONFIG.mountain_scale) - .min(1.0) - .max(0.0); - let water_alt = ((alt.max(water_alt) - CONFIG.sea_level) / CONFIG.mountain_scale) - .min(1.0) - .max(0.0); - match river_kind { - Some(RiverKind::Ocean) => u32::from_le_bytes([0, 32, 64, 255]), - Some(RiverKind::Lake { .. }) => u32::from_le_bytes([ - 0, - 32 + (water_alt * 95.0) as u8, - 64 + (water_alt * 191.0) as u8, - 255, - ]), - Some(RiverKind::River { .. }) => u32::from_le_bytes([ - 0, - 32 + (alt * 95.0) as u8, - 64 + (alt * 191.0) as u8, - 255, - ]), - None => u32::from_le_bytes([0, (alt * 255.0) as u8, 0, 255]), - } - }) - .collect() + let mut v = vec![0u32; WORLD_SIZE.x * WORLD_SIZE.y]; + // TODO: Parallelize again. + MapConfig::default().generate(&self, |pos, (r, g, b, a)| { + v[pos.y * WORLD_SIZE.x + pos.x] = u32::from_le_bytes([r, g, b, a]); + }); + v } /// Prepare the world for simulation @@ -1825,7 +1800,7 @@ impl WorldSim { chunk_idx_center(WORLD_SIZE.map(|e| e as i32)), ) .map_init( - || BlockGen::new(ColumnGen::new(self)), + || Box::new(BlockGen::new(ColumnGen::new(self))), |mut block_gen, (pos, seed)| { let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed)); // println!("Town: {:?}", town);