diff --git a/resources/Morrowind.fgd b/resources/Morrowind.fgd index 7db199b..7081e8c 100644 --- a/resources/Morrowind.fgd +++ b/resources/Morrowind.fgd @@ -1,12 +1,70 @@ -@BaseClass = paintable +@BaseClass = material [ - emissive_color(color) : "Color emitted by the brush" : "1.0 0 1.0" - ambient_color(color) : "Ambient color of the brush" : "1.0 0 0" - diffuse_color(color) : "Diffuse color of the brush" : "0 1.0 0" - material_alpha(float) : "Material Transparency" : "1.0" + Material_Emissive_color(color) : "Color emitted by the brush" : "1.0 0 0" + Material_Ambient_color(color) : "Ambient color of the brush" : "0 1.0 0" + Material_Diffuse_color(color) : "Diffuse color of the brush" : "0 0 1.0" + + Material_Alpha(float) : "Material Transparency" : "1.0" + Material_Alpha_UseBlend(choices) : "Use alpha blending for this brush" : 0 = + [ + 0 : "False" + 1 : "True" + ] + Material_Alpha_BlendSourceMode(choices) : "Mode to use for alpha blending on the light source": 0 = + [ + 0 : "One" + 2 : "Zero" + 4 : "Source Color" + 6 : "One Minus Source Color" + 8 : "Destination Color" + 10 : "One Minus Destination Color" + 12 : "Source Alpha" + 14 : "One Minus Source Alpha" + 16 : "Destination Alpha" + 18 : "One Minus Destination Alpha" + 20 : "Source Alpha Saturate" + ] + Material_Alpha_BlendDestinationMode(choices) : "Mode to use for alpha blending on the light destination": 0 = + [ + 0 : "One" + 32 : "Zero" + 64 : "Source Color" + 96 : "One Minus Source Color" + 128 : "Destination Color" + 160 : "One Minus Destination Color" + 192 : "Source Alpha" + 224 : "One Minus Source Alpha" + 256 : "Destination Alpha" + 288 : "One Minus Destination Alpha" + 320 : "Source Alpha Saturate" + ] + + Material_Alpha_TestEnable(choices) : "Use alpha testing for this brush" : 0 = + [ + 0 : "False" + 512 : "True" + ] + Material_Alpha_TestFunction(choices) : "Use alpha testing for this brush" : 0 = + [ + 0 : "Always" + 1024 : "Less" + 2048 : "Equal" + 3072 : "Less Than Or Equal" + 4096 : "Greater Than" + 5120 : "Not Equal" + 6144 : "Greater Than Or Equal" + 7168 : "Never" + ] + Material_Alpha_TestThreshold(float) : "Threshold to use against the background when alpha testing": "1.0" + + Material_Alpha_NoSort(choices) : "Disable triangle sorting for this object" : 0 = + [ + 0 : "False" + 8192 : "True" + ] ] -@SolidClass base(paintable) = worldspawn : "World entity" +@SolidClass base(material) = worldspawn : "World entity" [ FakeExterior(choices) : "Use sky for this cell" : 0 = [ @@ -41,7 +99,7 @@ // Begin common classes -@BaseClass base(paintable) = baseObject +@BaseClass base(material) = baseObject [ RefId(string) : "Ref Id for the object" : "agronian guy" Name(string) : "Ingame readable name for the book" : "Tarhiel" @@ -2082,15 +2140,15 @@ ] ] -@PointClass size(-32 -32 -32, 32 32 32) color (0 64 128) base(PointLightData) = Light_Point64: "64 Unit Light" [] +@PointClass size(-32 -32 -32, 32 32 32) iconsprite("Textures/light64.webp") base(PointLightData) = Light_Point64: "64 Unit Light" [] -@PointClass size(-32 -32 -32, 32 32 32) color (0 64 128) base(PointLightData) = Light_Point128: "128 Unit Light" [ Radius(int): "Light Radius" : 128 ] +@PointClass size(-64 -64 -64, 64 64 64) iconsprite("Textures/light128.webp") base(PointLightData) = Light_Point128: "128 Unit Light" [ Radius(int): "Light Radius" : 128 ] -@PointClass size(-32 -32 -32, 32 32 32) color (0 64 128) base(PointLightData) = Light_Point256: "256 Unit Light" [ Radius(int): "Light Radius" : 256 ] +@PointClass size(-128 -128 -128, 128 128 128) iconsprite("Textures/light256.webp") base(PointLightData) = Light_Point256: "256 Unit Light" [ Radius(int): "Light Radius" : 256 ] -@PointClass size(-32 -32 -32, 32 32 32) color (0 64 128) base(PointLightData) = Light_Point512: "512 Unit Light" [ Radius(int): "Light Radius" : 512 ] +@PointClass size(-256 -256 -256, 256 256 256) iconsprite("Textures/light512.webp") base(PointLightData) = Light_Point512: "512 Unit Light" [ Radius(int): "Light Radius" : 512 ] -@PointClass size(-32 -32 -32, 32 32 32) color (0 64 128) base(PointLightData) = Light_Point1024: "1024 Unit Light" [ Radius(int): "Light Radius" : 1024 ] +@PointClass size(-512 -512 -512, 512 512 512) iconsprite("Textures/light1024.webp") base(PointLightData) = Light_Point1024: "1024 Unit Light" [ Radius(int): "Light Radius" : 1024 ] @SolidClass base(baseObject) = world_Container : "Container type" [ Script(string) : "Id of the script used by the object. Optional. Be warned that all instances of this refId will share the script." : "fallingScript" diff --git a/src/brush_ni_node.rs b/src/brush_ni_node.rs index ddadf86..4534a0e 100644 --- a/src/brush_ni_node.rs +++ b/src/brush_ni_node.rs @@ -6,21 +6,155 @@ use tes3::nif::{NiTriShape, NiTriShapeData}; use crate::{map_data::MapData, surfaces, Mesh}; +macro_rules! define_enum_with_fromstr { + ( + $(#[$meta:meta])* + $vis:vis enum $name:ident { + $( + $variant:ident = $value:expr + ),* $(,)? + } + default = $default:ident + ) => { + #[derive(Clone, Copy, Debug, PartialEq)] + $vis enum $name { + $( + $variant = $value, + )* + } + + impl std::str::FromStr for $name { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.parse::() { + $( + Ok($value) => Ok($name::$variant), + )* + Ok(v) => { + println!("WARNING: Falling through to default value {:?} for {} (received {})", + $name::$default, stringify!($name), v); + Ok($name::$default) + }, + Err(_) => Err(format!("Cannot parse '{}' as {}", s, stringify!($name))), + } + } + } + + impl Default for $name { + fn default() -> $name { + $name::$default + } + } + } +} + +define_enum_with_fromstr! { + pub enum BrushSourceBlendMode { + One = 0, + Zero = 2, + SourceColor = 4, + OneMinusSourceColor = 6, + DestinationColor = 8, + OneMinusDestinationColor = 10, + SourceAlpha = 12, + OneMinusSourceAlpha = 14, + DestinationAlpha = 16, + OneMinusDestinationALpha = 18, + SourceAlphaSaturate = 20, + } + default = SourceAlpha +} + +define_enum_with_fromstr! { + pub enum BrushDestinationBlendMode { + One = 0, + Zero = 32, + SourceColor = 64, + OneMinusSourceColor = 96, + DestinationColor = 128, + OneMinusDestinationColor = 160, + SourceAlpha = 192, + OneMinusSourceAlpha = 224, + DestinationAlpha = 256, + OneMinusDestinationALpha = 288, + SourceAlphaSaturate = 320, + } + default = OneMinusSourceAlpha +} + +define_enum_with_fromstr! { + pub enum BrushAlphaTestFunction { + Always = 0, + Less = 1024, + Equal = 2048, + LessThanOrEqual = 3072, + GreaterThan = 4096, + NotEqual = 5120, + GreaterThanOrEqual = 6144, + Never = 7168, + } + default = GreaterThan +} + +define_enum_with_fromstr! { + pub enum BrushUseAlpha { + OFF = 0, + BlendEnable = 1, + TestEnable = 512, + } + default = TestEnable +} + +define_enum_with_fromstr! { + pub enum BrushNoSort { + OFF = 0, + ON = 8196, + } + default = OFF +} + +#[derive(Debug, Default, PartialEq)] +pub struct BrushNiAlphaProps { + pub opacity: Option, + pub use_blend: Option, + pub blend_source_mode: Option, + pub blend_destination_mode: Option, + pub use_test: Option, + pub test_function: Option, + pub test_threshold: Option, + pub no_sort: Option, +} + +impl BrushNiAlphaProps { + pub fn to_flags(&self) -> u16 { + self.use_blend.unwrap_or_default() as u16 + | self.blend_source_mode.unwrap_or_default() as u16 + | self.blend_destination_mode.unwrap_or_default() as u16 + | self.use_test.unwrap_or_default() as u16 + | self.test_function.unwrap_or_default() as u16 + | self.no_sort.unwrap_or_default() as u16 + } +} + +#[derive(Default, PartialEq)] +pub struct BrushNiColorProps { + pub emissive: Option<[f32; 3]>, + pub ambient: Option<[f32; 3]>, + pub diffuse: Option<[f32; 3]>, +} + #[derive(PartialEq)] pub struct BrushNiMatProps { - pub emissive_color: Option<[f32; 3]>, - pub ambient_color: Option<[f32; 3]>, - pub diffuse_color: Option<[f32; 3]>, - pub alpha: Option, + pub color: BrushNiColorProps, + pub alpha: BrushNiAlphaProps, } impl BrushNiMatProps { pub fn default() -> BrushNiMatProps { BrushNiMatProps { - emissive_color: None, - diffuse_color: None, - ambient_color: None, - alpha: None, + color: BrushNiColorProps::default(), + alpha: BrushNiAlphaProps::default(), } } } @@ -99,22 +233,76 @@ impl BrushNiNode { let entity_props = map_data.get_entity_properties(entity_id); - ["ambient", "diffuse", "emissive"] + ["Ambient", "Diffuse", "Emissive"] .iter() .for_each(|color_type| { - if let Some(color) = entity_props.get(&format!("{}_color", color_type)) { + if let Some(color) = entity_props.get(&format!("Material_Color_{}", color_type)) { let color_value = Some(Self::get_color(color)); match *color_type { - "ambient" => node.mat_props.ambient_color = color_value, - "diffuse" => node.mat_props.diffuse_color = color_value, - "emissive" => node.mat_props.emissive_color = color_value, + "ambient" => node.mat_props.color.ambient = color_value, + "diffuse" => node.mat_props.color.diffuse = color_value, + "emissive" => node.mat_props.color.emissive = color_value, _ => unreachable!(), } } }); - if let Some(value) = entity_props.get(&"material_alpha".to_string()) { - node.mat_props.alpha = Some( + [ + "UseBlend", + "BlendSourceMode", + "BlendDestinationMode", + "TestEnable", + "TestFunction", + "TestThreshold", + "NoSort", + ] + .iter() + .for_each(|alpha_prop| { + if let Some(prop) = entity_props.get(&format!("Material_Alpha_{}", alpha_prop)) { + println!("Parsing alpha prop {prop}"); + match *alpha_prop { + "UseBlend" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.use_blend = Some(value); + } + } + "BlendSourceMode" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.blend_source_mode = Some(value); + } + } + "BlendDestinationMode" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.blend_destination_mode = Some(value); + } + } + "TestEnable" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.use_test = Some(value); + } + } + "TestFunction" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.test_function = Some(value); + } + } + "TestThreshold" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.test_threshold = Some(value); + } + } + "NoSort" => { + if let Ok(value) = prop.parse::() { + node.mat_props.alpha.no_sort = Some(value); + } + } + _ => unreachable!(), + } + } + }); + + if let Some(value) = entity_props.get(&"Material_Alpha".to_string()) { + node.mat_props.alpha.opacity = Some( value .parse() .expect("Failed to parse float value from material properties!"), diff --git a/src/mesh.rs b/src/mesh.rs index 3fb424e..4f7b695 100644 --- a/src/mesh.rs +++ b/src/mesh.rs @@ -9,7 +9,10 @@ use tes3::{ }, }; -use crate::{brush_ni_node::BrushNiMatProps, BrushNiNode, MapData}; +use crate::{ + brush_ni_node::{BrushNiAlphaProps, BrushNiMatProps}, + BrushNiNode, MapData, +}; #[derive(Clone)] pub struct Mesh { @@ -193,20 +196,27 @@ impl Mesh { mat.flags = 1; - if let Some(color) = props.emissive_color { + if let Some(color) = props.color.emissive { mat.emissive_color = color.into(); } - if let Some(color) = props.ambient_color { + if let Some(color) = props.color.ambient { mat.ambient_color = color.into(); } - if let Some(color) = props.diffuse_color { + if let Some(color) = props.color.diffuse { mat.diffuse_color = color.into(); } - if let Some(value) = props.alpha { - mat.alpha = value; + if props.alpha != BrushNiAlphaProps::default() { + mat.alpha = props.alpha.opacity.unwrap_or(1.0); let mut alpha_prop = NiAlphaProperty::default(); - alpha_prop.flags = 237; // Default flag value for alpha prop + + alpha_prop.flags = props.alpha.to_flags(); + + if let Some(_) = props.alpha.use_test { + alpha_prop.test_ref = props.alpha.test_threshold.unwrap_or(128); + } + + dbg!(props.alpha, &alpha_prop.flags); let alpha_link = self.stream.insert(alpha_prop);