use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use super::common::Editor;
use crate::utils::{get_key, parse_hs_key, To01String};
use crate::{
errors::{VmfError, VmfResult},
VmfBlock, VmfSerializable,
};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct World {
pub key_values: IndexMap<String, String>,
pub solids: Vec<Solid>,
pub hidden: Vec<Solid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub group: Option<Group>,
}
impl TryFrom<VmfBlock> for World {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let mut world = World {
key_values: block.key_values,
..Default::default()
};
for inner_block in block.blocks {
match inner_block.name.as_str() {
"solid" => world.solids.push(Solid::try_from(inner_block)?),
"group" => world.group = Group::try_from(inner_block).ok(),
"hidden" => {
if let Some(hidden_block) = inner_block.blocks.first() {
world.hidden.push(Solid::try_from(hidden_block.to_owned())?);
}
}
_ => {
debug_assert!(false, "Unexpected block name: {}", inner_block.name);
}
};
}
Ok(world)
}
}
impl Into<VmfBlock> for World {
fn into(self) -> VmfBlock {
let mut blocks = Vec::new();
for solid in self.solids {
blocks.push(solid.into());
}
for hidden_solid in self.hidden {
blocks.push(VmfBlock {
name: "hidden".to_string(),
key_values: IndexMap::new(),
blocks: vec![hidden_solid.into()],
});
}
if let Some(group) = self.group {
blocks.push(group.into());
}
VmfBlock {
name: "world".to_string(),
key_values: self.key_values,
blocks,
}
}
}
impl VmfSerializable for World {
fn to_vmf_string(&self, indent_level: usize) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(2048);
output.push_str(&format!("{0}world\n{0}{{\n", indent));
for (key, value) in &self.key_values {
output.push_str(&format!("{}\t\"{}\" \"{}\"\n", indent, key, value));
}
if self.solids.len() > 0 {
for solid in &self.solids {
output.push_str(&solid.to_vmf_string(indent_level + 1));
}
}
if self.hidden.len() > 0 {
output.push_str(&format!("{0}\tHidden\n{0}\t{{\n", indent));
for solid in &self.hidden {
output.push_str(&solid.to_vmf_string(indent_level + 2));
}
output.push_str(&format!("{}\t}}\n", indent));
}
if let Some(group) = &self.group {
output.push_str(&group.to_vmf_string(indent_level + 1));
}
output.push_str(&format!("{}}}\n", indent));
output
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Solid {
pub id: u64,
pub sides: Vec<Side>,
pub editor: Editor,
}
impl TryFrom<VmfBlock> for Solid {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let mut solid = Solid {
id: parse_hs_key!(&block.key_values, "id", u64)?,
sides: Vec::with_capacity(4),
..Default::default()
};
for inner_block in block.blocks {
match inner_block.name.as_str() {
"side" => solid.sides.push(Side::try_from(inner_block)?),
"editor" => solid.editor = Editor::try_from(inner_block)?,
_ => {
debug_assert!(false, "Unexpected block name: {}", inner_block.name);
}
}
}
Ok(solid)
}
}
impl Into<VmfBlock> for Solid {
fn into(self) -> VmfBlock {
let mut blocks = Vec::new();
for side in self.sides {
blocks.push(side.into());
}
blocks.push(self.editor.into());
VmfBlock {
name: "solid".to_string(),
key_values: {
let mut key_values = IndexMap::new();
key_values.insert("id".to_string(), self.id.to_string());
key_values
},
blocks,
}
}
}
impl VmfSerializable for Solid {
fn to_vmf_string(&self, indent_level: usize) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(256);
output.push_str(&format!("{0}solid\n{0}{{\n", indent));
output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
for side in &self.sides {
output.push_str(&side.to_vmf_string(indent_level + 1));
}
output.push_str(&self.editor.to_vmf_string(indent_level + 1));
output.push_str(&format!("{}}}\n", indent));
output
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Side {
pub id: u32,
pub plane: String,
pub material: String,
pub u_axis: String,
pub v_axis: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rotation: Option<f32>,
pub lightmap_scale: u16,
pub smoothing_groups: i32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flags: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dispinfo: Option<DispInfo>,
}
impl TryFrom<VmfBlock> for Side {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let kv = &block.key_values;
let dispinfo_block = block.blocks.iter().find(|b| b.name == "dispinfo");
Ok(Side {
id: parse_hs_key!(kv, "id", u32)?,
plane: get_key!(kv, "plane")?.to_owned(),
material: get_key!(kv, "material")?.to_owned(),
u_axis: get_key!(kv, "uaxis")?.to_owned(),
v_axis: get_key!(kv, "vaxis")?.to_owned(),
rotation: get_key!(kv, "rotation", "_".into()).parse().ok(),
lightmap_scale: parse_hs_key!(kv, "lightmapscale", u16)?,
smoothing_groups: parse_hs_key!(kv, "smoothing_groups", i32)?,
flags: get_key!(kv, "flags", "_".into()).parse().ok(),
dispinfo: match dispinfo_block {
Some(block) => Some(DispInfo::try_from(block.clone())?), None => None,
},
})
}
}
impl Into<VmfBlock> for Side {
fn into(self) -> VmfBlock {
let mut key_values = IndexMap::new();
key_values.insert("id".to_string(), self.id.to_string());
key_values.insert("plane".to_string(), self.plane);
key_values.insert("material".to_string(), self.material);
key_values.insert("uaxis".to_string(), self.u_axis);
key_values.insert("vaxis".to_string(), self.v_axis);
if let Some(rotation) = self.rotation {
key_values.insert("rotation".to_string(), rotation.to_string());
}
key_values.insert("lightmapscale".to_string(), self.lightmap_scale.to_string());
key_values.insert(
"smoothing_groups".to_string(),
self.smoothing_groups.to_string(),
);
if let Some(flags) = self.flags {
key_values.insert("flags".to_string(), flags.to_string());
}
VmfBlock {
name: "side".to_string(),
key_values,
blocks: Vec::new(),
}
}
}
impl VmfSerializable for Side {
fn to_vmf_string(&self, indent_level: usize) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(256);
output.push_str(&format!("{0}side\n{0}{{\n", indent));
output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
output.push_str(&format!("{}\t\"plane\" \"{}\"\n", indent, self.plane));
output.push_str(&format!("{}\t\"material\" \"{}\"\n", indent, self.material));
output.push_str(&format!("{}\t\"uaxis\" \"{}\"\n", indent, self.u_axis));
output.push_str(&format!("{}\t\"vaxis\" \"{}\"\n", indent, self.v_axis));
if let Some(rotation) = self.rotation {
output.push_str(&format!("{}\t\"rotation\" \"{}\"\n", indent, rotation));
}
output.push_str(&format!(
"{}\t\"lightmapscale\" \"{}\"\n",
indent, self.lightmap_scale
));
output.push_str(&format!(
"{}\t\"smoothing_groups\" \"{}\"\n",
indent, self.smoothing_groups
));
if let Some(flags) = self.flags {
output.push_str(&format!("{}\t\"flags\" \"{}\"\n", indent, flags));
}
if let Some(dispinfo) = &self.dispinfo {
output.push_str(&dispinfo.to_vmf_string(indent_level + 1));
}
output.push_str(&format!("{0}}}\n", indent));
output
}
}
macro_rules! find_block {
($blocks:expr, $name:expr) => {
$blocks.iter().find(|b| b.name == $name).ok_or_else(|| {
VmfError::InvalidFormat(format!("Missing {} block in dispinfo", $name))
})?
};
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct DispInfo {
pub power: u8,
pub start_position: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flags: Option<u32>,
pub elevation: f32,
pub subdiv: bool,
pub normals: DispRows,
pub distances: DispRows,
pub offsets: DispRows,
pub offset_normals: DispRows,
pub alphas: DispRows,
pub triangle_tags: DispRows,
pub allowed_verts: IndexMap<String, Vec<i32>>,
}
impl TryFrom<VmfBlock> for DispInfo {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let normals_block = find_block!(block.blocks, "normals");
let distances_block = find_block!(block.blocks, "distances");
let offsets_block = find_block!(block.blocks, "offsets");
let offset_normals_block = find_block!(block.blocks, "offset_normals");
let alphas_block = find_block!(block.blocks, "alphas");
let triangle_tags_block = find_block!(block.blocks, "triangle_tags");
let allowed_verts_block = find_block!(block.blocks, "allowed_verts");
let kv = &block.key_values;
Ok(DispInfo {
power: parse_hs_key!(kv, "power", u8)?,
start_position: get_key!(kv, "startposition")?.to_string(),
flags: get_key!(kv, "flags", "_".into()).parse().ok(),
elevation: get_key!(kv, "elevation")?.parse()?,
subdiv: get_key!(kv, "subdiv")? == "1",
normals: DispRows::try_from(normals_block.clone())?,
distances: DispRows::try_from(distances_block.clone())?,
offsets: DispRows::try_from(offsets_block.clone())?,
offset_normals: DispRows::try_from(offset_normals_block.clone())?,
alphas: DispRows::try_from(alphas_block.clone())?,
triangle_tags: DispRows::try_from(triangle_tags_block.clone())?,
allowed_verts: DispInfo::parse_allowed_verts(allowed_verts_block)?,
})
}
}
impl Into<VmfBlock> for DispInfo {
fn into(self) -> VmfBlock {
let blocks = vec![
self.normals.into_vmf_block("normals"),
self.distances.into_vmf_block("distances"),
self.offsets.into_vmf_block("offsets"),
self.offset_normals.into_vmf_block("offset_normals"),
self.alphas.into_vmf_block("alphas"),
self.triangle_tags.into_vmf_block("triangle_tags"),
Self::allowed_verts_into_vmf_block(self.allowed_verts),
];
let mut key_values = IndexMap::new();
key_values.insert("power".to_string(), self.power.to_string());
key_values.insert("startposition".to_string(), self.start_position);
key_values.insert("elevation".to_string(), self.elevation.to_string());
key_values.insert("subdiv".to_string(), self.subdiv.to_01_string());
if let Some(flags) = self.flags {
key_values.insert("flags".to_string(), flags.to_string());
}
VmfBlock {
name: "dispinfo".to_string(),
key_values,
blocks,
}
}
}
impl VmfSerializable for DispInfo {
fn to_vmf_string(&self, indent_level: usize) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(256);
output.push_str(&format!("{}dispinfo\n", indent));
output.push_str(&format!("{}{{\n", indent));
output.push_str(&format!("{}\t\"power\" \"{}\"\n", indent, self.power));
output.push_str(&format!(
"{}\t\"startposition\" \"{}\"\n",
indent, self.start_position
));
if let Some(flags) = self.flags {
output.push_str(&format!("{}\t\"flags\" \"{}\"\n", indent, flags));
}
output.push_str(&format!(
"{}\t\"elevation\" \"{}\"\n",
indent, self.elevation
));
output.push_str(&format!(
"{}\t\"subdiv\" \"{}\"\n",
indent,
self.subdiv.to_01_string()
));
output.push_str(&self.normals.to_vmf_string(indent_level + 1, "normals"));
output.push_str(&self.distances.to_vmf_string(indent_level + 1, "distances"));
output.push_str(&self.offsets.to_vmf_string(indent_level + 1, "offsets"));
output.push_str(
&self
.offset_normals
.to_vmf_string(indent_level + 1, "offset_normals"),
);
output.push_str(&self.alphas.to_vmf_string(indent_level + 1, "alphas"));
output.push_str(
&self
.triangle_tags
.to_vmf_string(indent_level + 1, "triangle_tags"),
);
output.push_str(&Self::allowed_verts_to_vmf_string(
&self.allowed_verts,
indent_level + 1,
));
output.push_str(&format!("{}}}\n", indent));
output
}
}
impl DispInfo {
fn parse_allowed_verts(block: &VmfBlock) -> VmfResult<IndexMap<String, Vec<i32>>> {
let mut allowed_verts = IndexMap::new();
for (key, value) in &block.key_values {
let verts: VmfResult<Vec<i32>> = value
.split_whitespace()
.map(|s| {
s.parse::<i32>()
.map_err(|e| VmfError::ParseInt(e, s.to_string()))
})
.collect();
allowed_verts.insert(key.clone(), verts?);
}
Ok(allowed_verts)
}
fn allowed_verts_into_vmf_block(allowed_verts: IndexMap<String, Vec<i32>>) -> VmfBlock {
let mut key_values = IndexMap::new();
for (key, values) in allowed_verts {
key_values.insert(
key,
values
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(" "),
);
}
VmfBlock {
name: "allowed_verts".to_string(),
key_values,
blocks: Vec::new(),
}
}
fn allowed_verts_to_vmf_string(
allowed_verts: &IndexMap<String, Vec<i32>>,
indent_level: usize,
) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::new();
output.push_str(&format!("{}allowed_verts\n", indent));
output.push_str(&format!("{}{{\n", indent));
for (key, values) in allowed_verts {
output.push_str(&format!(
"{}\t\"{}\" \"{}\"\n",
indent,
key,
values
.iter()
.map(|v| v.to_string())
.collect::<Vec<String>>()
.join(" ")
));
}
output.push_str(&format!("{}}}\n", indent));
output
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct DispRows {
pub rows: Vec<String>,
}
impl TryFrom<VmfBlock> for DispRows {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let mut rows = Vec::with_capacity(block.key_values.len());
for (key, value) in block.key_values {
if key.starts_with("row") {
let index = key[3..]
.parse::<usize>()
.map_err(|e| VmfError::ParseInt(e, key.to_string()))?;
if index >= rows.len() {
rows.resize(index + 1, String::new());
}
rows[index] = value;
}
}
Ok(DispRows { rows })
}
}
impl DispRows {
fn into_vmf_block(self, name: &str) -> VmfBlock {
let mut key_values = IndexMap::new();
for (i, row) in self.rows.into_iter().enumerate() {
key_values.insert(format!("row{}", i), row);
}
VmfBlock {
name: name.to_string(),
key_values,
blocks: Vec::new(),
}
}
fn to_vmf_string(&self, indent_level: usize, name: &str) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(32);
output.push_str(&format!("{}{}\n", indent, name));
output.push_str(&format!("{}{{\n", indent));
for (i, row) in self.rows.iter().enumerate() {
output.push_str(&format!("{}\t\"row{}\" \"{}\"\n", indent, i, row));
}
output.push_str(&format!("{}}}\n", indent));
output
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Group {
pub id: u32,
pub editor: Editor,
}
impl TryFrom<VmfBlock> for Group {
type Error = VmfError;
fn try_from(block: VmfBlock) -> VmfResult<Self> {
let mut editor = None;
for inner_block in block.blocks {
if inner_block.name.eq_ignore_ascii_case("editor") {
editor = Some(Editor::try_from(inner_block)?);
}
}
Ok(Self {
id: parse_hs_key!(&block.key_values, "id", u32)?,
editor: editor.unwrap_or_default(),
})
}
}
impl Into<VmfBlock> for Group {
fn into(self) -> VmfBlock {
let mut blocks = Vec::with_capacity(2);
blocks.push(self.editor.into());
VmfBlock {
name: "group".to_string(),
key_values: {
let mut key_values = IndexMap::new();
key_values.insert("id".to_string(), self.id.to_string());
key_values
},
blocks,
}
}
}
impl VmfSerializable for Group {
fn to_vmf_string(&self, indent_level: usize) -> String {
let indent = "\t".repeat(indent_level);
let mut output = String::with_capacity(64);
output.push_str(&format!("{0}group\n{0}{{\n", indent));
output.push_str(&format!("{}\t\"id\" \"{}\"\n", indent, self.id));
output.push_str(&self.editor.to_vmf_string(indent_level + 1));
output.push_str(&format!("{}}}\n", indent));
output
}
}