vmt_parser/material/
mod.rs

1mod cable;
2mod eyerefract;
3mod lightmappedgeneric;
4mod modulate;
5mod refract;
6mod replacements;
7mod sky;
8mod sprite;
9mod spritecard;
10mod subrect;
11mod unlitgeneric;
12mod unlittwotexture;
13mod vertexlitgeneric;
14mod water;
15mod worldvertextransition;
16
17use crate::TextureTransform;
18pub use cable::CableMaterial;
19pub use eyerefract::EyeRefractMaterial;
20pub use lightmappedgeneric::LightMappedGenericMaterial;
21pub use modulate::ModulateMaterial;
22pub use refract::RefractMaterial;
23pub use replacements::{ReplacementPattern, ReplacementTemplate, ReplacementsMaterial};
24use serde::{Deserialize, Deserializer, Serialize};
25pub use sky::SkyMaterial;
26pub use sprite::{SpriteMaterial, SpriteOrientation};
27pub use spritecard::SpriteCardMaterial;
28pub use subrect::SubRectMaterial;
29pub use unlitgeneric::UnlitGenericMaterial;
30pub use unlittwotexture::UnlitTwoTextureMaterial;
31use vdf_reader::entry::{Entry, Table};
32use vdf_reader::error::UnknownError;
33use vdf_reader::{from_entry, VdfError};
34pub use vertexlitgeneric::VertexLitGenericMaterial;
35pub use water::WaterMaterial;
36pub use worldvertextransition::WorldVertexTransitionMaterial;
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[non_exhaustive]
40#[serde(rename_all = "lowercase")]
41pub enum Material {
42    LightMappedGeneric(LightMappedGenericMaterial),
43    VertexLitGeneric(VertexLitGenericMaterial),
44    #[serde(rename = "vertexlitgeneric_dx6")]
45    VertexLitGenericDx6(VertexLitGenericMaterial),
46    UnlitGeneric(UnlitGenericMaterial),
47    UnlitTwoTexture(UnlitTwoTextureMaterial),
48    Water(WaterMaterial),
49    WorldVertexTransition(WorldVertexTransitionMaterial),
50    EyeRefract(EyeRefractMaterial),
51    SubRect(SubRectMaterial),
52    Sprite(SpriteMaterial),
53    SpriteCard(SpriteCardMaterial),
54    Cable(CableMaterial),
55    Refract(RefractMaterial),
56    Modulate(ModulateMaterial),
57    DecalModulate(ModulateMaterial),
58    Sky(SkyMaterial),
59    Replacements(ReplacementsMaterial),
60    Patch(PatchMaterial),
61}
62
63impl Material {
64    /// If the material is a patch, apply it to the included material
65    pub fn resolve<E, Loader>(self, loader: Loader) -> Result<Material, E>
66    where
67        Loader: FnOnce(&str) -> Result<String, E>,
68        E: From<VdfError>,
69    {
70        match self {
71            Material::Patch(patch) => patch.resolve(loader),
72            mat => Ok(mat),
73        }
74    }
75
76    pub fn translucent(&self) -> bool {
77        match self {
78            Material::LightMappedGeneric(mat) => mat.translucent,
79            Material::VertexLitGeneric(mat) => mat.translucent,
80            Material::VertexLitGenericDx6(mat) => mat.translucent,
81            Material::UnlitGeneric(mat) => mat.translucent,
82            Material::UnlitTwoTexture(mat) => mat.translucent,
83            Material::WorldVertexTransition(mat) => mat.translucent,
84            Material::Sprite(mat) => mat.translucent,
85            Material::Water(_) => true,
86            _ => false,
87        }
88    }
89
90    pub fn no_cull(&self) -> bool {
91        match self {
92            Material::LightMappedGeneric(mat) => mat.no_cull,
93            Material::VertexLitGeneric(mat) => mat.no_cull,
94            Material::VertexLitGenericDx6(mat) => mat.no_cull,
95            Material::UnlitGeneric(mat) => mat.no_cull,
96            Material::UnlitTwoTexture(mat) => mat.no_cull,
97            Material::WorldVertexTransition(mat) => mat.no_cull,
98            Material::Water(_) => true,
99            _ => false,
100        }
101    }
102
103    pub fn alpha_test(&self) -> Option<f32> {
104        match self {
105            Material::LightMappedGeneric(mat) => mat.alpha_test.then_some(mat.alpha_test_reference),
106            Material::VertexLitGeneric(mat) => mat.alpha_test.then_some(mat.alpha_test_reference),
107            Material::VertexLitGenericDx6(mat) => {
108                mat.alpha_test.then_some(mat.alpha_test_reference)
109            }
110            Material::UnlitGeneric(mat) => mat.alpha_test.then_some(mat.alpha_test_reference),
111            Material::UnlitTwoTexture(mat) => mat.alpha_test.then_some(mat.alpha_test_reference),
112            Material::WorldVertexTransition(mat) => {
113                mat.alpha_test.then_some(mat.alpha_test_reference)
114            }
115            Material::Sprite(mat) => mat.alpha_test.then_some(mat.alpha_test_reference),
116            Material::Water(_) => None,
117            _ => None,
118        }
119    }
120
121    pub fn base_texture(&self) -> Option<&str> {
122        match self {
123            Material::LightMappedGeneric(mat) => Some(&mat.base_texture),
124            Material::VertexLitGeneric(mat) => mat.base_texture.as_deref(),
125            Material::VertexLitGenericDx6(mat) => mat.base_texture.as_deref(),
126            Material::UnlitGeneric(mat) => mat.base_texture.as_deref(),
127            Material::UnlitTwoTexture(mat) => mat.base_texture.as_deref(),
128            Material::WorldVertexTransition(mat) => Some(&mat.base_texture),
129            Material::Sprite(mat) => Some(&mat.base_texture),
130            Material::Water(mat) => mat.base_texture.as_deref(),
131            Material::EyeRefract(mat) => Some(&mat.iris),
132            _ => None,
133        }
134    }
135
136    pub fn base_texture_transform(&self) -> Option<&TextureTransform> {
137        match self {
138            Material::LightMappedGeneric(mat) => Some(&mat.base_texture_transform),
139            Material::VertexLitGeneric(mat) => Some(&mat.base_texture_transform),
140            Material::VertexLitGenericDx6(mat) => Some(&mat.base_texture_transform),
141            Material::UnlitTwoTexture(mat) => Some(&mat.base_texture_transform),
142            Material::WorldVertexTransition(mat) => Some(&mat.base_texture_transform),
143            _ => None,
144        }
145    }
146
147    pub fn bump_map(&self) -> Option<&str> {
148        match self {
149            Material::LightMappedGeneric(mat) => mat.bump_map.as_deref(),
150            Material::VertexLitGeneric(mat) => mat.bump_map.as_deref(),
151            Material::VertexLitGenericDx6(mat) => mat.bump_map.as_deref(),
152            Material::UnlitGeneric(mat) => mat.bump_map.as_deref(),
153            Material::UnlitTwoTexture(mat) => mat.bump_map.as_deref(),
154            Material::WorldVertexTransition(mat) => mat.bump_map.as_deref(),
155            Material::Water(mat) => mat.bump_map.as_deref(),
156            _ => None,
157        }
158    }
159
160    pub fn surface_prop(&self) -> Option<&str> {
161        match self {
162            Material::LightMappedGeneric(mat) => mat.surface_prop.as_deref(),
163            Material::UnlitGeneric(mat) => mat.surface_prop.as_deref(),
164            Material::UnlitTwoTexture(mat) => mat.surface_prop.as_deref(),
165            Material::WorldVertexTransition(mat) => mat.surface_prop.as_deref(),
166            _ => None,
167        }
168    }
169
170    pub fn alpha(&self) -> f32 {
171        match self {
172            Material::LightMappedGeneric(mat) => mat.alpha,
173            Material::VertexLitGeneric(mat) => mat.alpha,
174            Material::VertexLitGenericDx6(mat) => mat.alpha,
175            Material::UnlitGeneric(mat) => mat.alpha,
176            Material::UnlitTwoTexture(mat) => mat.alpha,
177            Material::Sprite(mat) => mat.alpha,
178            Material::WorldVertexTransition(mat) => mat.alpha,
179            _ => 1.0,
180        }
181    }
182
183    pub fn ignore_z_test(&self) -> bool {
184        match self {
185            Material::LightMappedGeneric(mat) => mat.ignore_z,
186            Material::VertexLitGeneric(mat) => mat.ignore_z,
187            Material::VertexLitGenericDx6(mat) => mat.ignore_z,
188            Material::UnlitGeneric(mat) => mat.ignore_z,
189            Material::UnlitTwoTexture(mat) => mat.ignore_z,
190            Material::WorldVertexTransition(mat) => mat.ignore_z,
191            _ => false,
192        }
193    }
194}
195
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct PatchMaterial {
198    #[serde(deserialize_with = "deserialize_path")]
199    include: String,
200    #[serde(default)]
201    replace: Table,
202}
203
204impl PatchMaterial {
205    /// Load the included material and apply the patch
206    pub fn resolve<E, Loader>(&self, loader: Loader) -> Result<Material, E>
207    where
208        Loader: FnOnce(&str) -> Result<String, E>,
209        E: From<VdfError>,
210    {
211        let base = loader(&self.include)?.to_ascii_lowercase();
212        let mut material = Table::load_from_str(&base)?;
213
214        let material_values = match material.iter_mut().next() {
215            Some((_, Entry::Table(table))) => table,
216            _ => {
217                return Err(VdfError::from(UnknownError::from(
218                    "included vdf doesn't look like a material",
219                ))
220                .into())
221            }
222        };
223        for (key, value) in self.replace.iter() {
224            material_values.insert(key.clone(), value.clone());
225        }
226
227        Ok(from_entry(Entry::Table(material))?)
228    }
229}
230
231trait PathLike {
232    fn normalize(self) -> Self;
233}
234
235impl PathLike for String {
236    fn normalize(self) -> Self {
237        self.replace('\\', "/")
238    }
239}
240
241impl<T: PathLike> PathLike for Option<T> {
242    fn normalize(self) -> Self {
243        self.map(T::normalize)
244    }
245}
246
247/// Deserialize string and convert windows \ path separators to /
248fn deserialize_path<'de, T: PathLike + Deserialize<'de>, D: Deserializer<'de>>(
249    deserializer: D,
250) -> Result<T, D::Error> {
251    Ok(T::deserialize(deserializer)?.normalize())
252}