mirror of
https://codeberg.org/icewind/vbsp.git
synced 2026-06-03 18:54:05 +02:00
split off common types into a separate crate for easier semver management
This commit is contained in:
parent
57e1eac4b6
commit
76918206da
19 changed files with 394 additions and 316 deletions
7
common/Cargo.lock
generated
Normal file
7
common/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "types"
|
||||
version = "0.1.0"
|
||||
16
common/Cargo.toml
Normal file
16
common/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "vbsp-common"
|
||||
version = "0.1.0"
|
||||
authors = ["Robin Appelman <robin@icewind.nl>"]
|
||||
homepage = "https://github.com/icewind1991/vbsp"
|
||||
repository = "https://github.com/icewind1991/vbsp"
|
||||
description = "Common types and helpers for valve bsp files."
|
||||
license = "MIT"
|
||||
edition = "2024"
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.14.1"
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
cgmath = "0.18.0"
|
||||
thiserror = "2.0.11"
|
||||
45
common/src/angle.rs
Normal file
45
common/src/angle.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use crate::EntityParseError;
|
||||
use binrw::BinRead;
|
||||
use cgmath::{Deg, Quaternion, Rotation3};
|
||||
use serde::de::{Error, Unexpected};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Copy, Clone, BinRead, Default)]
|
||||
pub struct Angles {
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
pub roll: f32,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Angles {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <&str>::deserialize(deserializer)?;
|
||||
str.parse()
|
||||
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of angles"))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Angles {
|
||||
type Err = EntityParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut floats = s.split_whitespace().map(f32::from_str);
|
||||
let pitch = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let yaw = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let roll = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
Ok(Angles { pitch, yaw, roll })
|
||||
}
|
||||
}
|
||||
|
||||
impl Angles {
|
||||
pub fn as_quaternion(&self) -> Quaternion<f32> {
|
||||
// angles are applied in roll, pitch, yaw order
|
||||
Quaternion::from_angle_y(Deg(self.yaw))
|
||||
* Quaternion::from_angle_x(Deg(self.pitch))
|
||||
* Quaternion::from_angle_z(Deg(self.roll))
|
||||
}
|
||||
}
|
||||
38
common/src/bool.rs
Normal file
38
common/src/bool.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use serde::Deserializer;
|
||||
use serde::de::{Error, Unexpected};
|
||||
use std::fmt;
|
||||
|
||||
struct BoolVisitor;
|
||||
impl serde::de::Visitor<'_> for BoolVisitor {
|
||||
type Value = bool;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "bool value")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match v {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match v {
|
||||
"0" | "no" => Ok(false),
|
||||
"1" | "yes" => Ok(true),
|
||||
other => Err(E::invalid_value(Unexpected::Str(other), &"bool")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_bool<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
|
||||
deserializer.deserialize_any(BoolVisitor)
|
||||
}
|
||||
38
common/src/color.rs
Normal file
38
common/src/color.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use crate::{EntityParseError, EntityProp};
|
||||
use serde::de::{Error, Unexpected};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Color {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
}
|
||||
|
||||
impl FromStr for Color {
|
||||
type Err = EntityParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut floats = s.split_whitespace().map(u8::from_str);
|
||||
let r = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let g = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let b = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
if floats.next().is_some() {
|
||||
return Err(EntityParseError::ElementCount);
|
||||
}
|
||||
Ok(Self { r, g, b })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Color {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <&str>::deserialize(deserializer)?;
|
||||
let [r, g, b] = <[u8; 3]>::parse(str)
|
||||
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a list of 3 numbers"))?;
|
||||
Ok(Color { r, g, b })
|
||||
}
|
||||
}
|
||||
17
common/src/lib.rs
Normal file
17
common/src/lib.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
mod angle;
|
||||
mod bool;
|
||||
mod color;
|
||||
mod lightcolor;
|
||||
mod negated;
|
||||
mod prop;
|
||||
mod property;
|
||||
mod vector;
|
||||
|
||||
pub use angle::Angles;
|
||||
pub use bool::deserialize_bool;
|
||||
pub use color::Color;
|
||||
pub use lightcolor::LightColor;
|
||||
pub use negated::Negated;
|
||||
pub use prop::{AsPropPlacement, PropPlacement};
|
||||
pub use property::{EntityParseError, EntityProp, FromStrProp};
|
||||
pub use vector::Vector;
|
||||
55
common/src/lightcolor.rs
Normal file
55
common/src/lightcolor.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use crate::EntityParseError;
|
||||
use serde::de::{Error, Unexpected};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightColor {
|
||||
pub r: u8,
|
||||
pub g: u8,
|
||||
pub b: u8,
|
||||
pub intensity: u16,
|
||||
}
|
||||
|
||||
impl FromStr for LightColor {
|
||||
type Err = EntityParseError;
|
||||
|
||||
fn from_str(str: &str) -> Result<Self, Self::Err> {
|
||||
let mut values = str.split_whitespace();
|
||||
let r = values
|
||||
.next()
|
||||
.ok_or(EntityParseError::ElementCount)?
|
||||
.parse()
|
||||
.map_err(EntityParseError::Int)?;
|
||||
let g = values
|
||||
.next()
|
||||
.ok_or(EntityParseError::ElementCount)?
|
||||
.parse()
|
||||
.map_err(EntityParseError::Int)?;
|
||||
let b = values
|
||||
.next()
|
||||
.ok_or(EntityParseError::ElementCount)?
|
||||
.parse()
|
||||
.map_err(EntityParseError::Int)?;
|
||||
let intensity = values
|
||||
.next()
|
||||
.ok_or(EntityParseError::ElementCount)?
|
||||
.parse()
|
||||
.map_err(EntityParseError::Int)?;
|
||||
if values.next().is_some() {
|
||||
return Err(EntityParseError::ElementCount);
|
||||
}
|
||||
Ok(LightColor { r, g, b, intensity })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for LightColor {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <&str>::deserialize(deserializer)?;
|
||||
str.parse()
|
||||
.map_err(|_| D::Error::invalid_value(Unexpected::Str(str), &"a list of 4 integers"))
|
||||
}
|
||||
}
|
||||
61
common/src/negated.rs
Normal file
61
common/src/negated.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use serde::de::{Error, Unexpected};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Negated {
|
||||
Yes,
|
||||
No,
|
||||
MatchingCriteria,
|
||||
}
|
||||
pub struct NegatedParseErr;
|
||||
impl FromStr for Negated {
|
||||
type Err = NegatedParseErr;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"1" => Ok(Negated::Yes),
|
||||
"0" => Ok(Negated::No),
|
||||
"allow entities that match criteria" => Ok(Negated::MatchingCriteria),
|
||||
_ => Err(NegatedParseErr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NegatedVisitor;
|
||||
impl serde::de::Visitor<'_> for NegatedVisitor {
|
||||
type Value = Negated;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(formatter, "Negated value")
|
||||
}
|
||||
|
||||
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
match v {
|
||||
0 => Ok(Negated::No),
|
||||
1 => Ok(Negated::Yes),
|
||||
_ => Err(E::invalid_value(Unexpected::Signed(v), &"0 or 1")),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
v.parse()
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(v), &"Negated"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Negated {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_any(NegatedVisitor)
|
||||
}
|
||||
}
|
||||
16
common/src/prop.rs
Normal file
16
common/src/prop.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use crate::Vector;
|
||||
use cgmath::Quaternion;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PropPlacement<'a> {
|
||||
pub model: &'a str,
|
||||
pub rotation: Quaternion<f32>,
|
||||
pub scale: f32,
|
||||
pub origin: Vector,
|
||||
pub skin: i32,
|
||||
}
|
||||
|
||||
/// Abstraction for various ways props are placed in a bsp
|
||||
pub trait AsPropPlacement<'a> {
|
||||
fn as_prop_placement(&self) -> PropPlacement<'a>;
|
||||
}
|
||||
74
common/src/property.rs
Normal file
74
common/src/property.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use crate::{Angles, Color, LightColor, Negated, Vector};
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait EntityProp<'a>: Sized {
|
||||
fn parse(raw: &'a str) -> Result<Self, EntityParseError>;
|
||||
}
|
||||
|
||||
/// Properties that can be parsed with their FromStr implementation
|
||||
pub trait FromStrProp: FromStr {}
|
||||
|
||||
impl FromStrProp for u8 {}
|
||||
impl FromStrProp for u16 {}
|
||||
impl FromStrProp for f32 {}
|
||||
impl FromStrProp for u32 {}
|
||||
impl FromStrProp for i32 {}
|
||||
impl FromStrProp for Color {}
|
||||
impl FromStrProp for Angles {}
|
||||
impl FromStrProp for Vector {}
|
||||
impl FromStrProp for LightColor {}
|
||||
impl FromStrProp for Negated {}
|
||||
|
||||
impl<T: FromStrProp> EntityProp<'_> for T
|
||||
where
|
||||
EntityParseError: From<<T as FromStr>::Err>,
|
||||
{
|
||||
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
|
||||
Ok(raw.parse()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromStrProp, const N: usize> EntityProp<'_> for [T; N]
|
||||
where
|
||||
EntityParseError: From<<T as FromStr>::Err>,
|
||||
[T; N]: Default,
|
||||
{
|
||||
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
|
||||
let mut values = raw.split_whitespace().map(T::from_str);
|
||||
let mut result = <[T; N]>::default();
|
||||
for item in result.iter_mut() {
|
||||
*item = values.next().ok_or(EntityParseError::ElementCount)??;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EntityProp<'a> for &'a str {
|
||||
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
|
||||
Ok(raw)
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityProp<'_> for bool {
|
||||
fn parse(raw: &'_ str) -> Result<Self, EntityParseError> {
|
||||
Ok(raw != "0" && raw != "no")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: EntityProp<'a>> EntityProp<'a> for Option<T> {
|
||||
fn parse(raw: &'a str) -> Result<Self, EntityParseError> {
|
||||
Ok(Some(T::parse(raw)?))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EntityParseError {
|
||||
#[error("wrong number of elements")]
|
||||
ElementCount,
|
||||
#[error(transparent)]
|
||||
Float(#[from] ParseFloatError),
|
||||
#[error(transparent)]
|
||||
Int(#[from] ParseIntError),
|
||||
}
|
||||
128
common/src/vector.rs
Normal file
128
common/src/vector.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
use crate::EntityParseError;
|
||||
use binrw::BinRead;
|
||||
use cgmath::Vector3;
|
||||
use serde::de::{Error, Unexpected};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, BinRead, Default)]
|
||||
pub struct Vector {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
pub fn iter(&self) -> impl Iterator<Item = f32> {
|
||||
[self.x, self.y, self.z].into_iter()
|
||||
}
|
||||
|
||||
pub fn length_squared(&self) -> f32 {
|
||||
self.x.powf(2.0) + self.y.powf(2.0) + self.z.powf(2.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vector> for Vector {
|
||||
type Output = Vector;
|
||||
|
||||
fn add(self, rhs: Vector) -> Self::Output {
|
||||
Vector {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
z: self.z + rhs.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vector> for Vector {
|
||||
type Output = Vector;
|
||||
|
||||
fn sub(self, rhs: Vector) -> Self::Output {
|
||||
Vector {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
z: self.z - rhs.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vector {
|
||||
type Output = Vector;
|
||||
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Vector {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
z: self.z * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Vector {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y && self.z == other.z
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Vector {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.length_squared().partial_cmp(&other.length_squared())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector> for [f32; 3] {
|
||||
fn from(vector: Vector) -> Self {
|
||||
[vector.x, vector.y, vector.z]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 3]> for Vector {
|
||||
fn from(vector: [f32; 3]) -> Self {
|
||||
Vector {
|
||||
x: vector[0],
|
||||
y: vector[1],
|
||||
z: vector[2],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vector> for [f32; 3] {
|
||||
fn from(vector: &Vector) -> Self {
|
||||
[vector.x, vector.y, vector.z]
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Vector {
|
||||
type Err = EntityParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut floats = s.split_whitespace().map(f32::from_str);
|
||||
let x = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let y = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
let z = floats.next().ok_or(EntityParseError::ElementCount)??;
|
||||
if floats.next().is_some() {
|
||||
return Err(EntityParseError::ElementCount);
|
||||
}
|
||||
Ok(Vector { x, y, z })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector> for Vector3<f32> {
|
||||
fn from(v: Vector) -> Self {
|
||||
Vector3::new(v.x, v.y, v.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Vector {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = <&str>::deserialize(deserializer)?;
|
||||
str.parse()
|
||||
.map_err(|_| D::Error::invalid_value(Unexpected::Other(str), &"a vector"))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue