From 5ed77b505447fa98cd58cfcbe10c44517ef0de3a Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 1 Apr 2020 20:03:15 -0400 Subject: [PATCH 1/5] Fix security --- Cargo.lock | 18 ++++++++++++++---- world/Cargo.toml | 2 +- world/src/sim/util.rs | 7 ++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 311406a162..98cf25c4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -331,9 +331,13 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" -version = "0.15.2" +version = "0.17.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" +checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +dependencies = [ + "either", + "radium", +] [[package]] name = "blake2b_simd" @@ -410,9 +414,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" +checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byteorder" @@ -3401,6 +3405,12 @@ dependencies = [ "proc-macro2 1.0.9", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + [[package]] name = "rand" version = "0.4.6" diff --git a/world/Cargo.toml b/world/Cargo.toml index 0784d933a9..0ff1838510 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [dependencies] bincode = "1.2.0" common = { package = "veloren-common", path = "../common" } -bitvec = "0.15.2" +bitvec = "0.17.4" image = "0.22.3" itertools = "0.8.2" vek = "0.10.0" diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index fa138c40bf..e9f9cd816f 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -1,5 +1,5 @@ use super::WORLD_SIZE; -use bitvec::prelude::{bitbox, bitvec, BitBox}; +use bitvec::prelude::{bitbox, BitBox}; use common::{terrain::TerrainChunkSize, vol::RectVolSize}; use noise::{MultiFractal, NoiseFn, Perlin, Point2, Point3, Point4, Seedable}; use num::Float; @@ -322,10 +322,11 @@ pub fn get_oceans(oldh: impl Fn(usize) -> F + Sync) -> BitBox { while let Some(chunk_idx) = stack.pop() { // println!("Ocean chunk {:?}: {:?}", uniform_idx_as_vec2(chunk_idx), // oldh(chunk_idx)); - if *is_ocean.at(chunk_idx) { + let mut is_ocean = is_ocean.get_mut(chunk_idx).unwrap(); + if *is_ocean { continue; } - *is_ocean.at(chunk_idx) = true; + *is_ocean = true; stack.extend(neighbors(chunk_idx).filter(|&neighbor_idx| { // println!("Ocean neighbor: {:?}: {:?}", uniform_idx_as_vec2(neighbor_idx), // oldh(neighbor_idx)); From 831d7c77a60b2370b0dc69ac3f98fe0634b0f9a1 Mon Sep 17 00:00:00 2001 From: Imbris Date: Sat, 28 Mar 2020 21:09:19 -0400 Subject: [PATCH 2/5] Make Asset impls avoid panics when parsing fails --- common/src/assets/mod.rs | 18 +++++++++----- common/src/comp/body.rs | 4 ++-- common/src/comp/inventory/item/mod.rs | 2 +- voxygen/src/hud/item_imgs.rs | 2 +- voxygen/src/i18n.rs | 3 ++- voxygen/src/scene/figure/load.rs | 34 +++++++++++++-------------- world/src/block/natural.rs | 2 +- 7 files changed, 36 insertions(+), 29 deletions(-) diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index cadff415de..1da7243b52 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -19,8 +19,8 @@ use std::{ /// The error returned by asset loading functions #[derive(Debug, Clone)] pub enum Error { - /// An internal error occurred. - Internal(Arc), + /// Parsing error occurred. + ParseError(Arc), /// An asset of a different type has already been loaded with this /// specifier. InvalidType, @@ -28,10 +28,16 @@ pub enum Error { NotFound(String), } +impl Error { + pub fn parse_error(err: E) -> Self { + Self::ParseError(Arc::new(err)) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Error::Internal(err) => err.fmt(f), + Error::ParseError(err) => write!(f, "{:?}", err), Error::InvalidType => write!( f, "an asset of a different type has already been loaded with this specifier." @@ -226,7 +232,7 @@ impl Asset for DynamicImage { fn parse(mut buf_reader: BufReader) -> Result { let mut buf = Vec::new(); buf_reader.read_to_end(&mut buf)?; - Ok(image::load_from_memory(&buf).unwrap()) + image::load_from_memory(&buf).map_err(Error::parse_error) } } @@ -236,7 +242,7 @@ impl Asset for DotVoxData { fn parse(mut buf_reader: BufReader) -> Result { let mut buf = Vec::new(); buf_reader.read_to_end(&mut buf)?; - Ok(dot_vox::load_bytes(&buf).unwrap()) + dot_vox::load_bytes(&buf).map_err(Error::parse_error) } } @@ -245,7 +251,7 @@ impl Asset for Value { const ENDINGS: &'static [&'static str] = &["json"]; fn parse(buf_reader: BufReader) -> Result { - Ok(serde_json::from_reader(buf_reader).unwrap()) + serde_json::from_reader(buf_reader).map_err(Error::parse_error) } } diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index f75acce151..a777c4ace5 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -16,7 +16,7 @@ use crate::{ }; use specs::{Component, FlaggedStorage}; use specs_idvs::IDVStorage; -use std::{fs::File, io::BufReader, sync::Arc}; +use std::{fs::File, io::BufReader}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[repr(u32)] @@ -84,7 +84,7 @@ impl< const ENDINGS: &'static [&'static str] = &["json"]; fn parse(buf_reader: BufReader) -> Result { - serde_json::de::from_reader(buf_reader).map_err(|e| assets::Error::Internal(Arc::new(e))) + serde_json::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 34743a0bb4..5cc42d5b8e 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -75,7 +75,7 @@ impl Asset for Item { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).unwrap()) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } diff --git a/voxygen/src/hud/item_imgs.rs b/voxygen/src/hud/item_imgs.rs index 816d5fd2aa..84566e45c0 100644 --- a/voxygen/src/hud/item_imgs.rs +++ b/voxygen/src/hud/item_imgs.rs @@ -78,7 +78,7 @@ impl Asset for ItemImagesSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing item images spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } diff --git a/voxygen/src/i18n.rs b/voxygen/src/i18n.rs index 3b8064a849..81b758a128 100644 --- a/voxygen/src/i18n.rs +++ b/voxygen/src/i18n.rs @@ -108,7 +108,8 @@ impl Asset for VoxygenLocalization { /// Load the translations located in the input buffer and convert them /// into a `VoxygenLocalization` object. fn parse(buf_reader: BufReader) -> Result { - let mut asked_localization: VoxygenLocalization = from_reader(buf_reader).unwrap(); + let mut asked_localization: VoxygenLocalization = + from_reader(buf_reader).map_err(assets::Error::parse_error)?; // Update the text if UTF-8 to ASCII conversion is enabled if asked_localization.convert_utf8_to_ascii { diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 31457d3fcd..fcb221726d 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -139,7 +139,7 @@ impl Asset for HumHeadSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid head spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -261,49 +261,49 @@ impl Asset for HumArmorShoulderSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor shoulder spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumArmorChestSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor chest spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumArmorHandSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor hand spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumArmorBeltSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor belt spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumArmorPantsSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor pants spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumArmorFootSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid armor foot spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } impl Asset for HumMainWeaponSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing humanoid main weapon spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -667,7 +667,7 @@ impl Asset for QuadrupedSmallCentralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing quad_small central spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -675,7 +675,7 @@ impl Asset for QuadrupedSmallLateralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing quadruped small lateral spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -828,7 +828,7 @@ impl Asset for QuadrupedMediumCentralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing quadruped medium central spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -836,7 +836,7 @@ impl Asset for QuadrupedMediumLateralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing quadruped medium lateral spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -1074,7 +1074,7 @@ impl Asset for BirdMediumCenterSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing bird medium center spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -1082,7 +1082,7 @@ impl Asset for BirdMediumLateralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing bird medium lateral spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -1232,7 +1232,7 @@ impl Asset for CritterCenterSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing critter center spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -1587,7 +1587,7 @@ impl Asset for BipedLargeCenterSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing biped large center spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } @@ -1595,7 +1595,7 @@ impl Asset for BipedLargeLateralSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing biped large lateral spec")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } diff --git a/world/src/block/natural.rs b/world/src/block/natural.rs index c60d593d34..0c2814dada 100644 --- a/world/src/block/natural.rs +++ b/world/src/block/natural.rs @@ -87,7 +87,7 @@ impl Asset for StructuresSpec { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - Ok(ron::de::from_reader(buf_reader).expect("Error parsing structure specs")) + ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error) } } From 4652ae2f034e02c1adf3fd46302d68bd15bbe996 Mon Sep 17 00:00:00 2001 From: Imbris Date: Wed, 1 Apr 2020 20:37:23 -0400 Subject: [PATCH 3/5] Show errors loading individual files during glob asset loading --- common/src/assets/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/src/assets/mod.rs b/common/src/assets/mod.rs index 1da7243b52..cdfa7fa1f2 100644 --- a/common/src/assets/mod.rs +++ b/common/src/assets/mod.rs @@ -116,7 +116,16 @@ pub fn load_glob(specifier: &str) -> Result>> let assets = Arc::new( glob_matches .into_iter() - .filter_map(|name| load(&specifier.replace("*", &name)).ok()) + .filter_map(|name| { + load(&specifier.replace("*", &name)) + .map_err(|e| { + error!( + "Failed to load \"{}\" as part of glob \"{}\" with error: {:?}", + name, specifier, e + ) + }) + .ok() + }) .collect::>(), ); let clone = Arc::clone(&assets); From 3e4b40380ffcf553e8db525e9a75000529a94912 Mon Sep 17 00:00:00 2001 From: Shane Handley Date: Thu, 2 Apr 2020 14:47:26 +1100 Subject: [PATCH 4/5] fix: Restore run SFX when the character is using a weapon other than the sword. --- .../src/audio/sfx/event_mapper/movement/mod.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs index 8f4a6d2916..36dc653303 100644 --- a/voxygen/src/audio/sfx/event_mapper/movement/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/movement/mod.rs @@ -98,17 +98,14 @@ impl MovementEventMapper { Some(Self::get_volume_for_body_type(body)), )); - // Set the new previous entity state - state.event = mapped_event; state.time = Instant::now(); - state.weapon_drawn = Self::weapon_drawn(character); - state.on_ground = physics.on_ground; - } else { - // If we don't dispatch the event, store this data as we can use it to determine - // the next event - state.event = mapped_event; - state.on_ground = physics.on_ground; } + + // update state to determine the next event. We only record the time (above) if + // it was dispatched + state.event = mapped_event; + state.weapon_drawn = Self::weapon_drawn(character); + state.on_ground = physics.on_ground; } } From 13388ee6a42943f3f79a9cb488346cef18e272fe Mon Sep 17 00:00:00 2001 From: Joshua Yanovski Date: Thu, 2 Apr 2020 20:30:08 +0200 Subject: [PATCH 5/5] Various fixes (to coloring and to soft shadows). --- client/src/lib.rs | 21 ++++++++------------- world/src/sim/map.rs | 5 ++++- world/src/sim/mod.rs | 5 ++--- world/src/sim/util.rs | 14 ++++++++++---- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/client/src/lib.rs b/client/src/lib.rs index d5bde04a28..fc1318f95a 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -30,9 +30,6 @@ use common::{ vol::RectVolSize, ChatType, }; -// TODO: remove CONFIG dependency by passing CONFIG.sea_level explicitly. -// In general any WORLD dependencies need to go away ASAP... we should see if we -// can pull out map drawing into common somehow. use hashbrown::HashMap; use image::DynamicImage; use log::{error, warn}; @@ -44,10 +41,9 @@ use std::{ }; use uvth::{ThreadPool, ThreadPoolBuilder}; use vek::*; -use world::{ - sim::{neighbors, Alt}, - CONFIG, -}; +// TODO: remove world dependencies. We should see if we +// can pull out map drawing into common somehow. +use world::sim::{neighbors, Alt}; // The duration of network inactivity until the player is kicked // @TODO: in the future, this should be configurable on the server @@ -135,9 +131,8 @@ impl Client { assert_eq!(rgba.len(), (map_size.x * map_size.y) as usize); let [west, east] = world_map.horizons; let scale_angle = - |a: u8| (a as Alt / 255.0 / ::FRAC_2_PI()).tan(); - let scale_height = - |h: u8| h as Alt * max_height as Alt / 255.0 + CONFIG.sea_level as Alt; + |a: u8| (a as Alt / 255.0 * ::FRAC_PI_2()).tan(); + let scale_height = |h: u8| h as Alt / 255.0 * max_height as Alt; log::debug!("Preparing image..."); let unzip_horizons = |(angles, heights): (Vec<_>, Vec<_>)| { @@ -154,8 +149,8 @@ impl Client { map_config.lgain = 1.0; map_config.gain = max_height; map_config.horizons = Some(&horizons); - let rescale_height = - |h: Alt| (h as f32 - map_config.focus.z as f32) / map_config.gain as f32; + map_config.focus.z = 0.0; + let rescale_height = |h: Alt| (h / max_height as Alt) as f32; let bounds_check = |pos: Vec2| { pos.reduce_partial_min() >= 0 && pos.x < map_size.x as i32 @@ -211,7 +206,7 @@ impl Client { let posi = pos.y as usize * map_size.x as usize + pos.x as usize; scale_height(rgba[posi].to_le_bytes()[3]) } else { - CONFIG.sea_level as Alt + 0.0 }) }, |pos, (r, g, b, a)| { diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 311ed32d6e..2835743fbb 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -273,6 +273,8 @@ impl<'a> MapConfig<'a> { }) .map(|sample| { // TODO: Eliminate the redundancy between this and the block renderer. + let alt = sample.alt; + let basement = sample.basement; let grass_depth = (1.5 + 2.0 * sample.chaos).min(alt - basement); let wposz = if is_basement { basement } else { alt }; if is_basement && wposz < alt - grass_depth { @@ -615,7 +617,8 @@ impl<'a> MapConfig<'a> { }) .map(|(angle, height)| { let w = 0.1; - if angle != 0.0 && light_direction.x != 0.0 { + let height = (height - alt as Alt * gain as Alt).max(0.0); + if angle != 0.0 && light_direction.x != 0.0 && height != 0.0 { let deltax = height / angle; let lighty = (light_direction.y / light_direction.x * deltax).abs(); let deltay = lighty - height; diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index c177885b3b..dafbbda996 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1314,9 +1314,8 @@ impl WorldSim { // Build a horizon map. let scale_angle = |angle: Alt| (angle.atan() * ::FRAC_2_PI() * 255.0).floor() as u8; - let scale_height = |height: Alt| { - ((height - CONFIG.sea_level as Alt) * 255.0 / self.max_height as Alt).floor() as u8 - }; + let scale_height = + |height: Alt| (height as Alt * 255.0 / self.max_height as Alt).floor() as u8; let horizons = get_horizon_map( map_config.lgain, Aabr { diff --git a/world/src/sim/util.rs b/world/src/sim/util.rs index e194a9aed5..d25c115b45 100644 --- a/world/src/sim/util.rs +++ b/world/src/sim/util.rs @@ -392,6 +392,10 @@ pub fn get_horizon_map( to_angle: impl Fn(F) -> A + Sync, to_height: impl Fn(F) -> H + Sync, ) -> Result<[(Vec, Vec); 2], ()> { + if maxh < minh { + // maxh must be greater than minh + return Err(()); + } let map_size = Vec2::::from(bounds.size()).map(|e| e as usize); let map_len = map_size.product(); @@ -421,9 +425,11 @@ pub fn get_horizon_map( // March in the given direction. let maxdx = maxdx(wposi.x as isize); let mut slope = F::zero(); - let mut max_height = F::zero(); let h0 = h(posi); - if h0 >= minh { + let h = if h0 < minh { + F::zero() + } else { + let mut max_height = F::zero(); let maxdz = maxh - h0; let posi = posi as isize; for deltax in 1..maxdx { @@ -441,9 +447,9 @@ pub fn get_horizon_map( max_height = h_j_act; } } - } + h0 - minh + max_height + }; let a = slope * lgain; - let h = h0 + max_height; (to_angle(a), to_height(h)) }) .unzip_into_vecs(&mut angles, &mut heights);