From ca8bf6bbccc007fb6182f705c8025919ae739470 Mon Sep 17 00:00:00 2001 From: checkraisefold Date: Sun, 5 Jan 2025 16:51:36 -0800 Subject: [PATCH] Terrain submodule --- rbx_types/src/lib.rs | 6 +- .../src/{ => terrain}/material_colors.rs | 54 ++++++----- rbx_types/src/terrain/mod.rs | 36 +++++++ rbx_types/src/{ => terrain}/smooth_grid.rs | 94 ++++++++++++++----- rbx_types/src/variant.rs | 3 +- 5 files changed, 138 insertions(+), 55 deletions(-) rename rbx_types/src/{ => terrain}/material_colors.rs (82%) create mode 100644 rbx_types/src/terrain/mod.rs rename rbx_types/src/{ => terrain}/smooth_grid.rs (84%) diff --git a/rbx_types/src/lib.rs b/rbx_types/src/lib.rs index bdc44cb8..0aaa7fce 100644 --- a/rbx_types/src/lib.rs +++ b/rbx_types/src/lib.rs @@ -12,13 +12,12 @@ mod error; mod faces; mod font; mod lister; -mod material_colors; mod physical_properties; mod referent; mod security_capabilities; mod shared_string; -mod smooth_grid; mod tags; +mod terrain; mod unique_id; mod variant; @@ -31,12 +30,11 @@ pub use content::*; pub use error::*; pub use faces::*; pub use font::*; -pub use material_colors::*; pub use physical_properties::*; pub use referent::*; pub use security_capabilities::*; pub use shared_string::*; -pub use smooth_grid::*; pub use tags::*; +pub use terrain::*; pub use unique_id::*; pub use variant::*; diff --git a/rbx_types/src/material_colors.rs b/rbx_types/src/terrain/material_colors.rs similarity index 82% rename from rbx_types/src/material_colors.rs rename to rbx_types/src/terrain/material_colors.rs index cbb37535..e1b281e7 100644 --- a/rbx_types/src/material_colors.rs +++ b/rbx_types/src/terrain/material_colors.rs @@ -16,7 +16,7 @@ use crate::Error as CrateError; pub struct MaterialColors { /// The underlying map used by this struct. A `BTreeMap` is used /// over a `HashMap` to ensure serialization with serde is ordered. - inner: BTreeMap, + inner: BTreeMap, } impl MaterialColors { @@ -32,7 +32,7 @@ impl MaterialColors { /// Retrieves the set color for the given material, or the default if /// none is set. #[inline] - pub fn get_color(&self, material: TerrainMaterials) -> Color3uint8 { + pub fn get_color(&self, material: TerrainColorMaterial) -> Color3uint8 { if let Some(color) = self.inner.get(&material) { *color } else { @@ -42,7 +42,7 @@ impl MaterialColors { /// Sets the color for the given material. #[inline] - pub fn set_color(&mut self, material: TerrainMaterials, color: Color3uint8) { + pub fn set_color(&mut self, material: TerrainColorMaterial, color: Color3uint8) { self.inner.insert(material, color); } @@ -79,7 +79,7 @@ impl MaterialColors { impl From for MaterialColors where - T: Into>, + T: Into>, { fn from(value: T) -> Self { Self { @@ -88,7 +88,7 @@ where } } -/// An error that can occur when deserializing or working with MaterialColors and TerrainMaterials. +/// An error that can occur when deserializing or working with MaterialColors and TerrainColorMaterial. #[derive(Debug, Error)] pub(crate) enum MaterialColorsError { /// The `MaterialColors` blob was the wrong number of bytes. @@ -102,7 +102,7 @@ pub(crate) enum MaterialColorsError { UnknownMaterial(String), } -/// Constructs an enum named `TerrainMaterials` for all values contained in +/// Constructs an enum named `TerrainColorMaterial` for all values contained in /// `MaterialColors` alongside a mapping for a default color for that material. /// /// Additionally, makes a constant named `MATERIAL_ORDER` that indicates what @@ -114,9 +114,9 @@ macro_rules! material_colors { // all have tangible downsides. // See: https://danielkeep.github.io/tlborm/book/blk-counting.html - /// A list of all `TerrainMaterials` in the order they must be read + /// A list of all `TerrainColorMaterial` in the order they must be read /// and written. - const MATERIAL_ORDER: [TerrainMaterials; 21] = [$(TerrainMaterials::$name,)*]; + const MATERIAL_ORDER: [TerrainColorMaterial; 21] = [$(TerrainColorMaterial::$name,)*]; /// All materials that are represented by `MaterialColors`. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] @@ -124,13 +124,13 @@ macro_rules! material_colors { feature = "serde", derive(serde::Serialize, serde::Deserialize), )] - pub enum TerrainMaterials { + enum TerrainColorMaterial { $( $name, )* } - impl TerrainMaterials { + impl TerrainColorMaterial { /// Returns the default color for the given `TerrainMaterial`. pub fn default_color(&self) -> Color3uint8 { match self { @@ -141,7 +141,7 @@ macro_rules! material_colors { } } - impl FromStr for TerrainMaterials { + impl FromStr for TerrainColorMaterial { type Err = CrateError; fn from_str(s: &str) -> Result { @@ -193,17 +193,17 @@ mod test { let expected: MaterialColors = serde_json::from_str(serialized).unwrap(); assert_eq!( - expected.get_color(TerrainMaterials::Grass), + expected.get_color(TerrainColorMaterial::Grass), Color3uint8::new(10, 20, 30), ); assert_eq!( - expected.get_color(TerrainMaterials::Mud), + expected.get_color(TerrainColorMaterial::Mud), Color3uint8::new(255, 0, 127), ); assert_eq!( - expected.get_color(TerrainMaterials::Brick), - TerrainMaterials::Brick.default_color() + expected.get_color(TerrainColorMaterial::Brick), + TerrainColorMaterial::Brick.default_color() ); } @@ -211,8 +211,8 @@ mod test { #[cfg(feature = "serde")] fn serialize() { let mut colors = MaterialColors::new(); - colors.set_color(TerrainMaterials::Grass, Color3uint8::new(10, 20, 30)); - colors.set_color(TerrainMaterials::Mud, Color3uint8::new(255, 0, 127)); + colors.set_color(TerrainColorMaterial::Grass, Color3uint8::new(10, 20, 30)); + colors.set_color(TerrainColorMaterial::Mud, Color3uint8::new(255, 0, 127)); assert_eq!( serde_json::to_string(&colors).unwrap(), @@ -284,15 +284,17 @@ mod test { #[test] fn from_str_materials() { - assert!(TerrainMaterials::from_str("Grass").is_ok()); - assert!(TerrainMaterials::from_str("Concrete").is_ok()); - assert!(TerrainMaterials::from_str("Rock").is_ok()); - assert!(TerrainMaterials::from_str("Asphalt").is_ok()); - assert!(TerrainMaterials::from_str("Salt").is_ok()); - assert!(TerrainMaterials::from_str("Pavement").is_ok()); - - assert!(TerrainMaterials::from_str("A name I am certain Roblox will never add").is_err()); + assert!(TerrainColorMaterial::from_str("Grass").is_ok()); + assert!(TerrainColorMaterial::from_str("Concrete").is_ok()); + assert!(TerrainColorMaterial::from_str("Rock").is_ok()); + assert!(TerrainColorMaterial::from_str("Asphalt").is_ok()); + assert!(TerrainColorMaterial::from_str("Salt").is_ok()); + assert!(TerrainColorMaterial::from_str("Pavement").is_ok()); + + assert!( + TerrainColorMaterial::from_str("A name I am certain Roblox will never add").is_err() + ); // `from_str` is case-sensitive - assert!(TerrainMaterials::from_str("gRaSs").is_err()); + assert!(TerrainColorMaterial::from_str("gRaSs").is_err()); } } diff --git a/rbx_types/src/terrain/mod.rs b/rbx_types/src/terrain/mod.rs new file mode 100644 index 00000000..95954e3e --- /dev/null +++ b/rbx_types/src/terrain/mod.rs @@ -0,0 +1,36 @@ +// Terrain has two binary formats, being the material colors and smooth grid blobs. +// The smooth grid spec can be found here: https://github.com/rojo-rbx/rbx-dom/blob/terrain/docs/smooth-grid.md +// The material colors spec can be found here: https://github.com/rojo-rbx/rbx-dom/blob/master/docs/binary-strings.md#materialcolors + +mod material_colors; +mod smooth_grid; + +pub use self::material_colors::*; +pub use self::smooth_grid::*; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +pub enum TerrainMaterials { + Air, + Water, + Grass, + Slate, + Concrete, + Brick, + Sand, + WoodPlanks, + Rock, + Glacier, + Snow, + Sandstone, + Mud, + Basalt, + Ground, + CrackedLava, + Asphalt, + Cobblestone, + Ice, + LeafyGrass, + Salt, + Limestone, + Pavement, +} diff --git a/rbx_types/src/smooth_grid.rs b/rbx_types/src/terrain/smooth_grid.rs similarity index 84% rename from rbx_types/src/smooth_grid.rs rename to rbx_types/src/terrain/smooth_grid.rs index aeaa7ddf..d0cd8305 100644 --- a/rbx_types/src/smooth_grid.rs +++ b/rbx_types/src/terrain/smooth_grid.rs @@ -9,6 +9,8 @@ use crate::Vector3; use crate::Error as CrateError; +use super::TerrainMaterials; + /// Size of a chunk. Chunks are cubes, so this is the length/width/height. const CHUNK_SIZE: i32 = 2i32.pow(5); @@ -101,7 +103,7 @@ impl ChunkCoordinates { #[repr(u8)] #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum Material { +enum TerrainGridMaterial { #[default] Air = 0x00, Water = 0x01, @@ -128,11 +130,43 @@ pub enum Material { Pavement = 0x16, } -impl TryFrom for Material { +impl From for TerrainGridMaterial { + fn from(value: TerrainMaterials) -> Self { + use TerrainGridMaterial::*; + + match value { + TerrainMaterials::Air => Air, + TerrainMaterials::Water => Water, + TerrainMaterials::Grass => Grass, + TerrainMaterials::Slate => Slate, + TerrainMaterials::Concrete => Concrete, + TerrainMaterials::Brick => Brick, + TerrainMaterials::Sand => Sand, + TerrainMaterials::WoodPlanks => WoodPlanks, + TerrainMaterials::Rock => Rock, + TerrainMaterials::Glacier => Glacier, + TerrainMaterials::Snow => Snow, + TerrainMaterials::Sandstone => Sandstone, + TerrainMaterials::Mud => Mud, + TerrainMaterials::Basalt => Basalt, + TerrainMaterials::Ground => Ground, + TerrainMaterials::CrackedLava => CrackedLava, + TerrainMaterials::Asphalt => Asphalt, + TerrainMaterials::Cobblestone => Cobblestone, + TerrainMaterials::Ice => Ice, + TerrainMaterials::LeafyGrass => LeafyGrass, + TerrainMaterials::Salt => Salt, + TerrainMaterials::Limestone => Limestone, + TerrainMaterials::Pavement => Pavement, + } + } +} + +impl TryFrom for TerrainGridMaterial { type Error = CrateError; fn try_from(value: u8) -> Result { - use Material::*; + use TerrainGridMaterial::*; Ok(match value { 0x00 => Air, @@ -167,8 +201,8 @@ impl TryFrom for Material { #[derive(Debug, Error)] pub(crate) enum SmoothGridError { /// The argument provided to `try_from` did not correspond to a known - /// Material. - #[error("cannot convert `{0}` into Material")] + /// TerrainGridMaterial. + #[error("cannot convert `{0}` into TerrainGridMaterial")] UnknownMaterial(u8), } @@ -178,16 +212,16 @@ pub(crate) enum SmoothGridError { pub struct Voxel { solid_occupancy: f32, water_occupancy: f32, - material: Material, + material: TerrainGridMaterial, } impl Voxel { /// Constructs a new `Voxel` with a material and occupancy percentage. /// Equivalent to data writeable from Roblox's `Terrain:WriteVoxels`. /// Occupancy values are between `0.0` and `1.0`, as a percentage of the voxel. - pub fn new(material: Material, solid_occupancy: f32) -> Self { + pub fn new(material: TerrainMaterials, solid_occupancy: f32) -> Self { let mut voxel = Self { - material, + material: material.into(), ..Default::default() }; voxel.set_occupancy(solid_occupancy, 0.0); @@ -199,9 +233,13 @@ impl Voxel { /// occupancy percentage. /// Equivalent to data writeable from Roblox's `Terrain:WriteVoxelChannels`. /// Occupancy values are between `0.0` and `1.0`, as a percentage of the voxel. - pub fn new_with_water(material: Material, solid_occupancy: f32, water_occupancy: f32) -> Self { + pub fn new_with_water( + material: TerrainMaterials, + solid_occupancy: f32, + water_occupancy: f32, + ) -> Self { let mut voxel = Self { - material, + material: material.into(), ..Default::default() }; voxel.set_occupancy(solid_occupancy, water_occupancy); @@ -272,15 +310,17 @@ impl Voxel { if solid_occupancy == 0.0 && water_occupancy == 0.0 { self.solid_occupancy = 1.0; self.water_occupancy = 0.0; - self.material = Material::Air; + self.material = TerrainGridMaterial::Air; return; } // We should encode water as a normal, non-shorelines voxel if there's no solids. - if (solid_occupancy == 0.0 || self.material == Material::Air) && water_occupancy > 0.0 { + if (solid_occupancy == 0.0 || self.material == TerrainGridMaterial::Air) + && water_occupancy > 0.0 + { self.solid_occupancy = water_occupancy; self.water_occupancy = 0.0; - self.material = Material::Water; + self.material = TerrainGridMaterial::Water; return; } @@ -294,8 +334,8 @@ impl Voxel { } } - pub fn set_material(&mut self, material: Material) { - self.material = material; + pub fn set_material(&mut self, material: TerrainMaterials) { + self.material = material.into(); // Occupancy determination depends on material. self.set_occupancy(self.solid_occupancy, self.water_occupancy) @@ -308,29 +348,35 @@ impl Voxel { pub struct Chunk { grid: HashMap, /// For all empty voxels in the chunk, we will write this material - /// at 100% occupancy. Defaults to `Material::Air`. - pub base_material: Material, + /// at 100% occupancy. Defaults to `TerrainGridMaterial::Air`. + base_material: TerrainGridMaterial, } impl Chunk { - /// Constructs a new `Chunk` with a base material of `Material::Air`. + /// Constructs a new `Chunk` with a base material of `TerrainGridMaterial::Air`. #[inline] pub fn new() -> Self { Self { grid: HashMap::new(), - base_material: Material::Air, + base_material: TerrainGridMaterial::Air, } } /// Constructs a new `Chunk` with a user-provided base material. #[inline] - pub fn new_with_base(base_material: Material) -> Self { + pub fn new_with_base(base_material: TerrainMaterials) -> Self { Self { grid: HashMap::new(), - base_material, + base_material: base_material.into(), } } + /// Changes the base material of a `Chunk` to a user-provided base material. + #[inline] + pub fn set_base(&mut self, base_material: TerrainMaterials) { + self.base_material = base_material.into(); + } + /// Finds a `Voxel` at the given position in this `Chunk`, /// returning a reference to it if it exists. #[inline] @@ -527,10 +573,10 @@ mod test { #[test] fn encode_default() { let mut terr = SmoothGrid::new(); - let mut chunk = Chunk::new_with_base(Material::Air); - let mut voxel = Voxel::new_with_water(Material::Grass, 1.0, 0.5); + let mut chunk = Chunk::new_with_base(TerrainGridMaterial::Air); + let mut voxel = Voxel::new_with_water(TerrainGridMaterial::Grass, 1.0, 0.5); for m in 2..=22 { - voxel.set_material(Material::try_from(m as u8).unwrap()); + voxel.set_material(TerrainGridMaterial::try_from(m as u8).unwrap()); chunk.write_voxel(&VoxelCoordinates::new(m - 2, 0, 0), voxel); } terr.write_chunk(&ChunkCoordinates::default(), chunk.clone()); diff --git a/rbx_types/src/variant.rs b/rbx_types/src/variant.rs index e539f688..b8b52e49 100644 --- a/rbx_types/src/variant.rs +++ b/rbx_types/src/variant.rs @@ -129,7 +129,8 @@ make_variant! { UniqueId(UniqueId), MaterialColors(MaterialColors), SecurityCapabilities(SecurityCapabilities), - Terrain(Terrain), + EnumItem(EnumItem), + SmoothGrid(SmoothGrid), } impl From<&'_ str> for Variant {