├── .gitignore ├── Cargo.toml ├── README.md └── src ├── misc.rs ├── types.rs ├── main.rs ├── lib.rs └── mappings.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vscode 5 | *.brs 6 | *.bls 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bls2brs" 3 | version = "0.3.0" 4 | authors = ["ns "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | lazy_static = "1" 9 | regex = "1" 10 | bl_save = "0.2" 11 | brs = "0.1" 12 | wexit = "0.1" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bls2brs 2 | 3 | Convert Blockland saves (bls) to [Brickadia] saves (brs). 4 | 5 | ## Download 6 | 7 | Executables for Linux and Windows can be found on [the Releases page]. 8 | 9 | ## Usage 10 | 11 | Drag `.bls` files onto the executable (`bls2brs.exe` or `bls2brs`) to create corresponding `.brs` files next to them. 12 | 13 | Not all Blockland bricks are supported, but the converter tries its best to support many variants. 14 | 15 | ## Contributing 16 | 17 | Pull requests are appreciated. If you encounter missing bricks, update `src/mappings.rs`. 18 | 19 | [Brickadia]: https://brickadia.com 20 | [the Releases page]: https://github.com/brickadia/bls2brs/releases 21 | -------------------------------------------------------------------------------- /src/misc.rs: -------------------------------------------------------------------------------- 1 | // #[macro_export] 2 | macro_rules! map { 3 | [$($key:expr => $value:expr),* $(,)?] => { 4 | vec![ 5 | $( 6 | ($key, $value), 7 | )* 8 | ].into_iter().collect() 9 | } 10 | } 11 | 12 | // #[macro_export] 13 | macro_rules! brick_map_literal { 14 | [$($ui:expr => $map:expr),* $(,)?] => { 15 | map![ 16 | $($ui => $map.into(),)* 17 | ] 18 | } 19 | } 20 | 21 | // #[macro_export] 22 | macro_rules! brick_map_regex { 23 | [$($source:expr => $func:expr),* $(,)?] => { 24 | vec![ 25 | $( 26 | ( 27 | Regex::new($source).expect("failed to compile regex"), 28 | Box::new($func), 29 | ), 30 | )* 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | pub type BrickMapping = Vec; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct BrickDesc { 5 | pub asset: &'static str, 6 | pub size: (u32, u32, u32), 7 | pub offset: (i32, i32, i32), 8 | pub rotation_offset: u8, 9 | pub color_override: Option, 10 | pub direction_override: Option, 11 | pub non_priority: bool, 12 | pub microwedge_rotate: bool, 13 | pub inverted_modter_rotate: bool, 14 | pub inverted_wedge_rotate: bool, 15 | } 16 | 17 | impl BrickDesc { 18 | pub const fn new(asset: &'static str) -> Self { 19 | Self { 20 | asset, 21 | size: (0, 0, 0), 22 | offset: (0, 0, 0), 23 | rotation_offset: 1, 24 | color_override: None, 25 | direction_override: None, 26 | non_priority: false, 27 | microwedge_rotate: false, 28 | inverted_modter_rotate: false, 29 | inverted_wedge_rotate: false, 30 | } 31 | } 32 | 33 | pub fn size(mut self, size: (u32, u32, u32)) -> Self { 34 | self.size = size; 35 | self 36 | } 37 | 38 | pub fn offset(mut self, offset: (i32, i32, i32)) -> Self { 39 | self.offset = offset; 40 | self 41 | } 42 | 43 | pub fn rotation_offset(mut self, rotation: u8) -> Self { 44 | self.rotation_offset = rotation; 45 | self 46 | } 47 | 48 | pub fn color_override(mut self, color_override: brs::Color) -> Self { 49 | self.color_override = Some(color_override); 50 | self 51 | } 52 | 53 | pub fn direction_override(mut self, direction_override: brs::Direction) -> Self { 54 | self.direction_override = Some(direction_override); 55 | self 56 | } 57 | 58 | pub fn non_priority(mut self, non_priority: bool) -> Self { 59 | self.non_priority = non_priority; 60 | self 61 | } 62 | 63 | pub fn microwedge_rotate(mut self, microwedge_rotate: bool) -> Self { 64 | self.microwedge_rotate = microwedge_rotate; 65 | self 66 | } 67 | 68 | pub fn inverted_modter_rotate(mut self, inverted_modter_rotate: bool) -> Self { 69 | self.inverted_modter_rotate = inverted_modter_rotate; 70 | self 71 | } 72 | 73 | pub fn inverted_wedge_rotate(mut self, inverted_wedge_rotate: bool) -> Self { 74 | self.inverted_wedge_rotate = inverted_wedge_rotate; 75 | self 76 | } 77 | } 78 | 79 | impl From for BrickMapping { 80 | fn from(desc: BrickDesc) -> Self { 81 | vec![desc] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use bls2brs::{bl_save, brs, convert}; 2 | use std::{ 3 | fs::File, 4 | ffi::OsStr, 5 | io::BufReader, 6 | path::{Path, PathBuf}, 7 | }; 8 | 9 | fn main() { 10 | eprintln!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); 11 | eprintln!(); 12 | 13 | if let Err(e) = run() { 14 | eprintln!("{}", e); 15 | eprintln!(); 16 | wexit::prompt_enter_to_exit(1); 17 | } 18 | 19 | eprintln!(); 20 | wexit::prompt_enter_to_exit(0); 21 | } 22 | 23 | fn run() -> Result<(), String> { 24 | let args = parse_args() 25 | .map_err(|_| String::from("Error: No bls files given. Drag them onto this program's executable file. (Not this window! This is just an error message, not the program itself.)"))?; 26 | 27 | for (i, input_path) in args.input_paths.iter().enumerate() { 28 | if i > 0 { 29 | println!(); 30 | } 31 | 32 | let input_path = PathBuf::from(input_path); 33 | 34 | println!("Converting {}", input_path.display()); 35 | 36 | if input_path.extension() != Some(OsStr::new("bls")) { 37 | println!("Extension is not .bls, skipping"); 38 | continue; 39 | } 40 | 41 | let mut output_path = input_path.clone(); 42 | 43 | output_path.set_extension("brs"); 44 | 45 | convert_one(&input_path, &output_path) 46 | .map_err(|e| format!("Error converting {}: {}", input_path.display(), e))?; 47 | } 48 | 49 | Ok(()) 50 | } 51 | 52 | fn convert_one(input_path: impl AsRef, output_path: impl AsRef) -> Result<(), String> { 53 | let input_path = input_path.as_ref(); 54 | let output_path = output_path.as_ref(); 55 | 56 | let input_file = errmsg(File::open(input_path), "Failed to open bls file")?; 57 | let input_file = BufReader::new(input_file); 58 | let input_reader = errmsg(bl_save::Reader::new(input_file), "Failed to read bls file")?; 59 | 60 | let mut converted = errmsg(convert(input_reader), "Failed to convert bls file")?; 61 | 62 | if let Some(file_name) = input_path.file_name() { 63 | let mut prefix = format!( 64 | "Converted from {} with bls2brs.", 65 | file_name.to_string_lossy() 66 | ); 67 | 68 | if !converted.write_data.description.is_empty() { 69 | prefix.push('\n'); 70 | } 71 | 72 | converted.write_data.description.insert_str(0, &prefix); 73 | } 74 | 75 | if !converted.unknown_ui_names.is_empty() { 76 | println!("Unknown bricks:"); 77 | let mut ui_names: Vec<_> = converted.unknown_ui_names.into_iter().collect(); 78 | ui_names.sort_by(|(_, ac), (_, bc)| ac.cmp(bc).reverse()); 79 | for (ui_name, count) in ui_names { 80 | let ui_name = if ui_name != ui_name.trim() { 81 | format!("{:?}", ui_name) 82 | } else { 83 | ui_name 84 | }; 85 | println!(" {:<28} {:>4} bricks", ui_name, count); 86 | } 87 | } 88 | 89 | if converted.count_failure > 0 { 90 | println!("{} bricks failed to convert", converted.count_failure); 91 | } 92 | 93 | println!( 94 | "{} of {} bricks converted successfully to {} bricks", 95 | converted.count_success, 96 | converted.count_success + converted.count_failure, 97 | converted.write_data.bricks.len(), 98 | ); 99 | 100 | let mut output_file = errmsg(File::create(output_path), "Failed to create BRS file")?; 101 | 102 | errmsg( 103 | brs::write_save(&mut output_file, &converted.write_data), 104 | "Failed to write BRS file", 105 | )?; 106 | 107 | Ok(()) 108 | } 109 | 110 | struct Args { 111 | input_paths: Vec, 112 | } 113 | 114 | fn parse_args() -> Result { 115 | let mut args = std::env::args(); 116 | args.next().unwrap(); 117 | 118 | let input_paths: Vec<_> = args.collect(); 119 | 120 | if input_paths.is_empty() { 121 | return Err(()); 122 | } 123 | 124 | Ok(Args { input_paths }) 125 | } 126 | 127 | fn errmsg(r: Result, message_prefix: &str) -> Result { 128 | r.map_err(|e| format!("{}: {}", message_prefix, e)) 129 | } 130 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use brs::{chrono::prelude::*, uuid::Uuid}; 2 | use std::{ 3 | collections::HashMap, 4 | convert::TryInto, 5 | io::{self, prelude::*}, 6 | ops::Neg, 7 | }; 8 | 9 | pub use bl_save; 10 | pub use brs; 11 | 12 | mod types; 13 | #[macro_use] 14 | mod misc; 15 | mod mappings; 16 | 17 | use mappings::{BRICK_MAP_LITERAL, BRICK_MAP_REGEX}; 18 | use types::{BrickDesc, BrickMapping}; 19 | 20 | // Keep this in sync. Would be nice to just determine the indices at compile time. 21 | const FIXED_MATERIAL_TABLE: &[&str] = &["BMC_Plastic", "BMC_Glow", "BMC_Metallic"]; 22 | const BMC_PLASTIC: usize = 0; 23 | const BMC_GLOW: usize = 1; 24 | const BMC_METALLIC: usize = 2; 25 | 26 | const BRICK_OWNER: usize = 0; 27 | 28 | pub struct ConvertReport { 29 | pub write_data: brs::WriteData, 30 | pub unknown_ui_names: HashMap, 31 | pub count_success: usize, 32 | pub count_failure: usize, 33 | } 34 | 35 | pub fn convert(reader: bl_save::Reader) -> io::Result { 36 | let data = brs::WriteData { 37 | map: String::from("Unknown"), 38 | author: brs::User { 39 | id: Uuid::nil(), 40 | name: String::from("Unknown"), 41 | }, 42 | description: reader.description().to_string(), 43 | save_time: Utc::now(), 44 | mods: vec![], 45 | brick_assets: vec![], 46 | colors: reader.colors().iter().map(|c| map_color(*c)).collect(), 47 | materials: FIXED_MATERIAL_TABLE 48 | .iter() 49 | .map(|s| String::from(*s)) 50 | .collect(), 51 | brick_owners: vec![brs::User { 52 | id: Uuid::from_bytes([u8::max_value(); 16]), 53 | name: String::from("PUBLIC"), 54 | }], 55 | bricks: Vec::with_capacity(reader.brick_count().unwrap_or(100).min(10_000_000)), 56 | }; 57 | 58 | let mut converter = Converter { 59 | write_data: data, 60 | asset_map: HashMap::new(), 61 | unknown_ui_names: HashMap::new(), 62 | }; 63 | 64 | let mut count_success = 0; 65 | let mut count_failure = 0; 66 | 67 | let mut non_prio = Vec::new(); 68 | 69 | for from in reader { 70 | let from = from?; 71 | let option = converter.map_brick(&from); 72 | 73 | let mappings = match option { 74 | Some(mappings) => { 75 | count_success += 1; 76 | mappings 77 | } 78 | None => { 79 | count_failure += 1; 80 | continue; 81 | } 82 | }; 83 | 84 | for BrickDesc { 85 | asset, 86 | mut size, 87 | offset, 88 | rotation_offset, 89 | color_override, 90 | mut direction_override, 91 | non_priority, 92 | microwedge_rotate, 93 | inverted_modter_rotate, 94 | inverted_wedge_rotate, 95 | } in mappings 96 | { 97 | let asset_name_index = converter.asset(asset); 98 | let mut rotation = (from.base.angle + rotation_offset) % 4; 99 | 100 | let rotated_xy = rotate_offset((offset.0, offset.1), from.base.angle); 101 | let offset = (rotated_xy.0, rotated_xy.1, offset.2); 102 | 103 | let position = ( 104 | (from.base.position.1 * 20.0) as i32 + offset.0, 105 | (from.base.position.0 * 20.0) as i32 + offset.1, 106 | (from.base.position.2 * 20.0) as i32 + offset.2, 107 | ); 108 | 109 | let material_index = match from.base.color_fx { 110 | 3 => BMC_GLOW, 111 | 1 | 2 => BMC_METALLIC, 112 | _ => BMC_PLASTIC, 113 | }; 114 | 115 | let color_index = match color_override { 116 | Some(color) => converter.color(color) as u32, 117 | None => u32::from(from.base.color_index), 118 | }; 119 | 120 | // convert a vertical slope to microwedge 121 | if microwedge_rotate { 122 | let original_dir = direction_override; 123 | let (x, y, z) = size; 124 | if rotation == 0 || rotation == 2 { 125 | direction_override = Some(brs::Direction::YPositive); 126 | if rotation == 0 { 127 | size = (z, x, y); 128 | } else { 129 | size = (x, z, y); 130 | rotation = (rotation + 1) % 4; 131 | } 132 | } else { 133 | direction_override = Some(brs::Direction::XPositive); 134 | if rotation == 1 { 135 | size = (x, z, y); 136 | rotation = (rotation + 2) % 4; 137 | } else { 138 | size = (z, x, y); 139 | rotation = (rotation + 1) % 4; 140 | } 141 | } 142 | if original_dir.is_some() && original_dir.unwrap() == brs::Direction::ZNegative { 143 | rotation = (rotation + 2) % 4; 144 | } 145 | } 146 | 147 | // fix odd rotation offsets on inverted ModTer, wedges 148 | if (inverted_modter_rotate && (rotation == 1 || rotation == 3)) || 149 | (inverted_wedge_rotate && (rotation == 0 || rotation == 2)) { 150 | rotation = (rotation + 2) % 4; 151 | } 152 | 153 | let brick = brs::Brick { 154 | asset_name_index: asset_name_index as u32, 155 | size, 156 | position, 157 | direction: direction_override.unwrap_or(brs::Direction::ZPositive), 158 | rotation: rotation.try_into().unwrap(), 159 | collision: from.base.collision, 160 | visibility: from.base.rendering, 161 | material_index: material_index as u32, 162 | color: brs::ColorMode::Set(color_index), 163 | owner_index: BRICK_OWNER as u32, 164 | }; 165 | 166 | if non_priority { 167 | non_prio.push(brick); 168 | } else { 169 | converter.write_data.bricks.push(brick); 170 | } 171 | } 172 | } 173 | 174 | converter.write_data.bricks.append(&mut non_prio); 175 | 176 | Ok(ConvertReport { 177 | write_data: converter.write_data, 178 | unknown_ui_names: converter.unknown_ui_names, 179 | count_success, 180 | count_failure, 181 | }) 182 | } 183 | 184 | struct Converter { 185 | write_data: brs::WriteData, 186 | asset_map: HashMap, 187 | unknown_ui_names: HashMap, 188 | } 189 | 190 | impl Converter { 191 | fn map_brick(&mut self, from: &bl_save::Brick) -> Option { 192 | let mapping = map_brick(from); 193 | 194 | if cfg!(debug_assertions) { 195 | println!("mapped '{}' to {:?}", from.base.ui_name, mapping); 196 | } 197 | 198 | if mapping.is_none() { 199 | *self 200 | .unknown_ui_names 201 | .entry(from.base.ui_name.clone()) 202 | .or_default() += 1; 203 | } 204 | 205 | mapping 206 | } 207 | 208 | fn asset(&mut self, asset_name: &str) -> usize { 209 | if let Some(index) = self.asset_map.get(asset_name) { 210 | return *index; 211 | } 212 | 213 | let index = self.write_data.brick_assets.len(); 214 | self.write_data.brick_assets.push(asset_name.to_string()); 215 | self.asset_map.insert(asset_name.to_string(), index); 216 | 217 | index 218 | } 219 | 220 | fn color(&mut self, color: brs::Color) -> usize { 221 | // TODO: Optimize lookup with a map 222 | for (index, other) in self.write_data.colors.iter().enumerate() { 223 | if *other == color { 224 | return index; 225 | } 226 | } 227 | 228 | let index = self.write_data.colors.len(); 229 | self.write_data.colors.push(color); 230 | index 231 | } 232 | } 233 | 234 | fn map_brick(from: &bl_save::Brick) -> Option { 235 | let ui_name = from.base.ui_name.as_str(); 236 | 237 | if let Some(mapping) = BRICK_MAP_LITERAL.get(ui_name) { 238 | return Some(mapping.clone()); 239 | } 240 | 241 | for (regex, func) in BRICK_MAP_REGEX.iter() { 242 | if let Some(captures) = regex.captures(ui_name) { 243 | return func(captures, from); 244 | } 245 | } 246 | 247 | None 248 | } 249 | 250 | fn map_color((r, g, b, a): (f32, f32, f32, f32)) -> brs::Color { 251 | // Convert into Unreal color space 252 | let r = gamma_expansion(r); 253 | let g = gamma_expansion(g); 254 | let b = gamma_expansion(b); 255 | let a = gamma_expansion(a); 256 | 257 | // Convert to 0-255 258 | let r = (r * 255.0).max(0.0).min(255.0) as u8; 259 | let g = (g * 255.0).max(0.0).min(255.0) as u8; 260 | let b = (b * 255.0).max(0.0).min(255.0) as u8; 261 | let a = (a * 255.0).max(0.0).min(255.0) as u8; 262 | 263 | brs::Color::from_rgba(r, g, b, a) 264 | } 265 | 266 | fn gamma_expansion(u: f32) -> f32 { 267 | if u <= 0.04045 { 268 | return u / 12.92; 269 | } 270 | let base = (u + 0.055) / 1.055; 271 | base.powf(2.4) 272 | } 273 | 274 | fn rotate_offset(mut offset: (i32, i32), angle: u8) -> (i32, i32) { 275 | for _ in 0..angle { 276 | offset = rotate_90_2d(offset); 277 | } 278 | offset 279 | } 280 | 281 | fn rotate_90_2d((x, y): (X, Y)) -> (::Output, X) { 282 | (-y, x) 283 | } 284 | -------------------------------------------------------------------------------- /src/mappings.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::identity_op)] 2 | 3 | use crate::types::{BrickDesc, BrickMapping}; 4 | use lazy_static::lazy_static; 5 | use regex::{Captures, Regex}; 6 | use std::collections::{HashMap, HashSet}; 7 | use brs::Direction::*; 8 | 9 | type RegexHandler = Box Option + Sync>; 10 | 11 | lazy_static! { 12 | static ref TILE_PRINTS: HashSet<&'static str> = vec![ 13 | "1x2f/blank", 14 | "2x2f/blank", 15 | ].into_iter().collect(); 16 | 17 | static ref BRICK_ROAD_LANE: BrickDesc = BrickDesc::new("PB_DefaultTile") 18 | .color_override(brs::Color::from_rgba(51, 51, 51, 255)); 19 | static ref BRICK_ROAD_STRIPE: BrickDesc = BrickDesc::new("PB_DefaultTile") 20 | .color_override(brs::Color::from_rgba(254, 254, 232, 255)); 21 | static ref GENERIC_DOOR: BrickMapping = vec![ 22 | //frame 23 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 5, 1)).offset((0, 0, -35)), 24 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 5, 1)).offset((0, 0, 35)), 25 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 5, 34)).offset((0, 19, 0)), 26 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 5, 34)).offset((0, -19, 0)), 27 | //door 28 | BrickDesc::new("PB_DefaultMicroBrick").size((18, 1, 34)), 29 | //handle 30 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 1, 3)).offset((2, 12, 0)), 31 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 1, 3)).offset((-2, 12, 0)), 32 | ]; 33 | static ref PRINT_4X4F_ROUND: BrickMapping = vec![ 34 | BrickDesc::new("PB_DefaultMicroBrick").size((18, 10, 2)), 35 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((14, 14, 0)).rotation_offset(0), 36 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((14, -14, 0)).rotation_offset(3), 37 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((-14, -14, 0)).rotation_offset(2), 38 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((-14, 14, 0)).rotation_offset(1), 39 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 4, 2)).offset((14, 0, 0)), 40 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 4, 2)).offset((-14, 0, 0)), 41 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((-5, 19, 0)), 42 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((5, 19, 0)).rotation_offset(0), 43 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((5, -19, 0)).rotation_offset(3), 44 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((-5, -19, 0)).rotation_offset(2), 45 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((19, -5, 0)).rotation_offset(3), 46 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((19, 5, 0)).rotation_offset(0), 47 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((-19, 5, 0)).rotation_offset(1), 48 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((-19, -5, 0)).rotation_offset(2), 49 | ]; 50 | 51 | pub static ref BRICK_MAP_LITERAL: HashMap<&'static str, BrickMapping> = brick_map_literal![ 52 | // # Correct mappings 53 | 54 | "1x1 Cone" => BrickDesc::new("B_1x1_Cone"), 55 | "2x2x2 Cone" => BrickDesc::new("B_2x2_Cone"), 56 | "1x1 cone Inv" => BrickDesc::new("B_1x1_Cone").direction_override(ZNegative), // 1RandomBrickPack 57 | "2x2x2 cone Inv" => BrickDesc::new("B_2x2_Cone").direction_override(ZNegative), // 1RBP 58 | "1x1 Round" => BrickDesc::new("B_1x1_Round"), 59 | "1x1 Octo Plate" => BrickDesc::new("B_1x1F_Octo"), 60 | "1x1F Round" => BrickDesc::new("B_1x1F_Round"), 61 | "2x2 Round" => BrickDesc::new("B_2x2_Round"), 62 | "2x2F Round" => BrickDesc::new("B_2x2F_Round"), 63 | "Pine Tree" => BrickDesc::new("B_Pine_Tree").offset((0, 0, -6)), 64 | "2x2 Corner" => BrickDesc::new("B_2x2_Corner").rotation_offset(0), 65 | "2x2 Octo Plate" => BrickDesc::new("B_2x2F_Octo"), 66 | "8x8 Grill" => BrickDesc::new("B_8x8_Lattice_Plate"), 67 | "1x4x2 Picket" => BrickDesc::new("B_Picket_Fence"), 68 | 69 | // 1RandomBrickPack 45° to 25° Ramp Adapters 70 | "45° 25° Adapter A" => BrickDesc::new("PB_DefaultRampInnerCorner").size((15, 10, 6)).rotation_offset(0), 71 | "45° 25° Adapter B" => BrickDesc::new("PB_DefaultRampInnerCorner").size((10, 15, 6)).rotation_offset(1), 72 | "45° 25° Adapter C" => BrickDesc::new("PB_DefaultRampCorner").size((15, 10, 6)).rotation_offset(0), 73 | "45° 25° Adapter D" => BrickDesc::new("PB_DefaultRampCorner").size((10, 15, 6)).rotation_offset(1), 74 | "-45°-25° Inv Adapter B" => BrickDesc::new("PB_DefaultRampInnerCorner").size((15, 10, 6)).rotation_offset(0).direction_override(ZNegative), 75 | "-45°-25° Inv Adapter A" => BrickDesc::new("PB_DefaultRampInnerCorner").size((10, 15, 6)).rotation_offset(1).direction_override(ZNegative), 76 | "-45° -25° Inv Adapter D" => BrickDesc::new("PB_DefaultRampCorner").size((15, 10, 6)).rotation_offset(0).direction_override(ZNegative), 77 | "-45° -25° Inv Adapter C" => BrickDesc::new("PB_DefaultRampCorner").size((10, 15, 6)).rotation_offset(1).direction_override(ZNegative), 78 | // 1RandomBrickPack Long Wedges 79 | "16.7° 1x2 Ramp" => BrickDesc::new("PB_DefaultWedge").size((10, 5, 4)).rotation_offset(0), 80 | "11.31° 1x3 Ramp" => BrickDesc::new("PB_DefaultWedge").size((15, 5, 4)).rotation_offset(0), 81 | "8.53° 1x4 Ramp" => BrickDesc::new("PB_DefaultWedge").size((20, 5, 4)).rotation_offset(0), 82 | // 1RandomBrickPack Correct Octo Mappings 83 | "2x2x2 Octo Elbow" => BrickDesc::new("B_2x_Octo_90Deg"), 84 | "2x2x2 Octo - Elbow" => BrickDesc::new("B_2x_Octo_90Deg").direction_override(ZNegative).rotation_offset(3), 85 | "2x2x2 Octo T Vert" => BrickDesc::new("B_2x_Octo_T"), 86 | "2x2x2 Octo Elbow Horz" => BrickDesc::new("B_2x_Octo_90Deg").direction_override(XPositive), 87 | "2x2x2 Octo T Horz" => BrickDesc::new("B_2x_Octo_T").direction_override(YNegative), 88 | "2x2x2 Octo T" => BrickDesc::new("B_2x_Octo_T").direction_override(YNegative).rotation_offset(2), 89 | "2x2x2 Octo T inv" => BrickDesc::new("B_2x_Octo_T").direction_override(YNegative).rotation_offset(0), 90 | "1x2 Octo Plate90" => BrickDesc::new("B_2x2F_Octo").direction_override(YNegative).offset((3, 0, 0)), 91 | "2x2 Octo Brick90" => BrickDesc::new("B_2x_Octo").direction_override(YNegative), 92 | 93 | // # Approximate mappings 94 | 95 | "2x2 Disc" => BrickDesc::new("B_2x2F_Round"), 96 | "2x2 disc Inv" => BrickDesc::new("B_2x2F_Round"), // 1RBP 97 | "Music Brick" => BrickDesc::new("PB_DefaultBrick").size((5, 5, 6)), 98 | "1x4x2 Fence" => BrickDesc::new("PB_DefaultBrick").size((5, 4*5, 2*6)).rotation_offset(0), 99 | "2x2x1 Octo Cone" => BrickDesc::new("B_2x2_Round"), 100 | "Gravestone" => BrickDesc::new("B_Gravestone"), 101 | "House Door" => GENERIC_DOOR.clone(), 102 | "Plain Door" => GENERIC_DOOR.clone(), 103 | 104 | "2x2 Octo" => vec![ 105 | BrickDesc::new("B_2x2F_Octo").offset((0, 0, -4)), 106 | BrickDesc::new("B_2x2F_Octo"), 107 | BrickDesc::new("B_2x2F_Octo").offset((0, 0, 4)), 108 | ], 109 | 110 | "Castle Wall" => vec![ 111 | BrickDesc::new("PB_DefaultTile").size((5, 5, 6*6)).offset((0, -10, 0)), 112 | BrickDesc::new("PB_DefaultTile").size((5, 5, 6*6)).offset((0, 10, 0)), 113 | BrickDesc::new("PB_DefaultTile").size((5, 5, 3*6)).offset((0, 0, -9*2)), 114 | BrickDesc::new("PB_DefaultTile").size((5, 5, 4*2)).offset((0, 0, 14*2)), 115 | ], 116 | 117 | "1x4x5 Window" => vec![ 118 | BrickDesc::new("PB_DefaultBrick").size((5, 4*5, 2)).rotation_offset(0).offset((0, 0, -14*2)), 119 | BrickDesc::new("PB_DefaultTile").size((5, 4*5, 5*6-2)).rotation_offset(0).offset((0, 0, 2)) 120 | .color_override(brs::Color::from_rgba(255, 255, 255, 76)), 121 | ], 122 | 123 | "1x4x2 Bars" => vec![ 124 | BrickDesc::new("PB_DefaultMicroBrick").size((5, 20, 2)).offset((0, 0, -10)), 125 | BrickDesc::new("PB_DefaultMicroBrick").size((5, 20, 1)).offset((0, 0, 11)), 126 | BrickDesc::new("PB_DefaultPole").size((3, 3, 1)).offset((-15, 0, -7)), 127 | BrickDesc::new("PB_DefaultPole").size((3, 3, 1)).offset((-5, 0, -7)), 128 | BrickDesc::new("PB_DefaultPole").size((3, 3, 1)).offset((5, 0, -7)), 129 | BrickDesc::new("PB_DefaultPole").size((3, 3, 1)).offset((15, 0, -7)), 130 | BrickDesc::new("PB_DefaultPole").size((2, 2, 8)).offset((15, 0, 2)), 131 | BrickDesc::new("PB_DefaultPole").size((2, 2, 8)).offset((5, 0, 2)), 132 | BrickDesc::new("PB_DefaultPole").size((2, 2, 8)).offset((-5, 0, 2)), 133 | BrickDesc::new("PB_DefaultPole").size((2, 2, 8)).offset((-15, 0, 2)), 134 | ], 135 | 136 | "Treasure Chest" => vec![ 137 | // Body 138 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 10, 2)).offset((0, 0, -8)), 139 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 2)).offset((8, 18, -4)), 140 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 2)).offset((8, -18, -4)), 141 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 2)).offset((-8, 18, -4)), 142 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 2)).offset((-8, -18, -4)), 143 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 2, 2)).offset((8, 0, -4)), 144 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 2, 2)).offset((-8, 0, -4)), 145 | BrickDesc::new("PB_DefaultMicroBrick").size((19, 6, 2)).offset((0, 0, -4)), 146 | BrickDesc::new("PB_DefaultMicroBrick").size((6, 1, 2)).offset((8, 10, -4)), 147 | BrickDesc::new("PB_DefaultMicroBrick").size((6, 1, 2)).offset((8, -10, -4)), 148 | BrickDesc::new("PB_DefaultMicroBrick").size((6, 1, 2)).offset((-8, 10, -4)), 149 | BrickDesc::new("PB_DefaultMicroBrick").size((6, 1, 2)).offset((-8, -10, -4)), 150 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 10, 4)).offset((0, 0, 2)), 151 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 6, 2)).offset((0, 0, 8)), 152 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 20, 2)).offset((-8, 0, 8)).microwedge_rotate(true).rotation_offset(2), 153 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 20, 2)).offset((8, 0, 8)).microwedge_rotate(true).rotation_offset(0), 154 | // Lock 155 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 1, 2)).offset((-11, 0, 2)).non_priority(true) 156 | .color_override(brs::Color::from_rgba(255, 255, 0, 255)), 157 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 1, 1)).offset((-11, 0, -1)).non_priority(true) 158 | .color_override(brs::Color::from_rgba(255, 255, 0, 255)), 159 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 1)).offset((-11, 3, -1)).non_priority(true).microwedge_rotate(true) 160 | .color_override(brs::Color::from_rgba(255, 255, 0, 255)).rotation_offset(3) 161 | .direction_override(ZNegative), 162 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 1)).offset((-11, -3, -1)).non_priority(true).microwedge_rotate(true) 163 | .color_override(brs::Color::from_rgba(255, 255, 0, 255)).rotation_offset(1) 164 | .direction_override(ZNegative), 165 | ], 166 | 167 | "32x32 Road" => vec![ 168 | // left and right sidewalks 169 | BrickDesc::new("PB_DefaultBrick").size((9*5, 32*5, 2)).offset((0, -115, 0)), 170 | BrickDesc::new("PB_DefaultBrick").size((9*5, 32*5, 2)).offset((0, 115, 0)), 171 | // left and right stripes 172 | BRICK_ROAD_STRIPE.clone().size((1*5, 32*5, 2)).offset((0, -65, 0)), 173 | BRICK_ROAD_STRIPE.clone().size((1*5, 32*5, 2)).offset((0, 65, 0)), 174 | // lanes 175 | BRICK_ROAD_LANE.clone().size((6*5, 32*5, 2)).offset((0, -6*5, 0)), 176 | BRICK_ROAD_LANE.clone().size((6*5, 32*5, 2)).offset((0, 6*5, 0)), 177 | ], 178 | 179 | // Orientations are relative to this camera position on Beta City: 180 | // 39.5712 0.0598862 14.5026 0.999998 -0.0007625 0.00180403 0.799784 181 | "32x32 Road T" => vec![ 182 | BrickDesc::new("PB_DefaultBrick").size((9*5, 32*5, 2)).offset((0, -115, 0)), // top 183 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((-115, 115, 0)), // bottom left 184 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((115, 115, 0)), // bottom right 185 | BRICK_ROAD_STRIPE.clone().size((1*5, 32*5, 2)).offset((0, -65, 0)), // straight top 186 | BRICK_ROAD_STRIPE.clone().size((1*5, 32*5, 2)).offset((0, 65, 0)), // straight bottom 187 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((-13*5, 23*5, 0)), // bottom left 188 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((13*5, 23*5, 0)), // bottom right 189 | BRICK_ROAD_LANE.clone().size((6*5, 32*5, 2)).offset((0, -6*5, 0)), // straight top 190 | BRICK_ROAD_LANE.clone().size((6*5, 32*5, 2)).offset((0, 6*5, 0)), // straight bottom 191 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((-6*5, 23*5, 0)), // bottom left 192 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((6*5, 23*5, 0)), // bottom right 193 | ], 194 | 195 | // Orientations are relative to this camera position on Beta City: 196 | // -56.5 -35 4 0 0 1 3.14159 197 | "32x32 Road X" => vec![ 198 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((-23*5, -23*5, 0)), // top left 199 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((23*5, -23*5, 0)), // top right 200 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((-23*5, 23*5, 0)), // bottom left 201 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((23*5, 23*5, 0)), // bottom right 202 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).offset((13*5, -13*5, 0)), // corner top left 203 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).offset((13*5, 13*5, 0)), // corner right right 204 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).offset((-13*5, -13*5, 0)), // corner bottom left 205 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).offset((-13*5, 13*5, 0)), // corner bottom right 206 | BRICK_ROAD_STRIPE.clone().size((1*5, 12*5, 2)).rotation_offset(0).offset((-13*5, 0, 0)), // inner bottom 207 | BRICK_ROAD_STRIPE.clone().size((1*5, 12*5, 2)).rotation_offset(0).offset((13*5, 0, 0)), // inner top 208 | BRICK_ROAD_STRIPE.clone().size((1*5, 12*5, 2)).offset((0, -13*5, 0)), // inner left 209 | BRICK_ROAD_STRIPE.clone().size((1*5, 12*5, 2)).offset((0, 13*5, 0)), // inner right 210 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((-13*5, 23*5, 0)), // right bottom 211 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((13*5, 23*5, 0)), // right top 212 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((-13*5, -23*5, 0)), // left bottom 213 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((13*5, -23*5, 0)), // left top 214 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).offset((-23*5, -13*5, 0)), // bottom left 215 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).offset((-23*5, 13*5, 0)), // bottom right 216 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).offset((23*5, -13*5, 0)), // top left 217 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).offset((23*5, 13*5, 0)), // top right 218 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((-6*5, -6*5, 0)), // inner bottom left 219 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((-6*5, 6*5, 0)), // inner bottom right 220 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((6*5, -6*5, 0)), // inner top left 221 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((6*5, 6*5, 0)), // inner top right 222 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((-6*5, 23*5, 0)), // right bottom 223 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((6*5, 23*5, 0)), // right top 224 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((-6*5, -23*5, 0)), // left bottom 225 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).rotation_offset(0).offset((6*5, -23*5, 0)), // left top 226 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).offset((-23*5, -6*5, 0)), // bottom left 227 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).offset((-23*5, 6*5, 0)), // bottom right 228 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).offset((23*5, -6*5, 0)), // top left 229 | BRICK_ROAD_LANE.clone().size((6*5, 9*5, 2)).offset((23*5, 6*5, 0)), // top right 230 | ], 231 | 232 | // Orientations are relative to this camera position on Beta City: 233 | // -25.9168 -110.523 12.5993 0.996034 0.0289472 -0.0841301 0.665224 234 | "32x32 Road C" => vec![ 235 | // sidewalks 236 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((-115, 115, 0)), // top left 237 | BrickDesc::new("PB_DefaultBrick").size((9*5, 9*5, 2)).offset((115, -115, 0)), // bottom right 238 | BrickDesc::new("PB_DefaultBrick").size((9*5, 23*5, 2)).rotation_offset(0).offset((115, 45, 0)), // bottom left 239 | BrickDesc::new("PB_DefaultBrick").size((9*5, 23*5, 2)).offset((-45, -115, 0)), // top right 240 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).offset((-115, 65, 0)), // inner right 241 | BRICK_ROAD_STRIPE.clone().size((1*5, 9*5, 2)).rotation_offset(0).offset((-65, 115, 0)), // inner bottom 242 | BRICK_ROAD_STRIPE.clone().size((1*5, 22*5, 2)).offset((-50, -65, 0)), // top right 243 | BRICK_ROAD_STRIPE.clone().size((1*5, 22*5, 2)).rotation_offset(0).offset((65, 50, 0)), // bottom left 244 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).offset((65, -65, 0)), // bottom right 245 | BRICK_ROAD_STRIPE.clone().size((1*5, 1*5, 2)).rotation_offset(0).offset((-65, 65, 0)), // inner bottom right 246 | BRICK_ROAD_LANE.clone().size((6*5, 10*5, 2)).offset((-22*5, 6*5, 0)), // top left 247 | BRICK_ROAD_LANE.clone().size((6*5, 16*5, 2)).offset((-16*5, -6*5, 0)), // top right 248 | BRICK_ROAD_LANE.clone().size((6*5, 16*5, 2)).rotation_offset(0).offset((6*5, 16*5, 0)), // bottom left 249 | BRICK_ROAD_LANE.clone().size((6*5, 10*5, 2)).rotation_offset(0).offset((-6*5, 22*5, 0)), // left top 250 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((-6*5, 6*5, 0)), // inner top left 251 | BRICK_ROAD_LANE.clone().size((6*5, 6*5, 2)).offset((6*5, -6*5, 0)), // inner bottom right 252 | ], 253 | 254 | // 1RandomBrickPack 255 | "2x2f Print 90" => BrickDesc::new("PB_DefaultSmoothTile").size((10, 10, 2)).offset((3, 0, 0)).direction_override(YPositive), 256 | "2x2f Round Ceiling" => BrickDesc::new("PB_DefaultPole").size((10, 10, 2)), 257 | "2x2f Round Print 90" => BrickDesc::new("PB_DefaultPole").size((10, 10, 2)).offset((3, 0, 0)).direction_override(YNegative), 258 | "2x2x2Undercarriage" => vec![ 259 | BrickDesc::new("PB_DefaultPole").size((10, 10, 2)).offset((0, 0, -10)), 260 | BrickDesc::new("PB_DefaultPole").size((4 , 4, 4)).offset((0, 0, -4)), 261 | BrickDesc::new("PB_DefaultPole").size((2, 2, 5)).offset((0, 0, 5)), 262 | BrickDesc::new("PB_DefaultPole").size((10, 10, 1)).offset((0, 0, 11)), 263 | ], 264 | "4x4f Round" => vec![ 265 | BrickDesc::new("PB_DefaultBrick").size((10, 10, 2)), 266 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((14, 14, 0)).rotation_offset(0), 267 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((14, -14, 0)).rotation_offset(3), 268 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((-14, -14, 0)).rotation_offset(2), 269 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((-14, 14, 0)).rotation_offset(1), 270 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 10, 2)).offset((0, 14, 0)), 271 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 10, 2)).offset((0, -14, 0)), 272 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 4, 2)).offset((14, 0, 0)), 273 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 4, 2)).offset((-14, 0, 0)), 274 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((-5, 19, 0)), 275 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((5, 19, 0)).rotation_offset(0), 276 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((5, -19, 0)).rotation_offset(3), 277 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((-5, -19, 0)).rotation_offset(2), 278 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((19, -5, 0)).rotation_offset(3), 279 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((19, 5, 0)).rotation_offset(0), 280 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((-19, 5, 0)).rotation_offset(1), 281 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((-19, -5, 0)).rotation_offset(2), 282 | ], 283 | "4x4f Round Print" => PRINT_4X4F_ROUND.clone(), 284 | "4x4f Round Print Ceiling" => PRINT_4X4F_ROUND.clone(), 285 | "6x6f Round" => vec![ 286 | BrickDesc::new("PB_DefaultBrick").size((20, 20, 2)), 287 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 14, 2)).offset((0, 23, 0)), 288 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 14, 2)).offset((0, -23, 0)), 289 | BrickDesc::new("PB_DefaultMicroBrick").size((14, 3, 2)).offset((23, 0, 0)), 290 | BrickDesc::new("PB_DefaultMicroBrick").size((14, 3, 2)).offset((-23, 0, 0)), 291 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((23, 17, 0)).rotation_offset(0), 292 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((17, 23, 0)).rotation_offset(0), 293 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((23, -17, 0)).rotation_offset(3), 294 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((17, -23, 0)).rotation_offset(3), 295 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((-23, -17, 0)).rotation_offset(2), 296 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((-17, -23, 0)).rotation_offset(2), 297 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((-23, 17, 0)).rotation_offset(1), 298 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((-17, 23, 0)).rotation_offset(1), 299 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((-7, 28, 0)), 300 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((7, 28, 0)).rotation_offset(0), 301 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((7, -28, 0)).rotation_offset(3), 302 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((-7, -28, 0)).rotation_offset(2), 303 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((28, -7, 0)).rotation_offset(3), 304 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((28, 7, 0)).rotation_offset(0), 305 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((-28, 7, 0)).rotation_offset(1), 306 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((-28, -7, 0)).rotation_offset(2), 307 | ], 308 | // Lazy half-round mappings 309 | "1x1 half-round 90" => BrickDesc::new("PB_DefaultMicroWedge").size((5, 5, 6)).microwedge_rotate(true).rotation_offset(0), 310 | "2x1 half-round 90" => BrickDesc::new("PB_DefaultMicroWedge").size((5, 10, 6)).microwedge_rotate(true).rotation_offset(0), 311 | "4x1 half-round 90" => BrickDesc::new("PB_DefaultMicroWedge").size((5, 20, 6)).microwedge_rotate(true).rotation_offset(0), 312 | "1x2 half-round 90" => vec![ 313 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 5, 6)).microwedge_rotate(true).rotation_offset(0).offset((5, 0, 0)), 314 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 5, 6)).microwedge_rotate(true).rotation_offset(2).offset((-5, 0, 0)) 315 | ], 316 | "2x2 half-round 90" => vec![ 317 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 10, 6)).microwedge_rotate(true).rotation_offset(0).offset((5, 0, 0)), 318 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 10, 6)).microwedge_rotate(true).rotation_offset(2).offset((-5, 0, 0)) 319 | ], 320 | "4x2 half-round 90" => vec![ 321 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 20, 6)).microwedge_rotate(true).rotation_offset(0).offset((5, 0, 0)), 322 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 20, 6)).microwedge_rotate(true).rotation_offset(2).offset((-5, 0, 0)) 323 | ], 324 | "1x1f half-round" => vec![ 325 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 2)).offset((4, -3, 0)).rotation_offset(0), 326 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 2)).offset((1, 1, 0)).rotation_offset(0), 327 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 2)).offset((-3, 4, 0)).rotation_offset(0), 328 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 4, 2)).offset((-1, -3, 0)), 329 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 2)).offset((-3, 1, 0)) 330 | ], 331 | "1x1 half-round" => vec![ 332 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((4, -3, 0)).rotation_offset(0), 333 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 6)).offset((1, 1, 0)).rotation_offset(0), 334 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((-3, 4, 0)).rotation_offset(0), 335 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 4, 6)).offset((-1, -3, 0)), 336 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 2, 6)).offset((-3, 1, 0)) 337 | ], 338 | "1x2F Half-round" => vec![ 339 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 2)).offset((9, -3, 0)).rotation_offset(0), 340 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 2)).offset((6, 1, 0)).rotation_offset(0), 341 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 2)).offset((2, 4, 0)).rotation_offset(0), 342 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 2)).offset((-9, -3, 0)), 343 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 2)).offset((-6, 1, 0)), 344 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 2)).offset((-2, 4, 0)), 345 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 8, 2)).offset((0, -3, 0)), 346 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 4, 2)).offset((0, 1, 0)) 347 | ], 348 | "1x2 Half-round" => vec![ 349 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((9, -3, 0)).rotation_offset(0), 350 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 6)).offset((6, 1, 0)).rotation_offset(0), 351 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((2, 4, 0)).rotation_offset(0), 352 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((-9, -3, 0)), 353 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 2, 6)).offset((-6, 1, 0)), 354 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((-2, 4, 0)), 355 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 8, 6)).offset((0, -3, 0)), 356 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 4, 6)).offset((0, 1, 0)) 357 | ], 358 | "6x3F Half-round" => vec![ 359 | BrickDesc::new("PB_DefaultBrick").size((20, 10, 2)).offset((-5, 0, 0)), 360 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 7, 2)).offset((-8, 23, 0)), 361 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 7, 2)).offset((-8, -23, 0)), 362 | BrickDesc::new("PB_DefaultMicroBrick").size((14, 3, 2)).offset((8, 0, 0)), 363 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((8, 17, 0)).rotation_offset(0), 364 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((2, 23, 0)).rotation_offset(0), 365 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((8, -17, 0)).rotation_offset(3), 366 | BrickDesc::new("PB_DefaultMicroWedge").size((3, 3, 2)).offset((2, -23, 0)).rotation_offset(3), 367 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((-8, 28, 0)).rotation_offset(0), 368 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((-8, -28, 0)).rotation_offset(3), 369 | BrickDesc::new("PB_DefaultMicroWedge").size((7, 2, 2)).offset((13, -7, 0)).rotation_offset(3), 370 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 7, 2)).offset((13, 7, 0)).rotation_offset(0), 371 | ], 372 | "4x2F Half-round" => vec![ 373 | BrickDesc::new("PB_DefaultBrick").size((10, 5, 2)).offset((-5, 0, 0)), 374 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((4, 14, 0)).rotation_offset(0), 375 | BrickDesc::new("PB_DefaultMicroWedge").size((4, 4, 2)).offset((4, -14, 0)).rotation_offset(3), 376 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 5, 2)).offset((-5, 14, 0)), 377 | BrickDesc::new("PB_DefaultMicroBrick").size((4, 5, 2)).offset((-5, -14, 0)), 378 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 4, 2)).offset((4, 0, 0)), 379 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((-5, 19, 0)).rotation_offset(0), 380 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((-5, -19, 0)).rotation_offset(3), 381 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 2)).offset((9, -5, 0)).rotation_offset(3), 382 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 5, 2)).offset((9, 5, 0)).rotation_offset(0), 383 | ], 384 | 385 | // TODO: Revisit if Octo bricks become procedural 386 | "1x1 Octo" => vec![ 387 | BrickDesc::new("B_1x1F_Octo"), 388 | BrickDesc::new("B_1x1F_Octo").offset((0, 0, 4)), 389 | BrickDesc::new("B_1x1F_Octo").offset((0, 0, -4)), 390 | ], 391 | "1x1x2 Octo" => vec![ 392 | BrickDesc::new("B_1x_Octo").offset((0, 0, -7)), 393 | BrickDesc::new("B_1x_Octo").offset((0, 0, 7)), 394 | BrickDesc::new("B_1x1F_Octo") 395 | ], 396 | "2x2x2 Octo" => vec![ 397 | BrickDesc::new("B_2x_Octo").offset((0, 0, -2)), 398 | BrickDesc::new("B_2x2F_Octo").offset((0, 0, 10)) 399 | ], 400 | "2x2x2 Octo Cone Inv" => vec![ 401 | BrickDesc::new("B_2x_Octo_Cone").offset((0, 0, -2)).direction_override(ZNegative), 402 | BrickDesc::new("B_2x2F_Octo").offset((0, 0, 10)) 403 | ], 404 | "2x2x2 Octo Plus Vert" => BrickDesc::new("PB_DefaultStudded").size((10, 10, 10)), 405 | "2x2x2 Octo Plus Horz" => BrickDesc::new("PB_DefaultStudded").size((10, 10, 10)), 406 | "2x2x2 Octo Plus Plus" => BrickDesc::new("PB_DefaultStudded").size((10, 10, 10)), 407 | "1x2 Octo Brick90" => vec![ 408 | BrickDesc::new("B_2x2F_Octo").direction_override(YNegative).offset((3, 0, 0)), 409 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 1, 10)), // TODO: replace this filler with micros to look like an octo 410 | BrickDesc::new("B_2x2F_Octo").direction_override(YNegative).offset((-3, 0, 0)), 411 | ], 412 | "2x3x2 Octo Offset" => vec![ 413 | BrickDesc::new("B_2x2F_Octo").offset((0, -5, -10)), 414 | BrickDesc::new("B_2x2F_Octo").offset((0, -3, -6)), 415 | BrickDesc::new("B_2x2F_Octo").offset((0, -1, -2)), 416 | BrickDesc::new("B_2x2F_Octo").offset((0, 1, 2)), 417 | BrickDesc::new("B_2x2F_Octo").offset((0, 3, 6)), 418 | BrickDesc::new("B_2x2F_Octo").offset((0, 5, 10)), 419 | ], 420 | "2x2x1 Octo Cone Inv" => vec![ 421 | BrickDesc::new("B_2x2F_Octo").offset((0, 0, 4)), 422 | BrickDesc::new("B_1x1F_Octo").offset((0, 0, -4)), 423 | BrickDesc::new("PB_DefaultPole").size((7, 7, 2)) 424 | ], 425 | 426 | "45° Crest Plus" => vec![ 427 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 10, 1)).offset((0, 0, -5)), 428 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((5, 5, 1)).rotation_offset(0), 429 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((5, -5, 1)).rotation_offset(3), 430 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((-5, 5, 1)).rotation_offset(1), 431 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((-5, -5, 1)).rotation_offset(2), 432 | ], 433 | "25° Crest Plus" => vec![ 434 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 10, 1)).offset((0, 0, -5)), 435 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((5, 5, -1)).rotation_offset(0), 436 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((5, -5, -1)).rotation_offset(3), 437 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((-5, 5, -1)).rotation_offset(1), 438 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((-5, -5, -1)).rotation_offset(2), 439 | ], 440 | "45° Crest T" => vec![ 441 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 10, 1)).offset((0, 0, -5)), 442 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((5, 5, 1)).rotation_offset(0), 443 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 5)).offset((-5, 5, 1)).rotation_offset(1), 444 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 10, 5)).microwedge_rotate(true).offset((0, -5, 1)).rotation_offset(3), 445 | ], 446 | "25° Crest T" => vec![ 447 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 10, 1)).offset((0, 0, -5)), 448 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((5, 5, -1)).rotation_offset(0), 449 | BrickDesc::new("PB_DefaultMicroWedgeInnerCorner").size((5, 5, 3)).offset((-5, 5, -1)).rotation_offset(1), 450 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 10, 3)).microwedge_rotate(true).offset((0, -5, -1)).rotation_offset(3), 451 | ], 452 | 453 | "Antenna" => vec![ 454 | BrickDesc::new("B_1x1F_Round").offset((0, 0, -28)), 455 | BrickDesc::new("PB_DefaultPole").size((4, 4, 2)).offset((0, 0, -24)), 456 | BrickDesc::new("PB_DefaultPole").size((2, 2, 24)).offset((0, 0, 2)) 457 | ], 458 | "1x2Log" => vec![ 459 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 10, 6)), 460 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 1, 6)).offset((5, 4, 0)), 461 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 1, 6)).offset((5, -4, 0)), 462 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 1, 6)).offset((-5, 4, 0)), 463 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 1, 6)).offset((-5, -4, 0)), 464 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((8, 4, 0)).rotation_offset(0), 465 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((-2, 4, 0)).rotation_offset(0), 466 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((-8, -4, 0)).rotation_offset(2), 467 | BrickDesc::new("PB_DefaultMicroWedge").size((2, 1, 6)).offset((2, -4, 0)).rotation_offset(2), 468 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((-8, 4, 0)).rotation_offset(1), 469 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((2, 4, 0)).rotation_offset(1), 470 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((8, -4, 0)).rotation_offset(3), 471 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 2, 6)).offset((-2, -4, 0)).rotation_offset(3), 472 | ], 473 | "1x2 Ridged" => vec![ 474 | BrickDesc::new("PB_DefaultMicroBrick").size((3, 10, 6)), 475 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((9, -4, 0)).rotation_offset(2), 476 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((7, -4, 0)).rotation_offset(3), 477 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((5, -4, 0)).rotation_offset(2), 478 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((3, -4, 0)).rotation_offset(3), 479 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((1, -4, 0)).rotation_offset(2), 480 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((-1, -4, 0)).rotation_offset(3), 481 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((-3, -4, 0)).rotation_offset(2), 482 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((-5, -4, 0)).rotation_offset(3), 483 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((-7, -4, 0)).rotation_offset(2), 484 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 1, 6)).offset((-9, -4, 0)).rotation_offset(3), 485 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, -5)).rotation_offset(1).microwedge_rotate(true), 486 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, -3)).rotation_offset(3).microwedge_rotate(true).direction_override(ZNegative), 487 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, -1)).rotation_offset(1).microwedge_rotate(true), 488 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, 1)).rotation_offset(3).microwedge_rotate(true).direction_override(ZNegative), 489 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, 3)).rotation_offset(1).microwedge_rotate(true), 490 | BrickDesc::new("PB_DefaultMicroWedge").size((1, 10, 1)).offset((0, 4, 5)).rotation_offset(3).microwedge_rotate(true).direction_override(ZNegative), 491 | ], 492 | "2x4x3 Tube" => vec![ 493 | BrickDesc::new("PB_DefaultTile").size((20, 10, 2)).offset((0, 0, -16)), 494 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 10, 14)).offset((0, -18, 0)), 495 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 10, 14)).offset((0, 18, 0)), 496 | BrickDesc::new("PB_DefaultBrick").size((20, 10, 2)).offset((0, 0, 16)), 497 | ], 498 | "2x4x3 Windscreen" => vec![ 499 | BrickDesc::new("PB_DefaultTile").size((20, 10, 2)).offset((0, 0, -16)), 500 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 5, 14)).offset((-5, -18, 0)), 501 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 5, 14)).offset((-5, 18, 0)), 502 | BrickDesc::new("PB_DefaultBrick").size((20, 5, 2)).offset((-5, 0, 16)), 503 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 2, 16)).offset((5, 18, 2)).microwedge_rotate(true).rotation_offset(0), 504 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 2, 16)).offset((5, -18, 2)).microwedge_rotate(true).rotation_offset(0), 505 | ], 506 | "2x4x3 Windscreen Inv" => vec![ 507 | BrickDesc::new("PB_DefaultTile").size((20, 5, 2)).offset((-5, 0, -16)), 508 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 5, 14)).offset((-5, -18, 0)), 509 | BrickDesc::new("PB_DefaultMicroBrick").size((2, 5, 14)).offset((-5, 18, 0)), 510 | BrickDesc::new("PB_DefaultBrick").size((20, 10, 2)).offset((0, 0, 16)), 511 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 2, 16)).offset((5, 18, -2)).microwedge_rotate(true).rotation_offset(2).direction_override(ZNegative), 512 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 2, 16)).offset((5, -18, -2)).microwedge_rotate(true).rotation_offset(2).direction_override(ZNegative), 513 | ], 514 | "1x4x2vertwing" => vec![ 515 | BrickDesc::new("PB_DefaultMicroBrick").size((15, 5, 2)).offset((0, 5, -10)), 516 | BrickDesc::new("PB_DefaultMicroBrick").size((5, 1, 9)).offset((0, -5, 1)), 517 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 9)).offset((0, -15, 1)).microwedge_rotate(true).direction_override(ZNegative), 518 | BrickDesc::new("PB_DefaultMicroWedge").size((10, 1, 9)).offset((0, 10, 1)).microwedge_rotate(true), 519 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 5, 1)).offset((0, -10, 11)), 520 | ], 521 | "1x5x3vertwing" => vec![ 522 | BrickDesc::new("PB_DefaultMicroBrick").size((20, 5, 2)).offset((0, 5, -16)), 523 | BrickDesc::new("PB_DefaultMicroBrick").size((5, 1, 15)).offset((0, -10, 1)), 524 | BrickDesc::new("PB_DefaultMicroWedge").size((5, 1, 15)).offset((0, -20, 1)).microwedge_rotate(true).direction_override(ZNegative), 525 | BrickDesc::new("PB_DefaultMicroWedge").size((15, 1, 15)).offset((0, 10, 1)).microwedge_rotate(true), 526 | BrickDesc::new("PB_DefaultMicroBrick").size((10, 5, 1)).offset((0, -15, 17)), 527 | ], 528 | ]; 529 | 530 | pub static ref BRICK_MAP_REGEX: Vec<(Regex, RegexHandler)> = brick_map_regex![ 531 | // TODO: Consider trying to handle fractional sizes that sometimes occur 532 | // TODO: Remove (?: Print)? when prints exist 533 | r"^(\d+)x(\d+)(?:x(\d+)|([Ff])|([Hh]))?( Print)?( Ceiling)?$" => |captures, from| { 534 | let width: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 535 | let length: u32 = captures.get(2).unwrap().as_str().parse().ok()?; 536 | let z: u32 = if captures.get(4).is_some() { // F 537 | 2 538 | } else if captures.get(5).is_some() { // H 539 | 4 540 | } else { // x(Z) 541 | captures 542 | .get(3) 543 | .map(|g| g.as_str().parse::().ok()) 544 | .unwrap_or(Some(1))? 545 | * 6 546 | }; 547 | 548 | let print = captures.get(6).is_some(); 549 | let asset = if z == 2 && print && TILE_PRINTS.contains(from.base.print.as_str()) { 550 | "PB_DefaultTile" 551 | } else if z == 2 && print { 552 | "PB_DefaultSmoothTile" 553 | } else { 554 | "PB_DefaultBrick" 555 | }; 556 | let rotation_offset = if print { 0 } else { 1 }; 557 | let dir = if captures.get(7).is_some() { ZNegative } else { ZPositive }; 558 | 559 | Some(vec![BrickDesc::new(asset) 560 | .size((width * 5, length * 5, z)) 561 | .rotation_offset(rotation_offset) 562 | .direction_override(dir)]) 563 | }, 564 | 565 | // TODO: Remove (?: Print)? when prints exist 566 | r"^(-)?(25|45|65|72|80)° ?(Inv )?Ramp(?: (\d+)x)?( Corner)?(?: Print)?$" => |captures, _| { 567 | let neg = captures.get(1).is_some(); 568 | let inv = captures.get(3).is_some(); 569 | let corner = captures.get(5).is_some(); 570 | 571 | if inv && !corner { 572 | return None; 573 | } 574 | 575 | let asset = if neg { 576 | if inv { 577 | "PB_DefaultRampInnerCornerInverted" 578 | } else if corner { 579 | "PB_DefaultRampCornerInverted" 580 | } else { 581 | "PB_DefaultRampInverted" 582 | } 583 | } else if inv { 584 | "PB_DefaultRampInnerCorner" 585 | } else if corner { 586 | "PB_DefaultRampCorner" 587 | } else { 588 | "PB_DefaultRamp" 589 | }; 590 | 591 | let degree_str = captures.get(2).unwrap().as_str(); 592 | 593 | let (x, z) = if degree_str == "25" { 594 | (15, 6) 595 | } else if degree_str == "45" { 596 | (10, 6) 597 | } else if degree_str == "65" { 598 | (10, 12) 599 | } else if degree_str == "72" { 600 | (10, 18) 601 | } else if degree_str == "80" { 602 | (10, 30) 603 | } else { 604 | return None; 605 | }; 606 | 607 | let mut y = x; 608 | 609 | if let Some(group) = captures.get(4) { 610 | if corner { 611 | return None; 612 | } 613 | 614 | let length: u32 = group.as_str().parse().ok()?; 615 | y = length * 5; 616 | } 617 | 618 | Some(vec![BrickDesc::new(asset).size((x, y, z)).rotation_offset(0)]) 619 | }, 620 | 621 | r"(?P25|45)° Crest (?:(?PEnd)|(?PCorner)|(?P\d+)x)" => |captures, _| { 622 | let (z, offset) = match captures.name("angle").unwrap().as_str() { 623 | s if s == "25" => (4, -2), 624 | s if s == "45" => (6, 0), 625 | _ => return None, 626 | }; 627 | 628 | let (asset, x, y, rotation) = if captures.name("end").is_some() { 629 | ("PB_DefaultRampCrestEnd", 10, 5, 2) 630 | } else if captures.name("corner").is_some() { 631 | ("PB_DefaultRampCrestCorner", 10, 10, 0) 632 | } else { 633 | let length: u32 = captures.name("length").unwrap().as_str().parse().ok()?; 634 | ("PB_DefaultRampCrest", 10, length * 5, 0) 635 | }; 636 | 637 | Some(vec![BrickDesc::new(asset) 638 | .size((x, y, z)) 639 | .rotation_offset(rotation) 640 | .offset((0, 0, offset))]) 641 | }, 642 | 643 | r"^(\d+)x(\d+)F Tile$" => |captures, _| { 644 | let length: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 645 | let width: u32 = captures.get(2).unwrap().as_str().parse().ok()?; 646 | Some(vec![BrickDesc::new("PB_DefaultTile").size((width * 5, length * 5, 2))]) 647 | }, 648 | r"^(\d+)x(\d+) Base$" => |captures, _| { 649 | let width: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 650 | let length: u32 = captures.get(2).unwrap().as_str().parse().ok()?; 651 | Some(vec![BrickDesc::new("PB_DefaultBrick").size((width * 5, length * 5, 2))]) 652 | }, 653 | r"^(\d+)x Cube$" => |captures, _| { 654 | let size: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 655 | Some(vec![BrickDesc::new("PB_DefaultBrick").size((size * 5, size * 5, size * 5))]) 656 | }, 657 | r"^(?P\d+)x (?:(?PCube)|(?PRamp)|(?PCornerA|CorA)|(?PCornerB|CorB)|(?PCornerC|CorC)|(?PCornerD|CorD)|(?PWedge))(?:(?P Steep)|(?P 3/4h)|(?P 1/2h)|(?P 1/4h)| )?(?P Inv.)?$" => |captures, _| { 658 | let size: u32 = captures.name("size").unwrap().as_str().parse().ok()?; 659 | let height = if captures.name("steep").is_some() { 660 | size * 2 * 5 661 | } else if captures.name("three_quarters").is_some() { 662 | match size { 663 | 8 => 5 * 6, 664 | _ => return None 665 | } 666 | } else if captures.name("half").is_some() { 667 | size / 2 * 5 668 | } else if captures.name("quarter").is_some() { 669 | size / 4 * 5 670 | } else { 671 | size * 5 672 | }; 673 | let (asset, mut rotation, use_offset, mw) = if captures.name("cube").is_some() { 674 | ("PB_DefaultMicroBrick", 1, false, false) 675 | } else if captures.name("wedge").is_some() { 676 | ("PB_DefaultMicroWedge", 2, false, false) 677 | } else if captures.name("ramp").is_some() { 678 | ("PB_DefaultMicroWedge", 3, false, true) 679 | } else if captures.name("cornera").is_some() { 680 | ("PB_DefaultMicroWedgeTriangleCorner", 2, false, false) 681 | } else if captures.name("cornerb").is_some() { 682 | ("PB_DefaultMicroWedgeOuterCorner", 2, false, false) 683 | } else if captures.name("cornerc").is_some() { 684 | ("PB_DefaultMicroWedgeCorner", 2, false, false) 685 | } else if captures.name("cornerd").is_some() { 686 | ("PB_DefaultMicroWedgeInnerCorner", 2, false, false) 687 | } else { 688 | unreachable!() 689 | }; 690 | let offset = if use_offset { 691 | (0, (size * 10) as i32, 0) 692 | } else { 693 | (0, 0, 0) 694 | }; 695 | let (direction, imr) = if captures.name("inv").is_some() { 696 | if captures.name("ramp").is_some() { 697 | rotation += 2; 698 | (ZNegative, false) 699 | } else { 700 | rotation += 3; 701 | (ZNegative, true) 702 | } 703 | } else { 704 | (ZPositive, false) 705 | }; 706 | 707 | Some(vec![BrickDesc::new(asset) 708 | .size((size * 5, size * 5, height)) 709 | .offset(offset) 710 | .rotation_offset(rotation) 711 | .microwedge_rotate(mw) 712 | .inverted_modter_rotate(imr) 713 | .direction_override(direction)]) 714 | }, 715 | r"(\d+)x(\d+)x?(?P\d+)? Arch(?P Up)?" => |captures, _| { 716 | let width: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 717 | let length: u32 = captures.get(2).unwrap().as_str().parse().ok()?; 718 | let height: u32 = if captures.name("height").is_some() { 719 | captures.name("height").unwrap().as_str().parse().ok()? 720 | } else { 721 | match length { 722 | 5 => 2, 723 | 8 => 3, 724 | _ => 1 725 | } 726 | }; 727 | let up = captures.name("up").is_some(); 728 | let direction = if up { ZNegative } else { ZPositive }; 729 | Some(vec![BrickDesc::new("PB_DefaultArch") 730 | .size((width * 5, length * 5, height * 6)) 731 | .direction_override(direction) 732 | ]) 733 | }, 734 | // 1RandomPack Panels 735 | r"^(\d)h Panel (?PCorner )?(?P\d)x" => |captures, _| { 736 | let height: u32 = captures.get(1).unwrap().as_str().parse().ok()?; 737 | let length: u32 = captures.name("length").unwrap().as_str().parse().ok()?; 738 | 739 | if captures.name("corner").is_some() { 740 | Some(vec![ 741 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 5, 2)).offset((0, 0, 2 - (height*6) as i32)), 742 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 1, 4)).offset((-4, 0, 2)), 743 | BrickDesc::new("PB_DefaultMicroBrick").size((1, 4, 4)).offset((1, -4, 2)), 744 | ]) 745 | } else if height == 1 { 746 | Some(vec![ 747 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 5, 2)).offset((0, 0, 2 - (height*6) as i32)), 748 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 1, 4)).offset((-4, 0, 2)) 749 | ]) 750 | } else { 751 | Some(vec![ 752 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 5, 2)).offset((0, 0, 2 - (height*6) as i32)), 753 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 1, height*6 - 4)).offset((-4, 0, 0)), 754 | BrickDesc::new("PB_DefaultMicroBrick").size((length*5, 5, 2)).offset((0, 0, (height*6) as i32 - 2)) 755 | ]) 756 | } 757 | }, 758 | // 1RandomPack Center Ramps 759 | r"^(-)?(\d+)° Center (Diag )?Ramp 1x" => |captures, _| { 760 | let neg = captures.get(1).is_some(); 761 | let angle = captures.get(2).unwrap().as_str(); 762 | let diag = captures.get(3).is_some(); 763 | let (z, x) = match angle { 764 | "18" => (6, 15), 765 | "25" => (6, 10), 766 | "45" => (6, 5), 767 | "65" => (12, 5), 768 | "72" => (18, 5), 769 | "80" => (30, 5), 770 | _ => return None 771 | }; 772 | let (dir, iwr) = if neg { 773 | (ZNegative, true) 774 | } else { 775 | (ZPositive, false) 776 | }; 777 | let (dir2, iwr2) = if diag { 778 | (ZNegative, true) 779 | } else { 780 | (dir, iwr) 781 | }; 782 | Some(vec![ 783 | BrickDesc::new("PB_DefaultBrick").size((5, 5, z)).direction_override(dir), 784 | BrickDesc::new("PB_DefaultWedge").size((x, 5, z)).offset((-(x as i32 + 5), 0, 0)) 785 | .rotation_offset(2).direction_override(dir2).inverted_wedge_rotate(iwr2), 786 | BrickDesc::new("PB_DefaultWedge").size((x, 5, z)).offset((x as i32 + 5, 0, 0)) 787 | .rotation_offset(0).direction_override(dir).inverted_wedge_rotate(iwr), 788 | ]) 789 | }, 790 | ]; 791 | } 792 | --------------------------------------------------------------------------------