mirror of
https://codeberg.org/icewind/vmt-parser.git
synced 2026-06-03 12:04:06 +02:00
LightMappedGeneric
This commit is contained in:
parent
971b42452d
commit
ab10e2d05c
14 changed files with 378 additions and 243 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,3 +3,4 @@ target
|
||||||
*.obj
|
*.obj
|
||||||
result
|
result
|
||||||
.direnv
|
.direnv
|
||||||
|
*.snap.new
|
||||||
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -426,6 +426,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_repr"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|
@ -608,9 +619,9 @@ name = "vmt-parser"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"insta",
|
"insta",
|
||||||
"logos",
|
|
||||||
"miette",
|
"miette",
|
||||||
"parse-display",
|
"serde",
|
||||||
|
"serde_repr",
|
||||||
"test-case",
|
"test-case",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"vdf-reader",
|
"vdf-reader",
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
logos = "0.13.0"
|
|
||||||
thiserror = "1.0.50"
|
thiserror = "1.0.50"
|
||||||
miette = "5.10.0"
|
miette = "5.10.0"
|
||||||
parse-display = "0.8.2"
|
|
||||||
vdf-reader = { version = "0.1", path = "../vdf-reader" }
|
vdf-reader = { version = "0.1", path = "../vdf-reader" }
|
||||||
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
|
serde_repr = "0.1.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test-case = "3.3.1"
|
test-case = "3.3.1"
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
use miette::{Context, IntoDiagnostic, Result};
|
use miette::{Context, IntoDiagnostic, Result};
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use vdf_reader::Reader;
|
use vmt_parser::from_str;
|
||||||
use vmt_parser::material::Material;
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let path = args().nth(1).expect("no path provided");
|
let path = args().nth(1).expect("no path provided");
|
||||||
let raw = read_to_string(path)
|
let raw = read_to_string(path)
|
||||||
.into_diagnostic()
|
.into_diagnostic()
|
||||||
.wrap_err("failed to read input")?;
|
.wrap_err("failed to read input")?;
|
||||||
let mut reader = Reader::from(raw.as_str());
|
let material = from_str(&raw).wrap_err("failed to parse material")?;
|
||||||
let material = Material::parse(&mut reader).wrap_err("failed to parse material")?;
|
|
||||||
dbg!(material);
|
dbg!(material);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@
|
||||||
cargo-audit
|
cargo-audit
|
||||||
cargo-msrv
|
cargo-msrv
|
||||||
cargo-semver-checks
|
cargo-semver-checks
|
||||||
|
cargo-insta
|
||||||
(writeShellApplication {
|
(writeShellApplication {
|
||||||
name = "cargo-fuzz";
|
name = "cargo-fuzz";
|
||||||
runtimeInputs = [cargo-fuzz toolchain];
|
runtimeInputs = [cargo-fuzz toolchain];
|
||||||
|
|
|
||||||
80
src/data/mod.rs
Normal file
80
src/data/mod.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
pub mod texture_transform;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
|
pub use texture_transform::TextureTransform;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
|
#[serde(from = "Vec2OrSingle<f32>")]
|
||||||
|
pub struct Vec2([f32; 2]);
|
||||||
|
|
||||||
|
impl From<Vec2OrSingle<f32>> for Vec2 {
|
||||||
|
fn from(value: Vec2OrSingle<f32>) -> Self {
|
||||||
|
match value {
|
||||||
|
Vec2OrSingle::Vec2(vec) => Vec2(vec),
|
||||||
|
Vec2OrSingle::Single(val) => Vec2([val; 2]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||||
|
#[serde(from = "Vec3OrSingle<f32>")]
|
||||||
|
pub struct Vec3([f32; 3]);
|
||||||
|
|
||||||
|
impl From<Vec3OrSingle<f32>> for Vec3 {
|
||||||
|
fn from(value: Vec3OrSingle<f32>) -> Self {
|
||||||
|
match value {
|
||||||
|
Vec3OrSingle::Vec3(vec) => Vec3(vec),
|
||||||
|
Vec3OrSingle::Single(val) => Vec3([val; 3]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum Vec3OrSingle<T> {
|
||||||
|
Vec3([T; 3]),
|
||||||
|
Single(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum Vec2OrSingle<T> {
|
||||||
|
Vec2([T; 2]),
|
||||||
|
Single(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_scale3() -> Vec3 {
|
||||||
|
Vec3([1.0; 3])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn default_scale2() -> Vec2 {
|
||||||
|
Vec2([1.0; 2])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_scale() -> f32 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_detail_scale() -> Vec2 {
|
||||||
|
Vec2([4.0; 2])
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Copy, Clone, Default)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum BlendMode {
|
||||||
|
DecalModulate = 0,
|
||||||
|
#[default]
|
||||||
|
Additive = 1,
|
||||||
|
TranslucentOverlay = 2,
|
||||||
|
BlendFactorOverlay = 3,
|
||||||
|
TranslucentBase = 4,
|
||||||
|
UnlitAdditive = 5,
|
||||||
|
UnlitAdditiveThreshold = 6,
|
||||||
|
TwoPatternModulate = 7,
|
||||||
|
Multiply = 8,
|
||||||
|
BaseMaskAlpha = 9,
|
||||||
|
SelfShadowedBumpMap = 10,
|
||||||
|
SelfShadowedBumpAlbedo = 11,
|
||||||
|
}
|
||||||
123
src/data/texture_transform.rs
Normal file
123
src/data/texture_transform.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct TextureTransform {
|
||||||
|
pub center: [f32; 2],
|
||||||
|
pub scale: [f32; 2],
|
||||||
|
pub rotate: f32,
|
||||||
|
pub translate: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TextureTransform {
|
||||||
|
fn default() -> Self {
|
||||||
|
TextureTransform {
|
||||||
|
center: [0.5; 2],
|
||||||
|
scale: [1.0; 2],
|
||||||
|
rotate: 0.0,
|
||||||
|
translate: [0.0; 2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TextureTransform {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"center {} {} scale {} {} rotate {} translate {} {}",
|
||||||
|
self.center[0],
|
||||||
|
self.center[1],
|
||||||
|
self.scale[0],
|
||||||
|
self.scale[1],
|
||||||
|
self.rotate,
|
||||||
|
self.translate[0],
|
||||||
|
self.translate[1],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_base_texture_transform() {
|
||||||
|
assert_eq!(
|
||||||
|
TextureTransform {
|
||||||
|
center: [0.5; 2],
|
||||||
|
scale: [1.0; 2],
|
||||||
|
rotate: 0.0,
|
||||||
|
translate: [0.0; 2],
|
||||||
|
},
|
||||||
|
TextureTransform::from_str("center .5 .5 scale 1 1 rotate 0 translate 0 0").unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
TextureTransform {
|
||||||
|
center: [0.2, 0.3],
|
||||||
|
scale: [1.1, 1.2],
|
||||||
|
rotate: 1.0,
|
||||||
|
translate: [0.4, 0.5],
|
||||||
|
},
|
||||||
|
TextureTransform::from_str("center .2 .3 scale 1.1 1.2 rotate 1 translate 0.4 0.5")
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for TextureTransform {
|
||||||
|
type Err = &'static str;
|
||||||
|
|
||||||
|
fn from_str(str: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = str.split(' ').filter(|p| !p.is_empty());
|
||||||
|
match (
|
||||||
|
parts.next(),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next(),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next(),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next(),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
parts.next().and_then(|val| f32::from_str(val).ok()),
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
Some("center"),
|
||||||
|
Some(center_x),
|
||||||
|
Some(center_y),
|
||||||
|
Some("scale"),
|
||||||
|
Some(scale_x),
|
||||||
|
Some(scale_y),
|
||||||
|
Some("rotate"),
|
||||||
|
Some(rotate),
|
||||||
|
Some("translate"),
|
||||||
|
Some(translate_x),
|
||||||
|
Some(translate_y),
|
||||||
|
) => Ok(TextureTransform {
|
||||||
|
center: [center_x, center_y],
|
||||||
|
scale: [scale_x, scale_y],
|
||||||
|
rotate,
|
||||||
|
translate: [translate_x, translate_y],
|
||||||
|
}),
|
||||||
|
_ => Err("invalid $basetexturetransform format"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for TextureTransform {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let str = Cow::<str>::deserialize(deserializer)?;
|
||||||
|
Self::from_str(str.as_ref()).map_err(D::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for TextureTransform {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/error.rs
60
src/error.rs
|
|
@ -1,7 +1,5 @@
|
||||||
use miette::{Diagnostic, SourceSpan};
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use vdf_reader::entry::Entry;
|
|
||||||
use vdf_reader::error::ParseEntryError;
|
|
||||||
use vdf_reader::VdfError;
|
use vdf_reader::VdfError;
|
||||||
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
|
|
@ -9,60 +7,4 @@ pub enum VmtError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
#[diagnostic(transparent)]
|
#[diagnostic(transparent)]
|
||||||
Vdf(#[from] VdfError),
|
Vdf(#[from] VdfError),
|
||||||
#[error(transparent)]
|
|
||||||
#[diagnostic(transparent)]
|
|
||||||
Eof(#[from] EofError),
|
|
||||||
#[error(transparent)]
|
|
||||||
#[diagnostic(transparent)]
|
|
||||||
ParseValue(#[from] ParseValueError),
|
|
||||||
}
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
|
||||||
#[error("Unexpected end of input while looking for {expected}")]
|
|
||||||
#[diagnostic(code(vmt_parser::eof))]
|
|
||||||
pub struct EofError {
|
|
||||||
expected: &'static str,
|
|
||||||
#[label("Expected {}", self.expected)]
|
|
||||||
err_span: SourceSpan,
|
|
||||||
#[source_code]
|
|
||||||
src: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EofError {
|
|
||||||
pub fn new(src: String, expected: &'static str) -> Self {
|
|
||||||
let span = src.len()..src.len();
|
|
||||||
EofError {
|
|
||||||
src,
|
|
||||||
err_span: span.into(),
|
|
||||||
expected,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug, Error, Diagnostic)]
|
|
||||||
#[error("Can't parse value {value:?} as {ty} to read {name}")]
|
|
||||||
#[diagnostic(code(vmt_parser::eof))]
|
|
||||||
pub struct ParseValueError {
|
|
||||||
name: &'static str,
|
|
||||||
ty: &'static str,
|
|
||||||
pub value: Entry,
|
|
||||||
#[label("Expected a {}", self.ty)]
|
|
||||||
err_span: SourceSpan,
|
|
||||||
#[source_code]
|
|
||||||
src: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParseValueError {
|
|
||||||
pub fn new(
|
|
||||||
src: String,
|
|
||||||
name: &'static str,
|
|
||||||
err_span: SourceSpan,
|
|
||||||
parse_err: ParseEntryError,
|
|
||||||
) -> Self {
|
|
||||||
ParseValueError {
|
|
||||||
src,
|
|
||||||
err_span,
|
|
||||||
name,
|
|
||||||
ty: parse_err.ty,
|
|
||||||
value: parse_err.value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
|
mod data;
|
||||||
mod error;
|
mod error;
|
||||||
pub mod material;
|
pub mod material;
|
||||||
|
|
||||||
|
use crate::material::Material;
|
||||||
|
pub use data::*;
|
||||||
pub use error::VmtError;
|
pub use error::VmtError;
|
||||||
|
|
||||||
pub type Result<T, E = VmtError> = std::result::Result<T, E>;
|
pub type Result<T, E = VmtError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
pub fn from_str(input: &str) -> Result<Material> {
|
||||||
|
let input = input.to_ascii_lowercase();
|
||||||
|
vdf_reader::from_str(&input).map_err(VmtError::from)
|
||||||
|
}
|
||||||
|
|
|
||||||
176
src/material.rs
176
src/material.rs
|
|
@ -1,176 +0,0 @@
|
||||||
use crate::error::{EofError, ParseValueError};
|
|
||||||
use crate::Result;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use vdf_reader::entry::{Entry, FromEntry, Table};
|
|
||||||
use vdf_reader::{Event, GroupStartEvent, Reader, VdfError};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Material {
|
|
||||||
Other(OtherMaterial),
|
|
||||||
LightMappedGeneric(LightMappedGenericMaterial),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Material {
|
|
||||||
pub fn parse(reader: &mut Reader) -> Result<Self> {
|
|
||||||
let start: GroupStartEvent = expect_event(reader, "material name")?;
|
|
||||||
let name = start.name.to_ascii_lowercase();
|
|
||||||
|
|
||||||
Ok(match name.as_str() {
|
|
||||||
"lightmappedgeneric" => {
|
|
||||||
Material::LightMappedGeneric(LightMappedGenericMaterial::parse(reader)?)
|
|
||||||
}
|
|
||||||
_ => Material::Other(OtherMaterial::parse(name, reader)?),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct LightMappedGenericMaterial {
|
|
||||||
pub keywords: String,
|
|
||||||
pub detail: Option<String>,
|
|
||||||
pub detail_blend_factory: f32,
|
|
||||||
pub detail_scale: f32,
|
|
||||||
pub detail_blend_mode: u32,
|
|
||||||
pub base_texture: String,
|
|
||||||
pub ss_bump: bool,
|
|
||||||
pub bump_map: Option<String>,
|
|
||||||
pub rest: HashMap<String, Entry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LightMappedGenericMaterial {
|
|
||||||
fn parse(reader: &mut Reader) -> Result<Self> {
|
|
||||||
let src = reader.source;
|
|
||||||
let mut keywords = Default::default();
|
|
||||||
let mut detail = Default::default();
|
|
||||||
let mut detail_blend_factory = Default::default();
|
|
||||||
let mut detail_scale = Default::default();
|
|
||||||
let mut detail_blend_mode = Default::default();
|
|
||||||
let mut base_texture = Default::default();
|
|
||||||
let mut ss_bump = Default::default();
|
|
||||||
let mut bump_map = Default::default();
|
|
||||||
|
|
||||||
let mut rest: HashMap<String, Entry> = Default::default();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (span, key, value) = match event(reader, "item or group end")? {
|
|
||||||
Event::GroupEnd(_) => break,
|
|
||||||
Event::GroupStart(start) => (
|
|
||||||
start.span,
|
|
||||||
start.name.to_ascii_lowercase(),
|
|
||||||
Entry::Table(Table::load(reader)?),
|
|
||||||
),
|
|
||||||
Event::Entry(entry) => (
|
|
||||||
entry.span,
|
|
||||||
entry.key.into_content().to_ascii_lowercase(),
|
|
||||||
entry.value.into(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
match key.as_str() {
|
|
||||||
"%keywords" => {
|
|
||||||
keywords = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "keywords", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$detail" => {
|
|
||||||
detail = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "detail", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$detailblendfactor" => {
|
|
||||||
detail_blend_factory = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "detail_blend_factory", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$detailscale" => {
|
|
||||||
detail_scale = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "detail_scale", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$detailblendmode" => {
|
|
||||||
detail_blend_mode = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "detail_blend_mode", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$basetexture" => {
|
|
||||||
base_texture = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "base_texture", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$ssbump" => {
|
|
||||||
ss_bump = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "ss_bump", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
"$bumpmap" => {
|
|
||||||
bump_map = FromEntry::from_entry(value).map_err(|err| {
|
|
||||||
ParseValueError::new(src.into(), "bump_map", span.into(), err)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
rest.insert(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(LightMappedGenericMaterial {
|
|
||||||
keywords,
|
|
||||||
detail,
|
|
||||||
detail_blend_factory,
|
|
||||||
detail_scale,
|
|
||||||
detail_blend_mode,
|
|
||||||
base_texture,
|
|
||||||
ss_bump,
|
|
||||||
bump_map,
|
|
||||||
rest,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OtherMaterial {
|
|
||||||
pub name: String,
|
|
||||||
pub values: HashMap<String, Entry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OtherMaterial {
|
|
||||||
fn parse(name: String, reader: &mut Reader) -> Result<Self> {
|
|
||||||
let mut values: HashMap<String, _> = HashMap::new();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let (key, value) = match event(reader, "item or group end")? {
|
|
||||||
Event::GroupEnd(_) => break,
|
|
||||||
Event::GroupStart(start) => (
|
|
||||||
start.name.to_ascii_lowercase(),
|
|
||||||
Entry::Table(Table::load(reader)?),
|
|
||||||
),
|
|
||||||
Event::Entry(entry) => (
|
|
||||||
entry.key.into_content().to_ascii_lowercase(),
|
|
||||||
entry.value.into(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
values.insert(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(OtherMaterial { name, values })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn event<'a>(reader: &mut Reader<'a>, expected: &'static str) -> Result<Event<'a>> {
|
|
||||||
Ok(reader
|
|
||||||
.next()
|
|
||||||
.ok_or_else(|| EofError::new(reader.source.into(), expected))??)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_event<'a, E: 'a>(reader: &mut Reader<'a>, expected: &'static str) -> Result<E>
|
|
||||||
where
|
|
||||||
E: TryFrom<Event<'a>, Error = VdfError>,
|
|
||||||
{
|
|
||||||
let event = event(reader, expected)?;
|
|
||||||
E::try_from(event).map_err(|e| {
|
|
||||||
match e {
|
|
||||||
VdfError::WrongEntryType(e) => e.with_source(reader.source.into()).into(),
|
|
||||||
e => e,
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
88
src/material/lightmappedgeneric.rs
Normal file
88
src/material/lightmappedgeneric.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::{
|
||||||
|
default_detail_scale, default_scale, default_scale3, BlendMode, TextureTransform, Vec2, Vec3,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct LightMappedGenericMaterial {
|
||||||
|
/// Defines an diffuse texture.
|
||||||
|
#[serde(rename = "$basetexture")]
|
||||||
|
pub base_texture: String,
|
||||||
|
/// Use this material as a decal.
|
||||||
|
#[serde(rename = "$decal", default)]
|
||||||
|
pub decal: bool,
|
||||||
|
/// Detail texturing.
|
||||||
|
#[serde(rename = "$detail")]
|
||||||
|
pub detail: Option<String>,
|
||||||
|
/// Links the surface to a set of physical properties.
|
||||||
|
#[serde(rename = "$surfaceprop")]
|
||||||
|
pub surface_prop: Option<String>,
|
||||||
|
|
||||||
|
/// Transforms the texture before use in the material. This does not affect lightmaps on the surface.
|
||||||
|
#[serde(rename = "$basetexturetransform", default)]
|
||||||
|
pub base_texture_transform: TextureTransform,
|
||||||
|
/// Independently scales the red, green and blue channels of an albedo.
|
||||||
|
#[serde(rename = "$color", default = "default_scale3")]
|
||||||
|
pub color: Vec3,
|
||||||
|
/// the number of units that each texel covers
|
||||||
|
#[serde(rename = "$decalscale", default = "default_scale")]
|
||||||
|
pub decal_scale: f32,
|
||||||
|
#[serde(rename = "$decalscale", default = "default_detail_scale")]
|
||||||
|
/// Fits the detail texture onto the material the given number of times
|
||||||
|
pub detail_scale: Vec2,
|
||||||
|
/// Controls the amount that the detail texture affects the base texture. The precise use of this depends on the blend factor; in most cases it acts similarly to $alpha. A value of 0 usually makes the detail texture have no effect, whilst a value of 1 applies the full effect.
|
||||||
|
#[serde(rename = "$detailblendfactor", default = "default_scale")]
|
||||||
|
pub detail_blend_factor: f32,
|
||||||
|
/// How to combine the detail material with the albedo.
|
||||||
|
#[serde(rename = "$detailblendmode", default)]
|
||||||
|
pub detail_blend_mode: BlendMode,
|
||||||
|
/// A separate VertexLitGeneric material to that will replace this one if the decal hits a model.
|
||||||
|
#[serde(rename = "$modelmaterial", default)]
|
||||||
|
pub model_material: Option<String>,
|
||||||
|
/// Disables texture filtering.
|
||||||
|
#[serde(rename = "$pointsamplemagfilter", default)]
|
||||||
|
pub point_sample_mag_filter: bool,
|
||||||
|
/// Mitigation for displacement texture stretching.
|
||||||
|
#[serde(rename = "$seamless_scale", default = "default_scale")]
|
||||||
|
pub seamless_scale: f32,
|
||||||
|
|
||||||
|
/// Scales the opacity of an entire material.
|
||||||
|
#[serde(rename = "$alpha", default = "default_scale")]
|
||||||
|
pub alpha: f32,
|
||||||
|
/// Specifies a mask to use to determine binary opacity.
|
||||||
|
#[serde(rename = "$alphatest", default)]
|
||||||
|
pub alpha_test: bool,
|
||||||
|
/// Vector-like edge filtering.
|
||||||
|
#[serde(rename = "$distancealpha", default)]
|
||||||
|
pub distance_alpha: bool,
|
||||||
|
/// Disables backface culling.
|
||||||
|
#[serde(rename = "$nocull", default)]
|
||||||
|
pub no_cull: bool,
|
||||||
|
/// Specifies that the material should be partially see-through.
|
||||||
|
#[serde(rename = "$translucent", default)]
|
||||||
|
pub translucent: bool,
|
||||||
|
|
||||||
|
/// Specifies a texture that will provide three-dimensional lighting information for a material.
|
||||||
|
#[serde(rename = "$bumpmap")]
|
||||||
|
pub bump_map: Option<String>,
|
||||||
|
/// Per-texel color modification via a warp texture.
|
||||||
|
#[serde(rename = "$lightwarptexture")]
|
||||||
|
pub light_wrap_texture: Option<String>,
|
||||||
|
/// Determines whether the surface is self-illuminated independent of environment lighting.
|
||||||
|
#[serde(rename = "$selfillum", default)]
|
||||||
|
pub self_illum: bool,
|
||||||
|
/// Flags the $bumpmap as being a self-shadowing bumpmap.
|
||||||
|
#[serde(rename = "$ssbump", default)]
|
||||||
|
pub ss_bump: bool,
|
||||||
|
|
||||||
|
/// Specular reflections.
|
||||||
|
#[serde(rename = "$envmap")]
|
||||||
|
pub env_map: Option<String>,
|
||||||
|
/// Diffuse reflections.
|
||||||
|
#[serde(rename = "$phong", default)]
|
||||||
|
pub phong: bool,
|
||||||
|
|
||||||
|
/// Prevents fog from overdrawing a material.
|
||||||
|
#[serde(rename = "$nofog", default)]
|
||||||
|
pub no_fog: bool,
|
||||||
|
}
|
||||||
10
src/material/mod.rs
Normal file
10
src/material/mod.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
mod lightmappedgeneric;
|
||||||
|
|
||||||
|
use lightmappedgeneric::LightMappedGenericMaterial;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum Material {
|
||||||
|
#[serde(rename = "lightmappedgeneric")]
|
||||||
|
LightMappedGeneric(LightMappedGenericMaterial),
|
||||||
|
}
|
||||||
18
tests/parse.rs
Normal file
18
tests/parse.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
use miette::{GraphicalReportHandler, GraphicalTheme};
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
use test_case::test_case;
|
||||||
|
use vmt_parser::from_str;
|
||||||
|
|
||||||
|
#[test_case("tests/data/concretefloor003.vmt")]
|
||||||
|
fn test_serde(path: &str) {
|
||||||
|
let raw = read_to_string(path).unwrap();
|
||||||
|
match from_str(&raw) {
|
||||||
|
Ok(result) => insta::assert_ron_snapshot!(path, result),
|
||||||
|
Err(e) => {
|
||||||
|
let handler = GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor());
|
||||||
|
let mut out = String::new();
|
||||||
|
handler.render_report(&mut out, &e).unwrap();
|
||||||
|
insta::assert_snapshot!(path, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
source: tests/parse.rs
|
||||||
|
expression: result
|
||||||
|
---
|
||||||
|
lightmappedgeneric(LightMappedGenericMaterial(
|
||||||
|
r#$basetexture: "cp_mountainlab/concrete/concretefloor003",
|
||||||
|
r#$decal: false,
|
||||||
|
r#$detail: Some("overlays/detail001"),
|
||||||
|
r#$surfaceprop: None,
|
||||||
|
r#$basetexturetransform: "center 0.5 0.5 scale 1 1 rotate 0 translate 0 0",
|
||||||
|
r#$color: Vec3((1.0, 1.0, 1.0)),
|
||||||
|
r#$decalscale: 1.0,
|
||||||
|
r#$decalscale: Vec2((4.0, 4.0)),
|
||||||
|
r#$detailblendfactor: 1.0,
|
||||||
|
r#$detailblendmode: 0,
|
||||||
|
r#$modelmaterial: None,
|
||||||
|
r#$pointsamplemagfilter: false,
|
||||||
|
r#$seamless_scale: 1.0,
|
||||||
|
r#$alpha: 1.0,
|
||||||
|
r#$alphatest: false,
|
||||||
|
r#$distancealpha: false,
|
||||||
|
r#$nocull: false,
|
||||||
|
r#$translucent: false,
|
||||||
|
r#$bumpmap: Some("concrete/concretefloor007b_height-ssbump"),
|
||||||
|
r#$lightwarptexture: None,
|
||||||
|
r#$selfillum: false,
|
||||||
|
r#$ssbump: true,
|
||||||
|
r#$envmap: None,
|
||||||
|
r#$phong: false,
|
||||||
|
r#$nofog: false,
|
||||||
|
))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue