diff --git a/examples/create_basic_vpx_file.rs b/examples/create_basic_vpx_file.rs index beb6e2b..a0743bf 100644 --- a/examples/create_basic_vpx_file.rs +++ b/examples/create_basic_vpx_file.rs @@ -1,6 +1,6 @@ use std::path::Path; use vpin::vpx; -use vpin::vpx::color::ColorNoAlpha; +use vpin::vpx::color::Color; use vpin::vpx::gameitem::bumper::Bumper; use vpin::vpx::gameitem::flipper::Flipper; use vpin::vpx::gameitem::GameItemEnum; @@ -14,11 +14,11 @@ fn main() -> Result<(), Box> { let mut material = Material::default(); material.name = "Playfield".to_string(); // material defaults to purple - material.base_color = ColorNoAlpha::from_rgb(0x966F33); // Wood + material.base_color = Color::from_rgb(0x966F33); // Wood vpx.gamedata.materials = Some(vec![material]); // black background (default is bluish gray) - vpx.gamedata.backdrop_color = ColorNoAlpha::from_rgb(0x060606); // Dark Gray + vpx.gamedata.backdrop_color = Color::from_rgb(0x060606); // Dark Gray vpx.gamedata.playfield_material = "Playfield".to_string(); // add a plunger diff --git a/src/vpx/color.rs b/src/vpx/color.rs index a4550fe..917e24e 100644 --- a/src/vpx/color.rs +++ b/src/vpx/color.rs @@ -5,53 +5,106 @@ use serde::{Deserialize, Serialize}; use super::biff::BiffWriter; #[derive(Debug, PartialEq, Clone, Copy, Dummy)] -pub struct ColorNoAlpha { +pub struct Color { + /// Unused byte, should be 0 but when reading from vpx files it might contain random data. + /// So used for BIFF reading and writing + /// And since we want to round-trip the data, we need to store it in the json format as well. + /// Seems to contain 255 or 128 in the wild. + unused: u8, r: u8, g: u8, b: u8, } +impl Color { + pub const RED: Color = Color { + r: 255, + g: 0, + b: 0, + unused: 0, + }; + pub const BLACK: Color = Color { + r: 0, + g: 0, + b: 0, + unused: 0, + }; + pub const WHITE: Color = Color { + r: 255, + g: 255, + b: 255, + unused: 0, + }; +} + /// Serialize as a string in the format "#RRGGBB". -impl Serialize for ColorNoAlpha { +impl Serialize for Color { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { - let s = format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b); - serializer.serialize_str(&s) + if self.unused == 0 { + let s = format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b); + serializer.serialize_str(&s) + } else { + let s = format!( + "{:02x}#{:02x}{:02x}{:02x}", + self.unused, self.r, self.g, self.b + ); + serializer.serialize_str(&s) + } } } // Deserialize from a string in the format "#RRGGBB". -impl<'de> Deserialize<'de> for ColorNoAlpha { - fn deserialize(deserializer: D) -> Result +impl<'de> Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; - if s.len() != 7 { - return Err(serde::de::Error::custom( - "Invalid color format, expected #RRGGBB", - )); - } - if &s[0..1] != "#" { - return Err(serde::de::Error::custom( + match s.len() { + 7 => { + if &s[0..1] != "#" { + return Err(serde::de::Error::custom( + "Invalid color format, expected #RRGGBB", + )); + } + let r = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?; + let g = u8::from_str_radix(&s[3..5], 16).map_err(serde::de::Error::custom)?; + let b = u8::from_str_radix(&s[5..7], 16).map_err(serde::de::Error::custom)?; + Ok(Color { + unused: 0u8, + r, + g, + b, + }) + } + 9 => { + if &s[2..3] != "#" { + return Err(serde::de::Error::custom( + "Invalid color format, expected #RRGGBB", + )); + } + let unused = u8::from_str_radix(&s[0..2], 16).map_err(serde::de::Error::custom)?; + let r = u8::from_str_radix(&s[3..5], 16).map_err(serde::de::Error::custom)?; + let g = u8::from_str_radix(&s[5..7], 16).map_err(serde::de::Error::custom)?; + let b = u8::from_str_radix(&s[7..9], 16).map_err(serde::de::Error::custom)?; + Ok(Color { unused, r, g, b }) + } + _ => Err(serde::de::Error::custom( "Invalid color format, expected #RRGGBB", - )); + )), } - let r = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?; - let g = u8::from_str_radix(&s[3..5], 16).map_err(serde::de::Error::custom)?; - let b = u8::from_str_radix(&s[5..7], 16).map_err(serde::de::Error::custom)?; - Ok(ColorNoAlpha { r, g, b }) } } -impl ColorNoAlpha { +impl Color { pub fn from_rgb(arg: u32) -> Self { let r = ((arg >> 16) & 0xff) as u8; let g = ((arg >> 8) & 0xff) as u8; let b = (arg & 0xff) as u8; - ColorNoAlpha { r, g, b } + Color { r, g, b, unused: 0 } } pub fn to_rgb(&self) -> u32 { @@ -61,178 +114,101 @@ impl ColorNoAlpha { r | g | b } - pub fn rgb(r: u8, g: u8, b: u8) -> Self { - Self { r, g, b } - } - - pub fn biff_read(reader: &mut BiffReader<'_>) -> ColorNoAlpha { - let r = reader.get_u8(); - let g = reader.get_u8(); - let b = reader.get_u8(); - let _ = reader.get_u8(); - ColorNoAlpha { r, g, b } - } - - pub fn biff_write(&self, writer: &mut BiffWriter) { - writer.write_u8(self.r); - writer.write_u8(self.g); - writer.write_u8(self.b); - writer.write_u8(0); - } -} - -#[derive(Debug, PartialEq, Clone, Copy, Dummy)] -pub struct Color { - a: u8, - r: u8, - g: u8, - b: u8, -} - -// TODO we might want to switch to a more standard format like #AARRGGBB -#[derive(Debug, PartialEq)] -pub(crate) struct ColorJson { - a: u8, - r: u8, - g: u8, - b: u8, -} - -impl ColorJson { - pub fn from_color(color: &Color) -> Self { - Self { - a: color.a, - r: color.r, - g: color.g, - b: color.b, - } - } - pub fn to_color(&self) -> Color { - Color { - a: self.a, - r: self.r, - g: self.g, - b: self.b, - } - } -} - -/** - * This is a custom serializer for the ColorJson struct. - * It serializes the color as a string in the format "#AARRGGBB". - */ -impl Serialize for ColorJson { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let s = format!("#{:02x}{:02x}{:02x}{:02x}", self.a, self.r, self.g, self.b); - serializer.serialize_str(&s) - } -} - -/** - * This is a custom deserializer for the ColorJson struct. - * It deserializes the color from a string in the format "#AARRGGBB". - */ -impl<'de> Deserialize<'de> for ColorJson { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - if s.len() != 9 { - return Err(serde::de::Error::custom( - "Invalid color format, expected #AARRGGBB", - )); - } - if &s[0..1] != "#" { - return Err(serde::de::Error::custom( - "Invalid color format, expected #AARRGGBB", - )); - } - let a = u8::from_str_radix(&s[1..3], 16).map_err(serde::de::Error::custom)?; - let r = u8::from_str_radix(&s[3..5], 16).map_err(serde::de::Error::custom)?; - let g = u8::from_str_radix(&s[5..7], 16).map_err(serde::de::Error::custom)?; - let b = u8::from_str_radix(&s[7..9], 16).map_err(serde::de::Error::custom)?; - Ok(ColorJson { a, r, g, b }) + // Representation used in vpinball is Windows GDI COLORREF + // https://learn.microsoft.com/en-us/windows/win32/gdi/colorref + // 0x00bbggrr + pub fn to_win_color(&self) -> u32 { + let unused = (self.unused as u32) << 24; + let r = self.r as u32; + let g = (self.g as u32) << 8; + let b = (self.b as u32) << 16; + unused | r | g | b } -} -impl Color { - pub fn from_argb(arg: u32) -> Color { - let a = ((arg >> 24) & 0xff) as u8; - let r = ((arg >> 16) & 0xff) as u8; + pub fn from_win_color(arg: u32) -> Self { + let unused = ((arg >> 24) & 0xff) as u8; + let r = (arg & 0xff) as u8; let g = ((arg >> 8) & 0xff) as u8; - let b = (arg & 0xff) as u8; - Color { a, r, g, b } - } - - pub fn new_bgr(arg: u32) -> Color { - let a = ((arg >> 24) & 0xff) as u8; let b = ((arg >> 16) & 0xff) as u8; - let g = ((arg >> 8) & 0xff) as u8; - let r = (arg & 0xff) as u8; - Color { a, r, g, b } + Color { r, g, b, unused } } - pub fn bgr(&self) -> u32 { - let a = (self.a as u32) << 24; - let b = (self.b as u32) << 16; - let g = (self.g as u32) << 8; - let r = self.r as u32; - a | b | g | r - } - - pub fn argb(&self) -> u32 { - let a = (self.a as u32) << 24; - let r = (self.r as u32) << 16; - let g = (self.g as u32) << 8; - let b = self.b as u32; - a | r | g | b + pub fn rgb(r: u8, g: u8, b: u8) -> Self { + Self { r, g, b, unused: 0 } } - pub const BLACK: Color = Color { - a: 255, - r: 0, - g: 0, - b: 0, - }; - pub const WHITE: Color = Color { - a: 255, - r: 255, - g: 255, - b: 255, - }; - pub const RED: Color = Color { - a: 255, - r: 255, - g: 0, - b: 0, - }; - - pub fn biff_read_bgr(reader: &mut BiffReader<'_>) -> Color { - let a = reader.get_u8(); + pub fn biff_read(reader: &mut BiffReader<'_>) -> Color { + // since we read in little endian, we need to read the color in BGR0 format let r = reader.get_u8(); let g = reader.get_u8(); let b = reader.get_u8(); - Color { a, r, g, b } + let unused = reader.get_u8(); + // if unused != 0 { + // eprintln!("Random data found in color: {unused} {r} {g} {b}"); + // } + Color { r, g, b, unused } } - pub fn biff_write_bgr(&self, writer: &mut BiffWriter) { - writer.write_u8(self.a); + pub fn biff_write(&self, writer: &mut BiffWriter) { + // since we write in little endian, we need to write the color in BGR0 format writer.write_u8(self.r); writer.write_u8(self.g); writer.write_u8(self.b); + writer.write_u8(self.unused); } } impl std::fmt::Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "{:02x}{:02x}{:02x}{:02x}", - self.a, self.r, self.g, self.b - ) + write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_color_serde() { + let color = Color::rgb(0x12, 0x34, 0x56); + let s = serde_json::to_string(&color).unwrap(); + assert_eq!(s, "\"#123456\""); + let color2: Color = serde_json::from_str(&s).unwrap(); + assert_eq!(color, color2); + } + + #[test] + fn test_color_biff() { + let color = Color::rgb(0x12, 0x34, 0x56); + let mut writer = BiffWriter::new(); + color.biff_write(&mut writer); + let data = writer.get_data(); + let mut reader = BiffReader::with_remaining(data, 4); + let color2 = Color::biff_read(&mut reader); + assert_eq!(color, color2); + } + + #[test] + fn test_color_biff_with_random_data() { + let data: Vec = vec![0xFF, 0xFF, 0xFF, 0x12]; + let mut reader = BiffReader::with_remaining(&data, 4); + let color = Color::biff_read(&mut reader); + assert_eq!(color.r, 0xFF); + assert_eq!(color.g, 0xFF); + assert_eq!(color.b, 0xFF); + assert_eq!(color.unused, 0x12); + let mut writer = BiffWriter::new(); + color.biff_write(&mut writer); + let data = writer.get_data(); + assert_eq!(data, vec![0xFF, 0xFF, 0xFF, 0x12]); + } + + #[test] + fn test_win_color() { + let color = Color::rgb(0x12, 0x34, 0x56); + let win_color = color.to_win_color(); + assert_eq!(win_color, 0x00563412); + let color2 = Color::from_win_color(win_color); + assert_eq!(color, color2); } } diff --git a/src/vpx/gamedata.rs b/src/vpx/gamedata.rs index 6801854..37d61cf 100644 --- a/src/vpx/gamedata.rs +++ b/src/vpx/gamedata.rs @@ -6,17 +6,110 @@ use super::{ version::Version, }; use crate::vpx::biff::{BiffRead, BiffWrite}; -use crate::vpx::color::{Color, ColorJson, ColorNoAlpha}; +use crate::vpx::color::Color; use crate::vpx::json::F32WithNanInf; use crate::vpx::material::{Material, SaveMaterial, SavePhysicsMaterial}; use crate::vpx::math::{dequantize_u8, quantize_u8}; use crate::vpx::renderprobe::RenderProbeWithGarbage; use bytes::{Buf, BufMut, BytesMut}; -use serde::{Deserialize, Serialize}; +use fake::Dummy; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; -pub const VIEW_LAYOUT_MODE_LEGACY: u32 = 0; // All tables before 10.8 used a viewer position relative to a fitting of a set of bounding vertices (not all parts) with a standard perspective projection skewed by a layback angle -pub const VIEW_LAYOUT_MODE_CAMERA: u32 = 1; // Position viewer relative to the bottom center of the table, use a standard camera perspective projection, replace layback by a frustrum offset -pub const VIEW_LAYOUT_MODE_WINDOW: u32 = 2; // Position viewer relative to the bottom center of the table, use an oblique surface (re)projection (needs some postprocess to avoid distortion) +#[derive(Debug, PartialEq, Dummy, Clone, Copy)] +pub enum ViewLayoutMode { + /// All tables before 10.8 used a viewer position relative to a fitting of a set of bounding vertices (not all parts) with a standard perspective projection skewed by a layback angle + Legacy = 0, + /// Position viewer relative to the bottom center of the table, use a standard camera perspective projection, replace layback by a frustum offset + Camera = 1, + /// Position viewer relative to the bottom center of the screen, use an oblique surface (re)projection (would need some postprocess to limit distortion) + Window = 2, +} + +impl From for ViewLayoutMode { + fn from(value: u32) -> Self { + match value { + 0 => ViewLayoutMode::Legacy, + 1 => ViewLayoutMode::Camera, + 2 => ViewLayoutMode::Window, + _ => panic!("Invalid ViewLayoutMode {}", value), + } + } +} + +impl From<&ViewLayoutMode> for u32 { + fn from(value: &ViewLayoutMode) -> Self { + match value { + ViewLayoutMode::Legacy => 0, + ViewLayoutMode::Camera => 1, + ViewLayoutMode::Window => 2, + } + } +} + +// Serialize ViewLayoutMode as to lowercase string +impl Serialize for ViewLayoutMode { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ViewLayoutMode::Legacy => serializer.serialize_str("legacy"), + ViewLayoutMode::Camera => serializer.serialize_str("camera"), + ViewLayoutMode::Window => serializer.serialize_str("window"), + } + } +} + +// Deserialize ViewLayoutMode from lowercase string +// or number for backwards compatibility +impl<'de> Deserialize<'de> for ViewLayoutMode { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ViewLayoutModeVisitor; + + impl<'de> serde::de::Visitor<'de> for ViewLayoutModeVisitor { + type Value = ViewLayoutMode; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a ViewLayoutMode as lowercase string or number") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "legacy" => Ok(ViewLayoutMode::Legacy), + "camera" => Ok(ViewLayoutMode::Camera), + "window" => Ok(ViewLayoutMode::Window), + _ => Err(serde::de::Error::unknown_variant( + value, + &["legacy", "camera", "window"], + )), + } + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + match value { + 0 => Ok(ViewLayoutMode::Legacy), + 1 => Ok(ViewLayoutMode::Camera), + 2 => Ok(ViewLayoutMode::Window), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(value), + &"0, 1, or 2", + )), + } + } + } + + deserializer.deserialize_any(ViewLayoutModeVisitor) + } +} // TODO switch to a array of 3 view modes like in the original code #[derive(Debug, PartialEq)] @@ -50,13 +143,13 @@ pub struct ViewSetup { // float mWindowBottomXOfs = 0.0f; // Lower window border offset from left and right table bounds // float mWindowBottomYOfs = 0.0f; // Lower window border Y coordinate, relative to table bottom // float mWindowBottomZOfs = CMTOVPU(7.5f); // Lower window border Z coordinate, relative to table playfield Z - pub mode: u32, + pub mode: ViewLayoutMode, } impl ViewSetup { pub fn new() -> Self { ViewSetup { - mode: VIEW_LAYOUT_MODE_LEGACY, + mode: ViewLayoutMode::Legacy, } } } @@ -67,147 +160,252 @@ impl Default for ViewSetup { } } +#[derive(Debug, PartialEq, Dummy, Clone, Copy)] +pub enum ToneMapper { + ///Reinhard, used to be the default until 10.8 + Reinhard = 0, + /// Precomputed high quality phenomenological tonemapping https://github.com/h3r2tic/tony-mc-mapface + TonyMcMapface = 1, + /// Filmic tonemapper + Filmic = 2, +} + +impl From for ToneMapper { + fn from(value: u32) -> Self { + match value { + 0 => ToneMapper::Reinhard, + 1 => ToneMapper::TonyMcMapface, + 2 => ToneMapper::Filmic, + _ => panic!("Invalid ToneMapper {}", value), + } + } +} + +impl From<&ToneMapper> for u32 { + fn from(value: &ToneMapper) -> Self { + match value { + ToneMapper::Reinhard => 0, + ToneMapper::TonyMcMapface => 1, + ToneMapper::Filmic => 2, + } + } +} + +/// Serializes ToneMapper to lowercase string +impl Serialize for ToneMapper { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + ToneMapper::Reinhard => serializer.serialize_str("reinhard"), + ToneMapper::TonyMcMapface => serializer.serialize_str("tony_mc_mapface"), + ToneMapper::Filmic => serializer.serialize_str("filmic"), + } + } +} + +/// Deserializes ToneMapper from lowercase string +/// or number for backwards compatibility +impl<'de> Deserialize<'de> for ToneMapper { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ToneMapperVisitor; + + impl<'de> serde::de::Visitor<'de> for ToneMapperVisitor { + type Value = ToneMapper; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a ToneMapper as lowercase string or number") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + match value { + "reinhard" => Ok(ToneMapper::Reinhard), + "tony_mc_mapface" => Ok(ToneMapper::TonyMcMapface), + "filmic" => Ok(ToneMapper::Filmic), + _ => Err(serde::de::Error::unknown_variant( + value, + &["reinhard", "tony_mc_mapface", "filmic"], + )), + } + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + match value { + 0 => Ok(ToneMapper::Reinhard), + 1 => Ok(ToneMapper::TonyMcMapface), + 2 => Ok(ToneMapper::Filmic), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Unsigned(value), + &"0, 1, or 2", + )), + } + } + } + + deserializer.deserialize_any(ToneMapperVisitor) + } +} + #[derive(Debug, PartialEq)] pub struct GameData { - pub left: f32, // LEFT 1 - pub top: f32, // TOPX 2 - pub right: f32, // RGHT 3 - pub bottom: f32, // BOTM 4 - pub clmo: Option, // CLMO added in 10.8.0? - pub bg_view_mode_desktop: Option, // VSM0 added in 10.8.x - pub bg_rotation_desktop: f32, // ROTA 5 - pub bg_inclination_desktop: f32, // INCL 6 - pub bg_layback_desktop: f32, // LAYB 7 - pub bg_fov_desktop: f32, // FOVX 8 - pub bg_offset_x_desktop: f32, // XLTX 9 - pub bg_offset_y_desktop: f32, // XLTY 10 - pub bg_offset_z_desktop: f32, // XLTZ 11 - pub bg_scale_x_desktop: f32, // SCLX 12 - pub bg_scale_y_desktop: f32, // SCLY 13 - pub bg_scale_z_desktop: f32, // SCLZ 14 - pub bg_enable_fss: Option, // EFSS 15 (added in 10.?) - pub bg_view_horizontal_offset_desktop: Option, // HOF0 added in 10.8.x - pub bg_view_vertical_offset_desktop: Option, // VOF0 added in 10.8.x - pub bg_window_top_x_offset_desktop: Option, // WTX0 added in 10.8.x - pub bg_window_top_y_offset_desktop: Option, // WTY0 added in 10.8.x - pub bg_window_top_z_offset_desktop: Option, // WTZ0 added in 10.8.x - pub bg_window_bottom_x_offset_desktop: Option, // WBX0 added in 10.8.x - pub bg_window_bottom_y_offset_desktop: Option, // WBY0 added in 10.8.x - pub bg_window_bottom_z_offset_desktop: Option, // WBZ0 added in 10.8.x - pub bg_view_mode_fullscreen: Option, // VSM1 added in 10.8.x - pub bg_rotation_fullscreen: f32, // ROTF 16 - pub bg_inclination_fullscreen: f32, // INCF 17 - pub bg_layback_fullscreen: f32, // LAYF 18 - pub bg_fov_fullscreen: f32, // FOVF 19 - pub bg_offset_x_fullscreen: f32, // XLFX 20 - pub bg_offset_y_fullscreen: f32, // XLFY 21 - pub bg_offset_z_fullscreen: f32, // XLFZ 22 - pub bg_scale_x_fullscreen: f32, // SCFX 23 - pub bg_scale_y_fullscreen: f32, // SCFY 24 - pub bg_scale_z_fullscreen: f32, // SCFZ 25 - pub bg_view_horizontal_offset_fullscreen: Option, // HOF1 added in 10.8.x - pub bg_view_vertical_offset_fullscreen: Option, // VOF1 added in 10.8.x - pub bg_window_top_x_offset_fullscreen: Option, // WTX1 added in 10.8.x - pub bg_window_top_y_offset_fullscreen: Option, // WTY1 added in 10.8.x - pub bg_window_top_z_offset_fullscreen: Option, // WTZ1 added in 10.8.x - pub bg_window_bottom_x_offset_fullscreen: Option, // WBX1 added in 10.8.x - pub bg_window_bottom_y_offset_fullscreen: Option, // WBY1 added in 10.8.x - pub bg_window_bottom_z_offset_fullscreen: Option, // WBZ1 added in 10.8.x - pub bg_view_mode_full_single_screen: Option, // VSM2 added in 10.8.x - pub bg_rotation_full_single_screen: Option, // ROFS 26 (added in 10.?) - pub bg_inclination_full_single_screen: Option, // INFS 27 (added in 10.?) - pub bg_layback_full_single_screen: Option, // LAFS 28 (added in 10.?) - pub bg_fov_full_single_screen: Option, // FOFS 29 (added in 10.?) - pub bg_offset_x_full_single_screen: Option, // XLXS 30 (added in 10.?) - pub bg_offset_y_full_single_screen: Option, // XLYS 31 (added in 10.?) - pub bg_offset_z_full_single_screen: Option, // XLZS 32 (added in 10.?) - pub bg_scale_x_full_single_screen: Option, // SCXS 33 (added in 10.?) - pub bg_scale_y_full_single_screen: Option, // SCYS 34 (added in 10.?) - pub bg_scale_z_full_single_screen: Option, // SCZS 35 (added in 10.?) + pub left: f32, // LEFT 1 + pub top: f32, // TOPX 2 + pub right: f32, // RGHT 3 + pub bottom: f32, // BOTM 4 + /// CLMO + /// During the 10.8.0 development cycle, this field was added but later again removed + /// Has meanwhile been replaced by the new [`GameData::bg_view_mode_desktop`], + /// [`GameData::bg_view_mode_fullscreen`] and [`GameData::bg_view_mode_ffs`] fields + /// See [the related commit](https://github.com/vpinball/vpinball/commit/5087b3c51b99676f91b02ee4b0c0af4b89b6afda) + /// CLM_RELATIVE = 0, // All tables before 10.8 used a camera position relative to a fitting of a set of bounding vertices (not all parts) + /// CLM_ABSOLUTE = 1 // Position camera relative to the bottom center of the table + pub camera_layout_mode: Option, + pub bg_view_mode_desktop: Option, // VSM0 added in 10.8.x + pub bg_rotation_desktop: f32, // ROTA 5 + pub bg_inclination_desktop: f32, // INCL 6 + pub bg_layback_desktop: f32, // LAYB 7 + pub bg_fov_desktop: f32, // FOVX 8 + pub bg_offset_x_desktop: f32, // XLTX 9 + pub bg_offset_y_desktop: f32, // XLTY 10 + pub bg_offset_z_desktop: f32, // XLTZ 11 + pub bg_scale_x_desktop: f32, // SCLX 12 + pub bg_scale_y_desktop: f32, // SCLY 13 + pub bg_scale_z_desktop: f32, // SCLZ 14 + pub bg_enable_fss: Option, // EFSS 15 (added in 10.?) + pub bg_view_horizontal_offset_desktop: Option, // HOF0 added in 10.8.x + pub bg_view_vertical_offset_desktop: Option, // VOF0 added in 10.8.x + pub bg_window_top_x_offset_desktop: Option, // WTX0 added in 10.8.x + pub bg_window_top_y_offset_desktop: Option, // WTY0 added in 10.8.x + pub bg_window_top_z_offset_desktop: Option, // WTZ0 added in 10.8.x + pub bg_window_bottom_x_offset_desktop: Option, // WBX0 added in 10.8.x + pub bg_window_bottom_y_offset_desktop: Option, // WBY0 added in 10.8.x + pub bg_window_bottom_z_offset_desktop: Option, // WBZ0 added in 10.8.x + pub bg_view_mode_fullscreen: Option, // VSM1 added in 10.8.x + pub bg_rotation_fullscreen: f32, // ROTF 16 + pub bg_inclination_fullscreen: f32, // INCF 17 + pub bg_layback_fullscreen: f32, // LAYF 18 + pub bg_fov_fullscreen: f32, // FOVF 19 + pub bg_offset_x_fullscreen: f32, // XLFX 20 + pub bg_offset_y_fullscreen: f32, // XLFY 21 + pub bg_offset_z_fullscreen: f32, // XLFZ 22 + pub bg_scale_x_fullscreen: f32, // SCFX 23 + pub bg_scale_y_fullscreen: f32, // SCFY 24 + pub bg_scale_z_fullscreen: f32, // SCFZ 25 + pub bg_view_horizontal_offset_fullscreen: Option, // HOF1 added in 10.8.x + pub bg_view_vertical_offset_fullscreen: Option, // VOF1 added in 10.8.x + pub bg_window_top_x_offset_fullscreen: Option, // WTX1 added in 10.8.x + pub bg_window_top_y_offset_fullscreen: Option, // WTY1 added in 10.8.x + pub bg_window_top_z_offset_fullscreen: Option, // WTZ1 added in 10.8.x + pub bg_window_bottom_x_offset_fullscreen: Option, // WBX1 added in 10.8.x + pub bg_window_bottom_y_offset_fullscreen: Option, // WBY1 added in 10.8.x + pub bg_window_bottom_z_offset_fullscreen: Option, // WBZ1 added in 10.8.x + pub bg_view_mode_full_single_screen: Option, // VSM2 added in 10.8.x + pub bg_rotation_full_single_screen: Option, // ROFS 26 (added in 10.?) + pub bg_inclination_full_single_screen: Option, // INFS 27 (added in 10.?) + pub bg_layback_full_single_screen: Option, // LAFS 28 (added in 10.?) + pub bg_fov_full_single_screen: Option, // FOFS 29 (added in 10.?) + pub bg_offset_x_full_single_screen: Option, // XLXS 30 (added in 10.?) + pub bg_offset_y_full_single_screen: Option, // XLYS 31 (added in 10.?) + pub bg_offset_z_full_single_screen: Option, // XLZS 32 (added in 10.?) + pub bg_scale_x_full_single_screen: Option, // SCXS 33 (added in 10.?) + pub bg_scale_y_full_single_screen: Option, // SCYS 34 (added in 10.?) + pub bg_scale_z_full_single_screen: Option, // SCZS 35 (added in 10.?) pub bg_view_horizontal_offset_full_single_screen: Option, // HOF2 added in 10.8.x - pub bg_view_vertical_offset_full_single_screen: Option, // VOF2 added in 10.8.x - pub bg_window_top_x_offset_full_single_screen: Option, // WTX2 added in 10.8.x - pub bg_window_top_y_offset_full_single_screen: Option, // WTY2 added in 10.8.x - pub bg_window_top_z_offset_full_single_screen: Option, // WTZ2 added in 10.8.x + pub bg_view_vertical_offset_full_single_screen: Option, // VOF2 added in 10.8.x + pub bg_window_top_x_offset_full_single_screen: Option, // WTX2 added in 10.8.x + pub bg_window_top_y_offset_full_single_screen: Option, // WTY2 added in 10.8.x + pub bg_window_top_z_offset_full_single_screen: Option, // WTZ2 added in 10.8.x pub bg_window_bottom_x_offset_full_single_screen: Option, // WBX2 added in 10.8.x pub bg_window_bottom_y_offset_full_single_screen: Option, // WBY2 added in 10.8.x pub bg_window_bottom_z_offset_full_single_screen: Option, // WBZ2 added in 10.8.x - pub override_physics: u32, // ORRP 36 - pub override_physics_flipper: Option, // ORPF 37 added in ? - pub gravity: f32, // GAVT 38 - pub friction: f32, // FRCT 39 - pub elasticity: f32, // ELAS 40 - pub elastic_falloff: f32, // ELFA 41 - pub scatter: f32, // PFSC 42 - pub default_scatter: f32, // SCAT 43 - pub nudge_time: f32, // NDGT 44 - pub plunger_normalize: u32, // MPGC 45 - pub plunger_filter: bool, // MPDF 46 - pub physics_max_loops: u32, // PHML 47 - pub render_em_reels: bool, // REEL 48 - pub render_decals: bool, // DECL 49 - pub offset_x: f32, // OFFX 50 - pub offset_y: f32, // OFFY 51 - pub zoom: f32, // ZOOM 52 - pub angle_tilt_max: f32, // SLPX 53 - pub angle_tilt_min: f32, // SLOP 54 - pub stereo_max_separation: f32, // MAXS 55 - pub stereo_zero_parallax_displacement: f32, // ZPD 56 - pub stereo_offset: Option, // STO 57 (was missing in 10.01) - pub overwrite_global_stereo3d: bool, // OGST 58 - pub image: String, // IMAG 59 - pub backglass_image_full_desktop: String, // BIMG 60 - pub backglass_image_full_fullscreen: String, // BIMF 61 - pub backglass_image_full_single_screen: Option, // BIMS 62 (added in 10.?) - pub image_backdrop_night_day: bool, // BIMN 63 - pub image_color_grade: String, // IMCG 64 - pub ball_image: String, // BLIM 65 - pub ball_spherical_mapping: Option, // BLSM (added in 10.8) - pub ball_image_front: String, // BLIF 66 - pub env_image: Option, // EIMG 67 (was missing in 10.01) - pub notes: Option, // NOTX 67.5 (added in 10.7) - pub screen_shot: String, // SSHT 68 - pub display_backdrop: bool, // FBCK 69 - pub glass_top_height: f32, // GLAS 70 - pub glass_bottom_height: Option, // GLAB 70.5 (added in 10.8) - pub table_height: Option, // TBLH 71 (optional in 10.8) - pub playfield_material: String, // PLMA 72 - pub backdrop_color: ColorNoAlpha, // BCLR 73 (color bgr) - pub global_difficulty: f32, // TDFT 74 - pub light_ambient: u32, // LZAM 75 (color) - pub light0_emission: u32, // LZDI 76 (color) - pub light_height: f32, // LZHI 77 - pub light_range: f32, // LZRA 78 - pub light_emission_scale: f32, // LIES 79 - pub env_emission_scale: f32, // ENES 80 - pub global_emission_scale: f32, // GLES 81 - pub ao_scale: f32, // AOSC 82 - pub ssr_scale: Option, // SSSC 83 (added in 10.?) - pub table_sound_volume: f32, // SVOL 84 - pub table_music_volume: f32, // MVOL 85 - pub table_adaptive_vsync: Option, // AVSY 86 (became optional in 10.8) - pub use_reflection_for_balls: Option, // BREF 87 (became optional in 10.8) - pub brst: Option, // BRST (in use in 10.01) - pub playfield_reflection_strength: f32, // PLST 88 - pub use_trail_for_balls: Option, // BTRA 89 (became optional in 10.8) - pub ball_decal_mode: bool, // BDMO 90 + pub override_physics: u32, // ORRP 36 + pub override_physics_flipper: Option, // ORPF 37 added in ? + pub gravity: f32, // GAVT 38 + pub friction: f32, // FRCT 39 + pub elasticity: f32, // ELAS 40 + pub elastic_falloff: f32, // ELFA 41 + pub scatter: f32, // PFSC 42 + pub default_scatter: f32, // SCAT 43 + pub nudge_time: f32, // NDGT 44 + pub plunger_normalize: u32, // MPGC 45 + pub plunger_filter: bool, // MPDF 46 + pub physics_max_loops: u32, // PHML 47 + pub render_em_reels: bool, // REEL 48 + pub render_decals: bool, // DECL 49 + pub offset_x: f32, // OFFX 50 + pub offset_y: f32, // OFFY 51 + pub zoom: f32, // ZOOM 52 + pub angle_tilt_max: f32, // SLPX 53 + pub angle_tilt_min: f32, // SLOP 54 + pub stereo_max_separation: f32, // MAXS 55 + pub stereo_zero_parallax_displacement: f32, // ZPD 56 + pub stereo_offset: Option, // STO 57 (was missing in 10.01) + pub overwrite_global_stereo3d: bool, // OGST 58 + pub image: String, // IMAG 59 + pub backglass_image_full_desktop: String, // BIMG 60 + pub backglass_image_full_fullscreen: String, // BIMF 61 + pub backglass_image_full_single_screen: Option, // BIMS 62 (added in 10.?) + pub image_backdrop_night_day: bool, // BIMN 63 + pub image_color_grade: String, // IMCG 64 + pub ball_image: String, // BLIM 65 + pub ball_spherical_mapping: Option, // BLSM (added in 10.8) + pub ball_image_front: String, // BLIF 66 + pub env_image: Option, // EIMG 67 (was missing in 10.01) + pub notes: Option, // NOTX 67.5 (added in 10.7) + pub screen_shot: String, // SSHT 68 + pub display_backdrop: bool, // FBCK 69 + pub glass_top_height: f32, // GLAS 70 + pub glass_bottom_height: Option, // GLAB 70.5 (added in 10.8) + pub table_height: Option, // TBLH 71 (optional in 10.8) + pub playfield_material: String, // PLMA 72 + pub backdrop_color: Color, // BCLR 73 (color bgr) + pub global_difficulty: f32, // TDFT 74 + /// changes the ambient light contribution for each material, please always try to keep this at full Black + pub light_ambient: Color, // LZAM 75 (color) + /// changes the light contribution for each material (currently light0 emission is copied to light1, too) + pub light0_emission: Color, // LZDI 76 (color) + pub light_height: f32, // LZHI 77 + pub light_range: f32, // LZRA 78 + pub light_emission_scale: f32, // LIES 79 + pub env_emission_scale: f32, // ENES 80 + pub global_emission_scale: f32, // GLES 81 + pub ao_scale: f32, // AOSC 82 + pub ssr_scale: Option, // SSSC 83 (added in 10.?) + pub table_sound_volume: f32, // SVOL 84 + pub table_music_volume: f32, // MVOL 85 + pub table_adaptive_vsync: Option, // AVSY 86 (became optional in 10.8) + pub use_reflection_for_balls: Option, // BREF 87 (became optional in 10.8) + pub brst: Option, // BRST (in use in 10.01) + pub playfield_reflection_strength: f32, // PLST 88 + pub use_trail_for_balls: Option, // BTRA 89 (became optional in 10.8) + pub ball_decal_mode: bool, // BDMO 90 pub ball_playfield_reflection_strength: Option, // BPRS 91 (was missing in 10.01) pub default_bulb_intensity_scale_on_ball: Option, // DBIS 92 (added in 10.?) /// this has a special quantization, /// See [`Self::get_ball_trail_strength`] and [`Self::set_ball_trail_strength`] pub ball_trail_strength: Option, // BTST 93 (became optional in 10.8) - pub user_detail_level: Option, // ARAC 94 (became optional in 10.8) - pub overwrite_global_detail_level: Option, // OGAC 95 (became optional in 10.8) - pub overwrite_global_day_night: Option, // OGDN 96 (became optional in 10.8) - pub show_grid: bool, // GDAC 97 - pub reflect_elements_on_playfield: Option, // REOP 98 (became optional in 10.8) - pub use_aal: Option, // UAAL 99 (became optional in 10.8) - pub use_fxaa: Option, // UFXA 100 (became optional in 10.8) - pub use_ao: Option, // UAOC 101 (became optional in 10.8) - pub use_ssr: Option, // USSR 102 (added in 10.?) - pub tone_mapper: Option, // TMAP 102.5 (added in 10.8) - pub bloom_strength: f32, // BLST 103 - pub materials_size: u32, // MASI 104 + pub user_detail_level: Option, // ARAC 94 (became optional in 10.8) + pub overwrite_global_detail_level: Option, // OGAC 95 (became optional in 10.8) + pub overwrite_global_day_night: Option, // OGDN 96 (became optional in 10.8) + pub show_grid: bool, // GDAC 97 + pub reflect_elements_on_playfield: Option, // REOP 98 (became optional in 10.8) + pub use_aal: Option, // UAAL 99 (became optional in 10.8) + pub use_fxaa: Option, // UFXA 100 (became optional in 10.8) + pub use_ao: Option, // UAOC 101 (became optional in 10.8) + pub use_ssr: Option, // USSR 102 (added in 10.?) + pub tone_mapper: Option, // TMAP 102.5 (added in 10.8) + pub bloom_strength: f32, // BLST 103 + pub materials_size: u32, // MASI 104 /// Legacy material saving for backward compatibility pub materials_old: Vec, // MATE 105 (only for <10.8) /// Legacy material saving for backward compatibility @@ -242,8 +440,9 @@ pub(crate) struct GameDataJson { pub top: f32, pub right: f32, pub bottom: f32, - pub clmo: Option, - pub bg_view_mode_desktop: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub camera_layout_mode: Option, + pub bg_view_mode_desktop: Option, pub bg_rotation_desktop: f32, pub bg_inclination_desktop: f32, pub bg_layback_desktop: f32, @@ -263,7 +462,7 @@ pub(crate) struct GameDataJson { pub bg_window_bottom_x_offset_desktop: Option, pub bg_window_bottom_y_offset_desktop: Option, pub bg_window_bottom_z_offset_desktop: Option, - pub bg_view_mode_fullscreen: Option, + pub bg_view_mode_fullscreen: Option, pub bg_rotation_fullscreen: f32, pub bg_inclination_fullscreen: f32, pub bg_layback_fullscreen: f32, @@ -282,7 +481,7 @@ pub(crate) struct GameDataJson { pub bg_window_bottom_x_offset_fullscreen: Option, pub bg_window_bottom_y_offset_fullscreen: Option, pub bg_window_bottom_z_offset_fullscreen: Option, - pub bg_view_mode_full_single_screen: Option, + pub bg_view_mode_full_single_screen: Option, pub bg_rotation_full_single_screen: Option, pub bg_inclination_full_single_screen: Option, pub bg_layback_full_single_screen: Option, @@ -341,10 +540,10 @@ pub(crate) struct GameDataJson { pub glass_bottom_height: Option, pub table_height: Option, pub playfield_material: String, - pub backdrop_color: ColorNoAlpha, + pub backdrop_color: Color, pub global_difficulty: f32, - pub light_ambient: u32, - pub light0_emission: u32, + pub light_ambient: Color, + pub light0_emission: Color, pub light_height: f32, pub light_range: f32, pub light_emission_scale: f32, @@ -372,10 +571,10 @@ pub(crate) struct GameDataJson { pub use_fxaa: Option, pub use_ao: Option, pub use_ssr: Option, - pub tone_mapper: Option, + pub tone_mapper: Option, pub bloom_strength: f32, pub name: String, - pub custom_colors: [ColorJson; 16], + pub custom_colors: [Color; 16], pub protection_data: Option>, //pub code: StringWithEncoding, pub locked: Option, @@ -385,20 +584,12 @@ pub(crate) struct GameDataJson { impl GameDataJson { pub fn to_game_data(&self) -> GameData { - let custom_colors: [Color; 16] = self - .custom_colors - .iter() - .map(|c| c.to_color()) - .collect::>() - .try_into() - .unwrap(); - GameData { left: self.left, top: self.top, right: self.right, bottom: self.bottom, - clmo: self.clmo, + camera_layout_mode: self.camera_layout_mode, bg_view_mode_desktop: self.bg_view_mode_desktop, bg_rotation_desktop: self.bg_rotation_desktop, bg_inclination_desktop: self.bg_inclination_desktop, @@ -559,7 +750,7 @@ impl GameDataJson { // this data is loaded from a separate file collections_size: 0, name: self.name.clone(), - custom_colors, + custom_colors: self.custom_colors, protection_data: self.protection_data.clone(), code: StringWithEncoding::empty(), locked: self.locked, @@ -568,14 +759,12 @@ impl GameDataJson { } pub fn from_game_data(game_data: &GameData) -> GameDataJson { - let custom_colors: [ColorJson; 16] = - game_data.custom_colors.map(|c| ColorJson::from_color(&c)); GameDataJson { left: game_data.left, top: game_data.top, right: game_data.right, bottom: game_data.bottom, - clmo: game_data.clmo, + camera_layout_mode: game_data.camera_layout_mode, bg_view_mode_desktop: game_data.bg_view_mode_desktop, bg_rotation_desktop: game_data.bg_rotation_desktop, bg_inclination_desktop: game_data.bg_inclination_desktop, @@ -722,7 +911,7 @@ impl GameDataJson { tone_mapper: game_data.tone_mapper, bloom_strength: game_data.bloom_strength, name: game_data.name.clone(), - custom_colors, + custom_colors: game_data.custom_colors, protection_data: game_data.protection_data.clone(), // code: game_data.code.clone(), locked: game_data.locked, @@ -753,7 +942,7 @@ impl Default for GameData { top: 0.0, right: 952.0, bottom: 2162.0, - clmo: None, + camera_layout_mode: None, bg_view_mode_desktop: None, bg_rotation_desktop: 0.0, bg_inclination_desktop: 0.0, @@ -827,10 +1016,10 @@ impl Default for GameData { glass_bottom_height: None, // new default 210 for both table_height: None, //0.0, playfield_material: "".to_string(), - backdrop_color: ColorNoAlpha::from_rgb(0x626E8E), // Waikawa/Bluish Gray + backdrop_color: Color::from_rgb(0x626E8E), // Waikawa/Bluish Gray global_difficulty: 0.2, - light_ambient: 0x000000ff, // TODO what is the format for all these? - light0_emission: 0xfffff0ff, // TODO is this correct? + light_ambient: Color::rgb((0.1 * 255.) as u8, (0.1 * 255.) as u8, (0.1 * 255.) as u8), + light0_emission: Color::rgb((0.4 * 255.) as u8, (0.4 * 255.) as u8, (0.4 * 255.) as u8), light_height: 5000.0, light_range: 4000000.0, light_emission_scale: 4000000.0, @@ -918,7 +1107,7 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec writer.write_tagged_f32("TOPX", gamedata.top); writer.write_tagged_f32("RGHT", gamedata.right); writer.write_tagged_f32("BOTM", gamedata.bottom); - if let Some(clmo) = gamedata.clmo { + if let Some(clmo) = gamedata.camera_layout_mode { writer.write_tagged_u32("CLMO", clmo); } @@ -927,8 +1116,8 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec writer.write_tagged_bool("EFSS", efss); } } - if let Some(vsm0) = gamedata.bg_view_mode_desktop { - writer.write_tagged_u32("VSM0", vsm0); + if let Some(vsm0) = &gamedata.bg_view_mode_desktop { + writer.write_tagged_u32("VSM0", vsm0.into()); } writer.write_tagged_f32("ROTA", gamedata.bg_rotation_desktop); writer.write_tagged_f32("INCL", gamedata.bg_inclination_desktop); @@ -966,8 +1155,8 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec writer.write_tagged_f32("WBZ0", wbz0); } - if let Some(vsm1) = gamedata.bg_view_mode_fullscreen { - writer.write_tagged_u32("VSM1", vsm1); + if let Some(vsm1) = &gamedata.bg_view_mode_fullscreen { + writer.write_tagged_u32("VSM1", vsm1.into()); } if version.u32() < 1080 || gamedata.is_10_8_0_beta1_to_beta4 { @@ -1010,8 +1199,8 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec writer.write_tagged_f32("WBZ1", wbz1); } - if let Some(vsm2) = gamedata.bg_view_mode_full_single_screen { - writer.write_tagged_u32("VSM2", vsm2); + if let Some(vsm2) = &gamedata.bg_view_mode_full_single_screen { + writer.write_tagged_u32("VSM2", vsm2.into()); } if let Some(rofs) = gamedata.bg_rotation_full_single_screen { writer.write_tagged_f32("ROFS", rofs); @@ -1125,10 +1314,10 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec writer.write_tagged_f32("TBLH", table_height); } writer.write_tagged_string("PLMA", &gamedata.playfield_material); - writer.write_tagged_with("BCLR", &gamedata.backdrop_color, ColorNoAlpha::biff_write); + writer.write_tagged_with("BCLR", &gamedata.backdrop_color, Color::biff_write); writer.write_tagged_f32("TDFT", gamedata.global_difficulty); - writer.write_tagged_u32("LZAM", gamedata.light_ambient); - writer.write_tagged_u32("LZDI", gamedata.light0_emission); + writer.write_tagged_with("LZAM", &gamedata.light_ambient, Color::biff_write); + writer.write_tagged_with("LZDI", &gamedata.light0_emission, Color::biff_write); writer.write_tagged_f32("LZHI", gamedata.light_height); writer.write_tagged_f32("LZRA", gamedata.light_range); writer.write_tagged_f32("LIES", gamedata.light_emission_scale); @@ -1188,8 +1377,8 @@ pub fn write_all_gamedata_records(gamedata: &GameData, version: &Version) -> Vec if let Some(ussr) = gamedata.use_ssr { writer.write_tagged_i32("USSR", ussr); } - if let Some(tmap) = gamedata.tone_mapper { - writer.write_tagged_i32("TMAP", tmap); + if let Some(tmap) = &gamedata.tone_mapper { + writer.write_tagged_u32("TMAP", tmap.into()); } writer.write_tagged_f32("BLST", gamedata.bloom_strength); writer.write_tagged_u32("MASI", gamedata.materials_size); @@ -1261,8 +1450,8 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData { "TOPX" => gamedata.top = reader.get_f32(), "RGHT" => gamedata.right = reader.get_f32(), "BOTM" => gamedata.bottom = reader.get_f32(), - "CLMO" => gamedata.clmo = Some(reader.get_u32()), - "VSM0" => gamedata.bg_view_mode_desktop = Some(reader.get_u32()), + "CLMO" => gamedata.camera_layout_mode = Some(reader.get_u32()), + "VSM0" => gamedata.bg_view_mode_desktop = Some(reader.get_u32().into()), "ROTA" => gamedata.bg_rotation_desktop = reader.get_f32(), "INCL" => gamedata.bg_inclination_desktop = reader.get_f32(), "LAYB" => gamedata.bg_layback_desktop = reader.get_f32(), @@ -1287,7 +1476,7 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData { "WBX0" => gamedata.bg_window_bottom_x_offset_desktop = Some(reader.get_f32()), "WBY0" => gamedata.bg_window_bottom_y_offset_desktop = Some(reader.get_f32()), "WBZ0" => gamedata.bg_window_bottom_z_offset_desktop = Some(reader.get_f32()), - "VSM1" => gamedata.bg_view_mode_fullscreen = Some(reader.get_u32()), + "VSM1" => gamedata.bg_view_mode_fullscreen = Some(reader.get_u32().into()), "ROTF" => gamedata.bg_rotation_fullscreen = reader.get_f32(), "INCF" => gamedata.bg_inclination_fullscreen = reader.get_f32(), "LAYF" => gamedata.bg_layback_fullscreen = reader.get_f32(), @@ -1306,7 +1495,7 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData { "WBX1" => gamedata.bg_window_bottom_x_offset_fullscreen = Some(reader.get_f32()), "WBY1" => gamedata.bg_window_bottom_y_offset_fullscreen = Some(reader.get_f32()), "WBZ1" => gamedata.bg_window_bottom_z_offset_fullscreen = Some(reader.get_f32()), - "VSM2" => gamedata.bg_view_mode_full_single_screen = Some(reader.get_u32()), + "VSM2" => gamedata.bg_view_mode_full_single_screen = Some(reader.get_u32().into()), "ROFS" => gamedata.bg_rotation_full_single_screen = Some(reader.get_f32()), "INFS" => gamedata.bg_inclination_full_single_screen = Some(reader.get_f32()), "LAFS" => gamedata.bg_layback_full_single_screen = Some(reader.get_f32()), @@ -1373,10 +1562,10 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData { "GLAB" => gamedata.glass_bottom_height = Some(reader.get_f32()), "TBLH" => gamedata.table_height = Some(reader.get_f32()), "PLMA" => gamedata.playfield_material = reader.get_string(), - "BCLR" => gamedata.backdrop_color = ColorNoAlpha::biff_read(reader), + "BCLR" => gamedata.backdrop_color = Color::biff_read(reader), "TDFT" => gamedata.global_difficulty = reader.get_f32(), - "LZAM" => gamedata.light_ambient = reader.get_u32(), - "LZDI" => gamedata.light0_emission = reader.get_u32(), + "LZAM" => gamedata.light_ambient = Color::biff_read(reader), + "LZDI" => gamedata.light0_emission = Color::biff_read(reader), "LZHI" => gamedata.light_height = reader.get_f32(), "LZRA" => gamedata.light_range = reader.get_f32(), "LIES" => gamedata.light_emission_scale = reader.get_f32(), @@ -1407,7 +1596,7 @@ pub fn read_all_gamedata_records(input: &[u8], version: &Version) -> GameData { "UFXA" => gamedata.use_fxaa = Some(reader.get_i32()), "UAOC" => gamedata.use_ao = Some(reader.get_i32()), "USSR" => gamedata.use_ssr = Some(reader.get_i32()), - "TMAP" => gamedata.tone_mapper = Some(reader.get_i32()), + "TMAP" => gamedata.tone_mapper = Some(reader.get_u32().into()), "BLST" => gamedata.bloom_strength = reader.get_f32(), "MASI" => gamedata.materials_size = reader.get_u32(), "MATE" => { @@ -1483,7 +1672,7 @@ fn read_colors(data: Vec) -> [Color; 16] { let mut colors = Vec::new(); let mut buff = BytesMut::from(data.as_slice()); for _ in 0..16 { - let color = Color::new_bgr(buff.get_u32_le()); + let color = Color::from_win_color(buff.get_u32_le()); colors.push(color); } <[Color; 16]>::try_from(colors).unwrap() @@ -1492,7 +1681,7 @@ fn read_colors(data: Vec) -> [Color; 16] { fn write_colors(colors: &[Color; 16]) -> Vec { let mut bytes = BytesMut::new(); for color in colors { - bytes.put_u32_le(color.bgr()); + bytes.put_u32_le(color.to_win_color()); } bytes.to_vec() } @@ -1520,8 +1709,8 @@ mod tests { right: 2.0, top: 3.0, bottom: 4.0, - clmo: None, - bg_view_mode_desktop: Some(1), + camera_layout_mode: None, + bg_view_mode_desktop: Faker.fake(), bg_rotation_desktop: 1.0, bg_inclination_desktop: 2.0, bg_layback_desktop: 3.0, @@ -1593,10 +1782,10 @@ mod tests { glass_bottom_height: Some(123.0), table_height: Some(12.0), playfield_material: "material_pf".to_string(), - backdrop_color: ColorNoAlpha::rgb(0x11, 0x22, 0x33), + backdrop_color: Color::rgb(0x11, 0x22, 0x33), global_difficulty: 0.3, - light_ambient: 0x11223344, - light0_emission: 0xaabbccdd, + light_ambient: Faker.fake(), + light0_emission: Faker.fake(), light_height: 4000.0, light_range: 50000.0, light_emission_scale: 1.2, @@ -1624,7 +1813,7 @@ mod tests { use_fxaa: Some(-2), use_ao: Some(-3), use_ssr: Some(-4), - tone_mapper: Some(1), // TM_TONY_MC_MAPFACE + tone_mapper: Faker.fake(), bloom_strength: 0.3, materials_size: 0, gameitems_size: 0, @@ -1648,7 +1837,7 @@ mod tests { bg_window_bottom_x_offset_desktop: None, bg_window_bottom_y_offset_desktop: None, bg_window_bottom_z_offset_desktop: None, - bg_view_mode_fullscreen: None, + bg_view_mode_fullscreen: Faker.fake(), bg_view_horizontal_offset_fullscreen: None, bg_view_vertical_offset_fullscreen: None, bg_window_top_x_offset_fullscreen: None, @@ -1657,7 +1846,7 @@ mod tests { bg_window_bottom_x_offset_fullscreen: None, bg_window_bottom_y_offset_fullscreen: None, bg_window_bottom_z_offset_fullscreen: None, - bg_view_mode_full_single_screen: None, + bg_view_mode_full_single_screen: Faker.fake(), bg_view_horizontal_offset_full_single_screen: None, bg_view_vertical_offset_full_single_screen: None, bg_window_top_x_offset_full_single_screen: None, @@ -1675,4 +1864,15 @@ mod tests { assert_eq!(gamedata, read_game_data); } + + #[test] + fn test_write_read_colors() { + let mut colors = [Color::RED; 16]; + for color in &mut colors { + *color = Faker.fake(); + } + let bytes = write_colors(&colors); + let read_colors = read_colors(bytes); + assert_eq!(colors, read_colors); + } } diff --git a/src/vpx/gameitem/decal.rs b/src/vpx/gameitem/decal.rs index 6f1cd8d..d92d2f1 100644 --- a/src/vpx/gameitem/decal.rs +++ b/src/vpx/gameitem/decal.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, color::Color, @@ -204,7 +203,7 @@ struct DecalJson { text: String, decal_type: DecalType, material: String, - color: ColorJson, + color: Color, sizing_type: SizingType, vertical_text: bool, backglass: bool, @@ -224,7 +223,7 @@ impl DecalJson { text: decal.text.clone(), decal_type: decal.decal_type.clone(), material: decal.material.clone(), - color: ColorJson::from_color(&decal.color), + color: decal.color, sizing_type: decal.sizing_type.clone(), vertical_text: decal.vertical_text, backglass: decal.backglass, @@ -244,7 +243,7 @@ impl DecalJson { text: self.text.clone(), decal_type: self.decal_type.clone(), material: self.material.clone(), - color: self.color.to_color(), + color: self.color, sizing_type: self.sizing_type.clone(), vertical_text: self.vertical_text, backglass: self.backglass, @@ -274,7 +273,7 @@ impl Default for Decal { text: Default::default(), decal_type: DecalType::Image, material: Default::default(), - color: Color::new_bgr(0x000000), + color: Color::from_rgb(0x000000), sizing_type: SizingType::ManualSize, vertical_text: false, backglass: false, @@ -354,7 +353,7 @@ impl BiffRead for Decal { decal.material = reader.get_string(); } "COLR" => { - decal.color = Color::biff_read_bgr(reader); + decal.color = Color::biff_read(reader); } "SIZE" => { decal.sizing_type = reader.get_u32().into(); @@ -409,7 +408,7 @@ impl BiffWrite for Decal { writer.write_tagged_string("TEXT", &self.text); writer.write_tagged_u32("TYPE", (&self.decal_type).into()); writer.write_tagged_string("MATR", &self.material); - writer.write_tagged_with("COLR", &self.color, Color::biff_write_bgr); + writer.write_tagged_with("COLR", &self.color, Color::biff_write); writer.write_tagged_u32("SIZE", (&self.sizing_type).into()); writer.write_tagged_bool("VERT", self.vertical_text); writer.write_tagged_bool("BGLS", self.backglass); @@ -453,7 +452,7 @@ mod tests { text: "text".to_owned(), decal_type: Faker.fake(), material: "material".to_owned(), - color: Color::new_bgr(0x010203), + color: Faker.fake(), sizing_type: Faker.fake(), vertical_text: true, backglass: true, diff --git a/src/vpx/gameitem/flasher.rs b/src/vpx/gameitem/flasher.rs index d1e57b4..778a06f 100644 --- a/src/vpx/gameitem/flasher.rs +++ b/src/vpx/gameitem/flasher.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::gameitem::ramp_image_alignment::RampImageAlignment; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, @@ -131,6 +130,7 @@ pub struct Flasher { pub light_map: Option, // LMAP added in 10.8 pub drag_points: Vec, + // these are shared between all items pub is_locked: bool, pub editor_layer: u32, @@ -139,6 +139,42 @@ pub struct Flasher { pub editor_layer_visibility: Option, } +impl Default for Flasher { + fn default() -> Self { + Self { + height: 50.0, + pos_x: 0.0, + pos_y: 0.0, + rot_x: 0.0, + rot_y: 0.0, + rot_z: 0.0, + color: Color::WHITE, + is_timer_enabled: false, + timer_interval: 0, + name: "".to_string(), + image_a: "".to_string(), + image_b: "".to_string(), + alpha: 100, + modulate_vs_add: 0.9, + is_visible: true, + add_blend: false, + is_dmd: None, + display_texture: false, + depth_bias: 0.0, + image_alignment: RampImageAlignment::Wrap, + filter: Filter::Overlay, + filter_amount: 100, + light_map: None, + drag_points: vec![], + + is_locked: false, + editor_layer: 0, + editor_layer_name: None, + editor_layer_visibility: None, + } + } +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct FlasherJson { height: f32, @@ -147,7 +183,7 @@ pub(crate) struct FlasherJson { rot_x: f32, rot_y: f32, rot_z: f32, - color: ColorJson, + color: Color, is_timer_enabled: bool, timer_interval: i32, name: String, @@ -176,7 +212,7 @@ impl FlasherJson { rot_x: flasher.rot_x, rot_y: flasher.rot_y, rot_z: flasher.rot_z, - color: ColorJson::from_color(&flasher.color), + color: flasher.color, is_timer_enabled: flasher.is_timer_enabled, timer_interval: flasher.timer_interval, name: flasher.name.clone(), @@ -204,7 +240,7 @@ impl FlasherJson { rot_x: self.rot_x, rot_y: self.rot_y, rot_z: self.rot_z, - color: self.color.to_color(), + color: self.color, is_timer_enabled: self.is_timer_enabled, timer_interval: self.timer_interval, name: self.name.clone(), @@ -255,37 +291,7 @@ impl<'de> Deserialize<'de> for Flasher { impl BiffRead for Flasher { fn biff_read(reader: &mut BiffReader<'_>) -> Self { - let mut height = 50.0; - let mut pos_x = Default::default(); - let mut pos_y = Default::default(); - let mut rot_x = Default::default(); - let mut rot_y = Default::default(); - let mut rot_z = Default::default(); - let mut color = Color::new_bgr(0xfffffff); - let mut is_timer_enabled = Default::default(); - let mut timer_interval = Default::default(); - let mut name = Default::default(); - let mut image_a = Default::default(); - let mut image_b = Default::default(); - let mut alpha = 100; - let mut modulate_vs_add = 0.9; - let mut is_visible = true; - let mut add_blend = Default::default(); - let mut is_dmd = None; - let mut display_texture = Default::default(); - let mut depth_bias = Default::default(); - let mut image_alignment = RampImageAlignment::Wrap; - let mut filter = Filter::Overlay; - let mut filter_amount: u32 = 100; - let mut light_map: Option = None; - - // these are shared between all items - let mut is_locked: bool = false; - let mut editor_layer: u32 = Default::default(); - let mut editor_layer_name: Option = None; - let mut editor_layer_visibility: Option = None; - - let mut drag_points: Vec = Default::default(); + let mut flasher = Flasher::default(); loop { reader.next(biff::WARN); @@ -296,91 +302,91 @@ impl BiffRead for Flasher { let tag_str = tag.as_str(); match tag_str { "FHEI" => { - height = reader.get_f32(); + flasher.height = reader.get_f32(); } "FLAX" => { - pos_x = reader.get_f32(); + flasher.pos_x = reader.get_f32(); } "FLAY" => { - pos_y = reader.get_f32(); + flasher.pos_y = reader.get_f32(); } "FROX" => { - rot_x = reader.get_f32(); + flasher.rot_x = reader.get_f32(); } "FROY" => { - rot_y = reader.get_f32(); + flasher.rot_y = reader.get_f32(); } "FROZ" => { - rot_z = reader.get_f32(); + flasher.rot_z = reader.get_f32(); } "COLR" => { - color = Color::biff_read_bgr(reader); + flasher.color = Color::biff_read(reader); } "TMON" => { - is_timer_enabled = reader.get_bool(); + flasher.is_timer_enabled = reader.get_bool(); } "TMIN" => { - timer_interval = reader.get_i32(); + flasher.timer_interval = reader.get_i32(); } "NAME" => { - name = reader.get_wide_string(); + flasher.name = reader.get_wide_string(); } "IMAG" => { - image_a = reader.get_string(); + flasher.image_a = reader.get_string(); } "IMAB" => { - image_b = reader.get_string(); + flasher.image_b = reader.get_string(); } "FALP" => { - alpha = reader.get_i32(); + flasher.alpha = reader.get_i32(); } "MOVA" => { - modulate_vs_add = reader.get_f32(); + flasher.modulate_vs_add = reader.get_f32(); } "FVIS" => { - is_visible = reader.get_bool(); + flasher.is_visible = reader.get_bool(); } "DSPT" => { - display_texture = reader.get_bool(); + flasher.display_texture = reader.get_bool(); } "ADDB" => { - add_blend = reader.get_bool(); + flasher.add_blend = reader.get_bool(); } "IDMD" => { - is_dmd = Some(reader.get_bool()); + flasher.is_dmd = Some(reader.get_bool()); } "FLDB" => { - depth_bias = reader.get_f32(); + flasher.depth_bias = reader.get_f32(); } "ALGN" => { - image_alignment = reader.get_u32().into(); + flasher.image_alignment = reader.get_u32().into(); } "FILT" => { - filter = reader.get_u32().into(); + flasher.filter = reader.get_u32().into(); } "FIAM" => { - filter_amount = reader.get_u32(); + flasher.filter_amount = reader.get_u32(); } "LMAP" => { - light_map = Some(reader.get_string()); + flasher.light_map = Some(reader.get_string()); } // shared "LOCK" => { - is_locked = reader.get_bool(); + flasher.is_locked = reader.get_bool(); } "LAYR" => { - editor_layer = reader.get_u32(); + flasher.editor_layer = reader.get_u32(); } "LANR" => { - editor_layer_name = Some(reader.get_string()); + flasher.editor_layer_name = Some(reader.get_string()); } "LVIS" => { - editor_layer_visibility = Some(reader.get_bool()); + flasher.editor_layer_visibility = Some(reader.get_bool()); } "DPNT" => { let point = DragPoint::biff_read(reader); - drag_points.push(point); + flasher.drag_points.push(point); } _ => { println!( @@ -392,36 +398,7 @@ impl BiffRead for Flasher { } } } - Flasher { - height, - pos_x, - pos_y, - rot_x, - rot_y, - rot_z, - color, - is_timer_enabled, - timer_interval, - name, - image_a, - image_b, - alpha, - modulate_vs_add, - is_visible, - add_blend, - is_dmd, - display_texture, - depth_bias, - image_alignment, - filter, - filter_amount, - light_map, - is_locked, - editor_layer, - editor_layer_name, - editor_layer_visibility, - drag_points, - } + flasher } } @@ -433,7 +410,7 @@ impl BiffWrite for Flasher { writer.write_tagged_f32("FROX", self.rot_x); writer.write_tagged_f32("FROY", self.rot_y); writer.write_tagged_f32("FROZ", self.rot_z); - writer.write_tagged_with("COLR", &self.color, Color::biff_write_bgr); + writer.write_tagged_with("COLR", &self.color, Color::biff_write); writer.write_tagged_bool("TMON", self.is_timer_enabled); writer.write_tagged_i32("TMIN", self.timer_interval); writer.write_tagged_wide_string("NAME", &self.name); @@ -492,7 +469,7 @@ mod tests { rot_x: rng.gen(), rot_y: rng.gen(), rot_z: rng.gen(), - color: Color::new_bgr(rng.gen()), + color: Faker.fake(), is_timer_enabled: rng.gen(), timer_interval: rng.gen(), name: "test name".to_string(), diff --git a/src/vpx/gameitem/light.rs b/src/vpx/gameitem/light.rs index 9988d6b..35a8f93 100644 --- a/src/vpx/gameitem/light.rs +++ b/src/vpx/gameitem/light.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::json::F32WithNanInf; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, @@ -246,8 +245,8 @@ struct LightJson { falloff_power: f32, state_u32: u32, state: Option, - color: ColorJson, - color2: ColorJson, + color: Color, + color2: Color, is_timer_enabled: bool, timer_interval: u32, blink_pattern: String, @@ -284,8 +283,8 @@ impl LightJson { falloff_power: light.falloff_power, state_u32: light.state_u32, state: light.state, - color: ColorJson::from_color(&light.color), - color2: ColorJson::from_color(&light.color2), + color: light.color, + color2: light.color2, is_timer_enabled: light.is_timer_enabled, timer_interval: light.timer_interval, blink_pattern: light.blink_pattern.clone(), @@ -322,8 +321,8 @@ impl LightJson { falloff_power: self.falloff_power, state_u32: self.state_u32, state: self.state, - color: self.color.to_color(), - color2: self.color2.to_color(), + color: self.color, + color2: self.color2, is_timer_enabled: self.is_timer_enabled, timer_interval: self.timer_interval, blink_pattern: self.blink_pattern.clone(), @@ -390,9 +389,10 @@ impl Default for Light { let falloff_power: f32 = Default::default(); let status: u32 = Default::default(); let state: Option = None; - // should these not have alpha ff? - let color: Color = Color::from_argb(0xffff00); - let color2: Color = Color::from_argb(0xffffff); + // Default to 2700K incandescent bulb + let color: Color = Color::rgb(255, 169, 87); + // Default to 2700K incandescent bulb (burst is useless since VPX is HDR) + let color2: Color = Color::rgb(255, 169, 87); let is_timer_enabled: bool = false; let timer_interval: u32 = Default::default(); let blink_pattern: String = "10".to_owned(); @@ -481,8 +481,8 @@ impl BiffRead for Light { "FAPO" => light.falloff_power = reader.get_f32(), "STAT" => light.state_u32 = reader.get_u32(), "STTF" => light.state = Some(reader.get_f32()), - "COLR" => light.color = Color::biff_read_bgr(reader), - "COL2" => light.color2 = Color::biff_read_bgr(reader), + "COLR" => light.color = Color::biff_read(reader), + "COL2" => light.color2 = Color::biff_read(reader), "TMON" => light.is_timer_enabled = reader.get_bool(), "TMIN" => light.timer_interval = reader.get_u32(), "BPAT" => light.blink_pattern = reader.get_string(), @@ -545,8 +545,8 @@ impl BiffWrite for Light { if let Some(state) = self.state { writer.write_tagged_f32("STTF", state); } - writer.write_tagged_with("COLR", &self.color, Color::biff_write_bgr); - writer.write_tagged_with("COL2", &self.color2, Color::biff_write_bgr); + writer.write_tagged_with("COLR", &self.color, Color::biff_write); + writer.write_tagged_with("COL2", &self.color2, Color::biff_write); writer.write_tagged_bool("TMON", self.is_timer_enabled); writer.write_tagged_u32("TMIN", self.timer_interval); writer.write_tagged_string("BPAT", &self.blink_pattern); @@ -616,8 +616,8 @@ mod tests { falloff_power: 3.0, state_u32: 4, state: Some(5.0), - color: Color::from_argb(0x123456), - color2: Color::from_argb(0x654321), + color: Faker.fake(), + color2: Faker.fake(), is_timer_enabled: true, timer_interval: 7, blink_pattern: "test pattern".to_string(), diff --git a/src/vpx/gameitem/primitive.rs b/src/vpx/gameitem/primitive.rs index b76146e..e68e4d9 100644 --- a/src/vpx/gameitem/primitive.rs +++ b/src/vpx/gameitem/primitive.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, color::Color, @@ -82,7 +81,7 @@ struct PrimitiveJson { sides: u32, name: String, material: String, - side_color: ColorJson, + side_color: Color, is_visible: bool, draw_textures_inside: bool, hit_event: bool, @@ -113,7 +112,7 @@ struct PrimitiveJson { add_blend: Option, use_depth_mask: Option, alpha: Option, - color: Option, + color: Option, light_map: Option, reflection_probe: Option, reflection_strength: Option, @@ -132,7 +131,7 @@ impl PrimitiveJson { sides: primitive.sides, name: primitive.name.clone(), material: primitive.material.clone(), - side_color: ColorJson::from_color(&primitive.side_color), + side_color: primitive.side_color, is_visible: primitive.is_visible, draw_textures_inside: primitive.draw_textures_inside, hit_event: primitive.hit_event, @@ -173,7 +172,7 @@ impl PrimitiveJson { add_blend: primitive.add_blend, use_depth_mask: primitive.use_depth_mask, alpha: primitive.alpha, - color: primitive.color.map(|c| ColorJson::from_color(&c)), + color: primitive.color, light_map: primitive.light_map.clone(), reflection_probe: primitive.reflection_probe.clone(), reflection_strength: primitive.reflection_strength, @@ -191,7 +190,7 @@ impl PrimitiveJson { sides: self.sides, name: self.name.clone(), material: self.material.clone(), - side_color: self.side_color.to_color(), + side_color: self.side_color, is_visible: self.is_visible, draw_textures_inside: self.draw_textures_inside, hit_event: self.hit_event, @@ -230,7 +229,7 @@ impl PrimitiveJson { add_blend: self.add_blend, use_depth_mask: self.use_depth_mask, alpha: self.alpha, - color: self.color.as_ref().map(ColorJson::to_color), + color: self.color, light_map: self.light_map.clone(), reflection_probe: self.reflection_probe.clone(), reflection_strength: self.reflection_strength, @@ -277,7 +276,7 @@ impl BiffRead for Primitive { let mut sides: u32 = 4; let mut name = Default::default(); let mut material = Default::default(); - let mut side_color = Color::new_bgr(0x0); + let mut side_color = Color::BLACK; let mut is_visible: bool = true; let mut draw_textures_inside: bool = false; let mut hit_event: bool = true; @@ -394,7 +393,7 @@ impl BiffRead for Primitive { material = reader.get_string(); } "SCOL" => { - side_color = Color::biff_read_bgr(reader); + side_color = Color::biff_read(reader); } "TVIS" => { is_visible = reader.get_bool(); @@ -532,7 +531,7 @@ impl BiffRead for Primitive { alpha = Some(reader.get_f32()); } "COLR" => { - color = Some(Color::biff_read_bgr(reader)); + color = Some(Color::biff_read(reader)); } "LMAP" => { light_map = Some(reader.get_string()); @@ -655,7 +654,7 @@ impl BiffWrite for Primitive { writer.write_tagged_u32("SIDS", self.sides); writer.write_tagged_wide_string("NAME", &self.name); writer.write_tagged_string("MATR", &self.material); - writer.write_tagged_with("SCOL", &self.side_color, Color::biff_write_bgr); + writer.write_tagged_with("SCOL", &self.side_color, Color::biff_write); writer.write_tagged_bool("TVIS", self.is_visible); writer.write_tagged_bool("DTXI", self.draw_textures_inside); writer.write_tagged_bool("HTEV", self.hit_event); @@ -753,7 +752,7 @@ impl BiffWrite for Primitive { writer.write_tagged_f32("FALP", alpha); } if let Some(color) = &self.color { - writer.write_tagged_with("COLR", color, Color::biff_write_bgr); + writer.write_tagged_with("COLR", color, Color::biff_write); } if let Some(light_map) = &self.light_map { writer.write_tagged_string("LMAP", light_map); @@ -788,6 +787,7 @@ impl BiffWrite for Primitive { #[cfg(test)] mod tests { use crate::vpx::biff::BiffWriter; + use fake::{Fake, Faker}; use super::*; use pretty_assertions::assert_eq; @@ -805,7 +805,7 @@ mod tests { sides: 1, name: "name".to_string(), material: "material".to_string(), - side_color: Color::new_bgr(0x12345678), + side_color: Faker.fake(), is_visible: rng.gen(), // random bool draw_textures_inside: rng.gen(), @@ -848,7 +848,7 @@ mod tests { add_blend: rng.gen(), use_depth_mask: rng.gen(), alpha: Some(13.0), - color: Some(Color::new_bgr(0x23456789)), + color: Faker.fake(), light_map: Some("light_map".to_string()), reflection_probe: Some("reflection_probe".to_string()), reflection_strength: Some(14.0), diff --git a/src/vpx/gameitem/reel.rs b/src/vpx/gameitem/reel.rs index 7bbf052..2a60bc4 100644 --- a/src/vpx/gameitem/reel.rs +++ b/src/vpx/gameitem/reel.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, color::Color, @@ -43,7 +42,7 @@ pub struct Reel { struct ReelJson { ver1: Vertex2D, ver2: Vertex2D, - back_color: ColorJson, + back_color: Color, is_timer_enabled: bool, timer_interval: u32, is_transparent: bool, @@ -67,7 +66,7 @@ impl ReelJson { Self { ver1: reel.ver1, ver2: reel.ver2, - back_color: ColorJson::from_color(&reel.back_color), + back_color: reel.back_color, is_timer_enabled: reel.is_timer_enabled, timer_interval: reel.timer_interval, is_transparent: reel.is_transparent, @@ -90,7 +89,7 @@ impl ReelJson { Reel { ver1: self.ver1, ver2: self.ver2, - back_color: self.back_color.to_color(), + back_color: self.back_color, is_timer_enabled: self.is_timer_enabled, timer_interval: self.timer_interval, is_transparent: self.is_transparent, @@ -124,7 +123,7 @@ impl Default for Reel { Self { ver1: Vertex2D::default(), ver2: Vertex2D::default(), - back_color: Color::new_bgr(0x404040f), + back_color: Color::rgb(64, 64, 64), is_timer_enabled: false, timer_interval: Default::default(), is_transparent: false, @@ -187,7 +186,7 @@ impl BiffRead for Reel { reel.ver2 = Vertex2D::biff_read(reader); } "CLRB" => { - reel.back_color = Color::biff_read_bgr(reader); + reel.back_color = Color::biff_read(reader); } "TMON" => { reel.is_timer_enabled = reader.get_bool(); @@ -269,7 +268,7 @@ impl BiffWrite for Reel { fn biff_write(&self, writer: &mut biff::BiffWriter) { writer.write_tagged("VER1", &self.ver1); writer.write_tagged("VER2", &self.ver2); - writer.write_tagged_with("CLRB", &self.back_color, Color::biff_write_bgr); + writer.write_tagged_with("CLRB", &self.back_color, Color::biff_write); writer.write_tagged_bool("TMON", self.is_timer_enabled); writer.write_tagged_u32("TMIN", self.timer_interval); writer.write_tagged_bool("TRNS", self.is_transparent); @@ -304,6 +303,7 @@ impl BiffWrite for Reel { #[cfg(test)] mod tests { use crate::vpx::biff::BiffWriter; + use fake::{Fake, Faker}; use super::*; use pretty_assertions::assert_eq; @@ -316,7 +316,7 @@ mod tests { let reel = Reel { ver1: Vertex2D::new(rng.gen(), rng.gen()), ver2: Vertex2D::new(rng.gen(), rng.gen()), - back_color: Color::new_bgr(rng.gen()), + back_color: Faker.fake(), is_timer_enabled: rng.gen(), timer_interval: rng.gen(), is_transparent: rng.gen(), diff --git a/src/vpx/gameitem/textbox.rs b/src/vpx/gameitem/textbox.rs index b8e7d11..3a3c4f9 100644 --- a/src/vpx/gameitem/textbox.rs +++ b/src/vpx/gameitem/textbox.rs @@ -1,4 +1,3 @@ -use crate::vpx::color::ColorJson; use crate::vpx::gameitem::font::FontJson; use crate::vpx::{ biff::{self, BiffRead, BiffReader, BiffWrite}, @@ -134,8 +133,8 @@ pub struct TextBox { struct TextBoxJson { ver1: Vertex2D, ver2: Vertex2D, - back_color: ColorJson, - font_color: ColorJson, + back_color: Color, + font_color: Color, intensity_scale: f32, text: String, is_timer_enabled: bool, @@ -152,8 +151,8 @@ impl TextBoxJson { Self { ver1: textbox.ver1, ver2: textbox.ver2, - back_color: ColorJson::from_color(&textbox.back_color), - font_color: ColorJson::from_color(&textbox.font_color), + back_color: textbox.back_color, + font_color: textbox.font_color, intensity_scale: textbox.intensity_scale, text: textbox.text.clone(), is_timer_enabled: textbox.is_timer_enabled, @@ -170,8 +169,8 @@ impl TextBoxJson { TextBox { ver1: self.ver1, ver2: self.ver2, - back_color: self.back_color.to_color(), - font_color: self.font_color.to_color(), + back_color: self.back_color, + font_color: self.font_color, intensity_scale: self.intensity_scale, text: self.text, is_timer_enabled: self.is_timer_enabled, @@ -217,8 +216,8 @@ impl Default for TextBox { Self { ver1: Vertex2D::default(), ver2: Vertex2D::default(), - back_color: Color::new_bgr(0x000000f), - font_color: Color::new_bgr(0xfffffff), + back_color: Color::BLACK, + font_color: Color::WHITE, intensity_scale: 1.0, text: Default::default(), is_timer_enabled: false, @@ -255,10 +254,10 @@ impl BiffRead for TextBox { textbox.ver2 = Vertex2D::biff_read(reader); } "CLRB" => { - textbox.back_color = Color::biff_read_bgr(reader); + textbox.back_color = Color::biff_read(reader); } "CLRF" => { - textbox.font_color = Color::biff_read_bgr(reader); + textbox.font_color = Color::biff_read(reader); } "INSC" => { textbox.intensity_scale = reader.get_f32(); @@ -319,8 +318,8 @@ impl BiffWrite for TextBox { fn biff_write(&self, writer: &mut biff::BiffWriter) { writer.write_tagged("VER1", &self.ver1); writer.write_tagged("VER2", &self.ver2); - writer.write_tagged_with("CLRB", &self.back_color, Color::biff_write_bgr); - writer.write_tagged_with("CLRF", &self.font_color, Color::biff_write_bgr); + writer.write_tagged_with("CLRB", &self.back_color, Color::biff_write); + writer.write_tagged_with("CLRF", &self.font_color, Color::biff_write); writer.write_tagged_f32("INSC", self.intensity_scale); writer.write_tagged_string("TEXT", &self.text); writer.write_tagged_bool("TMON", self.is_timer_enabled); @@ -363,8 +362,8 @@ mod tests { let textbox = TextBox { ver1: Vertex2D::new(1.0, 2.0), ver2: Vertex2D::new(3.0, 4.0), - back_color: Color::new_bgr(0x1234567), - font_color: Color::new_bgr(0xfedcba9), + back_color: Faker.fake(), + font_color: Faker.fake(), intensity_scale: 1.0, text: "test text".to_string(), is_timer_enabled: true, diff --git a/src/vpx/material.rs b/src/vpx/material.rs index c16e9ae..f18c3ab 100644 --- a/src/vpx/material.rs +++ b/src/vpx/material.rs @@ -1,6 +1,6 @@ use crate::vpx::biff; use crate::vpx::biff::{BiffRead, BiffReader, BiffWrite, BiffWriter}; -use crate::vpx::color::{Color, ColorJson, ColorNoAlpha}; +use crate::vpx::color::Color; use crate::vpx::json::F32WithNanInf; use crate::vpx::math::quantize_u8; use bytes::{Buf, BufMut, BytesMut}; @@ -100,7 +100,7 @@ pub struct SaveMaterial { * Base color of the material * Can be overridden by texture on object itself */ - pub base_color: ColorNoAlpha, + pub base_color: Color, /** * Specular of glossy layer */ @@ -209,9 +209,9 @@ impl From<&Material> for SaveMaterial { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct SaveMaterialJson { name: String, - base_color: ColorNoAlpha, - glossy_color: ColorJson, - clearcoat_color: ColorJson, + base_color: Color, + glossy_color: Color, + clearcoat_color: Color, wrap_lighting: f32, is_metal: bool, roughness: f32, @@ -227,8 +227,8 @@ impl SaveMaterialJson { Self { name: save_material.name.clone(), base_color: save_material.base_color, - glossy_color: ColorJson::from_color(&save_material.glossy_color), - clearcoat_color: ColorJson::from_color(&save_material.clearcoat_color), + glossy_color: save_material.glossy_color, + clearcoat_color: save_material.clearcoat_color, wrap_lighting: save_material.wrap_lighting, is_metal: save_material.is_metal, roughness: save_material.roughness, @@ -243,8 +243,8 @@ impl SaveMaterialJson { SaveMaterial { name: self.name.clone(), base_color: self.base_color, - glossy_color: self.glossy_color.to_color(), - clearcoat_color: self.clearcoat_color.to_color(), + glossy_color: self.glossy_color, + clearcoat_color: self.clearcoat_color, wrap_lighting: self.wrap_lighting, is_metal: self.is_metal, roughness: self.roughness, @@ -285,9 +285,9 @@ impl SaveMaterial { SaveMaterial { name, - base_color: ColorNoAlpha::from_rgb(base_color), - glossy_color: Color::from_argb(glossy_color), - clearcoat_color: Color::from_argb(clearcoat_color), + base_color: Color::from_win_color(base_color), + glossy_color: Color::from_win_color(glossy_color), + clearcoat_color: Color::from_win_color(clearcoat_color), wrap_lighting, is_metal, roughness, @@ -301,9 +301,9 @@ impl SaveMaterial { pub(crate) fn write(&self, bytes: &mut BytesMut) { write_padded_cstring(self.name.as_str(), bytes, MAX_NAME_BUFFER); - bytes.put_u32_le(self.base_color.to_rgb()); - bytes.put_u32_le(self.glossy_color.argb()); - bytes.put_u32_le(self.clearcoat_color.argb()); + bytes.put_u32_le(self.base_color.to_win_color()); + bytes.put_u32_le(self.glossy_color.to_win_color()); + bytes.put_u32_le(self.clearcoat_color.to_win_color()); bytes.put_f32_le(self.wrap_lighting); bytes.put_u8(if self.is_metal { 1 } else { 0 }); bytes.put_u8(0); @@ -468,7 +468,7 @@ pub struct Material { pub edge: f32, pub edge_alpha: f32, pub opacity: f32, - pub base_color: ColorNoAlpha, + pub base_color: Color, pub glossy_color: Color, pub clearcoat_color: Color, // Transparency active in the UI @@ -494,15 +494,15 @@ pub(crate) struct MaterialJson { edge: f32, edge_alpha: f32, opacity: f32, - base_color: ColorNoAlpha, - glossy_color: ColorJson, - clearcoat_color: ColorJson, + base_color: Color, + glossy_color: Color, + clearcoat_color: Color, opacity_active: bool, elasticity: F32WithNanInf, elasticity_falloff: F32WithNanInf, friction: F32WithNanInf, scatter_angle: F32WithNanInf, - refraction_tint: ColorJson, + refraction_tint: Color, } impl MaterialJson { @@ -518,14 +518,14 @@ impl MaterialJson { edge_alpha: material.edge_alpha, opacity: material.opacity, base_color: material.base_color, - glossy_color: ColorJson::from_color(&material.glossy_color), - clearcoat_color: ColorJson::from_color(&material.clearcoat_color), + glossy_color: material.glossy_color, + clearcoat_color: material.clearcoat_color, opacity_active: material.opacity_active, elasticity: material.elasticity.into(), elasticity_falloff: material.elasticity_falloff.into(), friction: material.friction.into(), scatter_angle: material.scatter_angle.into(), - refraction_tint: ColorJson::from_color(&material.refraction_tint), + refraction_tint: material.refraction_tint, } } pub fn to_material(&self) -> Material { @@ -540,14 +540,14 @@ impl MaterialJson { edge_alpha: self.edge_alpha, opacity: self.opacity, base_color: self.base_color, - glossy_color: self.glossy_color.to_color(), - clearcoat_color: self.clearcoat_color.to_color(), + glossy_color: self.glossy_color, + clearcoat_color: self.clearcoat_color, opacity_active: self.opacity_active, elasticity: self.elasticity.into(), elasticity_falloff: self.elasticity_falloff.into(), friction: self.friction.into(), scatter_angle: self.scatter_angle.into(), - refraction_tint: self.refraction_tint.to_color(), + refraction_tint: self.refraction_tint, } } } @@ -563,15 +563,15 @@ impl Default for Material { edge: 1.0, edge_alpha: 1.0, opacity: 1.0, - base_color: ColorNoAlpha::from_rgb(0xB469FF), // Purple / Heliotrope - glossy_color: Color::from_argb(0), - clearcoat_color: Color::from_argb(0), + base_color: Color::from_rgb(0xB469FF), // Purple / Heliotrope + glossy_color: Color::BLACK, + clearcoat_color: Color::BLACK, opacity_active: false, elasticity: 0.0, elasticity_falloff: 0.0, friction: 0.0, scatter_angle: 0.0, - refraction_tint: Color::from_argb(0xFFFFFF), + refraction_tint: Color::WHITE, name: "dummyMaterial".to_string(), } } @@ -581,9 +581,9 @@ impl Default for SaveMaterial { fn default() -> Self { SaveMaterial { name: "dummyMaterial".to_string(), - base_color: ColorNoAlpha::from_rgb(0xB469FF), - glossy_color: Color::from_argb(0), - clearcoat_color: Color::from_argb(0), + base_color: Color::from_rgb(0xB469FF), + glossy_color: Color::BLACK, + clearcoat_color: Color::BLACK, wrap_lighting: 0.0, is_metal: false, roughness: 0.0, @@ -647,10 +647,10 @@ impl BiffRead for Material { "EDGE" => material.edge = reader.get_f32(), "EALP" => material.edge_alpha = reader.get_f32(), "OPAC" => material.opacity = reader.get_f32(), - "BASE" => material.base_color = ColorNoAlpha::biff_read(reader), - "GLOS" => material.glossy_color = Color::from_argb(reader.get_u32()), - "COAT" => material.clearcoat_color = Color::from_argb(reader.get_u32()), - "RTNT" => material.refraction_tint = Color::from_argb(reader.get_u32()), + "BASE" => material.base_color = Color::biff_read(reader), + "GLOS" => material.glossy_color = Color::biff_read(reader), + "COAT" => material.clearcoat_color = Color::biff_read(reader), + "RTNT" => material.refraction_tint = Color::biff_read(reader), "EOPA" => material.opacity_active = reader.get_bool(), "ELAS" => material.elasticity = reader.get_f32(), "ELFO" => material.elasticity_falloff = reader.get_f32(), @@ -681,10 +681,10 @@ impl BiffWrite for Material { writer.write_tagged_f32("EDGE", self.edge); writer.write_tagged_f32("EALP", self.edge_alpha); writer.write_tagged_f32("OPAC", self.opacity); - writer.write_tagged_with("BASE", &self.base_color, ColorNoAlpha::biff_write); - writer.write_tagged_u32("GLOS", self.glossy_color.argb()); - writer.write_tagged_u32("COAT", self.clearcoat_color.argb()); - writer.write_tagged_u32("RTNT", self.refraction_tint.argb()); + writer.write_tagged_with("BASE", &self.base_color, Color::biff_write); + writer.write_tagged_with("GLOS", &self.glossy_color, Color::biff_write); + writer.write_tagged_with("COAT", &self.clearcoat_color, Color::biff_write); + writer.write_tagged_with("RTNT", &self.refraction_tint, Color::biff_write); writer.write_tagged_bool("EOPA", self.opacity_active); writer.write_tagged_f32("ELAS", self.elasticity); writer.write_tagged_f32("ELFO", self.elasticity_falloff); @@ -754,15 +754,15 @@ mod tests { edge: 0.5, edge_alpha: 0.9, opacity: 0.5, - base_color: ColorNoAlpha::from_rgb(0x123456), - glossy_color: Color::from_argb(0x123456), - clearcoat_color: Color::from_argb(0x123456), + base_color: Faker.fake(), + glossy_color: Faker.fake(), + clearcoat_color: Faker.fake(), opacity_active: true, elasticity: 0.5, elasticity_falloff: 0.5, friction: 0.5, scatter_angle: 0.5, - refraction_tint: Color::from_argb(0x123456), + refraction_tint: Faker.fake(), }; let save_material: SaveMaterial = (&material).into(); assert_eq!(save_material.name, "test"); diff --git a/src/vpx/mod.rs b/src/vpx/mod.rs index 3412445..80edd07 100644 --- a/src/vpx/mod.rs +++ b/src/vpx/mod.rs @@ -859,7 +859,7 @@ mod tests { let mac = read_mac(&mut comp)?; let expected = [ - 244, 78, 31, 241, 224, 80, 178, 192, 252, 104, 96, 110, 72, 115, 225, 83, + 51, 168, 77, 130, 248, 220, 159, 228, 203, 214, 8, 8, 148, 140, 213, 34, ]; assert_eq!(mac, expected); Ok(())