#[cfg(feature = "std")]
extern crate std;
extern crate alloc;
#[cfg(feature = "std")]
use std::io;
use {
alloc::ffi::CString, alloc::format, alloc::string::String, alloc::vec::Vec,
core::ffi::CStr,
};
#[cfg(feature = "std")]
use crate::{WriteAttempt, WriteError};
pub type ValidationResult = Result<(), String>;
pub trait CheckWritable {
fn check_writable(&self) -> ValidationResult;
}
pub type Point = [f64; 3];
pub type Vec3 = [f64; 3];
pub type Vec2 = [f64; 2];
#[derive(Clone, Debug)]
pub struct QuakeMap {
pub entities: Vec<Entity>,
}
impl QuakeMap {
pub const fn new() -> Self {
QuakeMap {
entities: Vec::new(),
}
}
#[cfg(feature = "std")]
pub fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
for ent in &self.entities {
ent.write_to(writer)?;
}
Ok(())
}
}
impl Default for QuakeMap {
fn default() -> Self {
Self::new()
}
}
impl CheckWritable for QuakeMap {
fn check_writable(&self) -> ValidationResult {
for ent in &self.entities {
ent.check_writable()?;
}
Ok(())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EntityKind {
Point,
Brush,
}
#[derive(Clone, Debug)]
pub struct Entity {
pub edict: Edict,
pub brushes: Vec<Brush>,
}
impl Entity {
pub fn new() -> Self {
Entity {
edict: Edict::new(),
brushes: Vec::new(),
}
}
pub fn kind(&self) -> EntityKind {
if self.brushes.is_empty() {
EntityKind::Point
} else {
EntityKind::Brush
}
}
#[cfg(feature = "std")]
pub fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
self.check_writable().map_err(WriteError::Validation)?;
writer.write_all(b"{\r\n").map_err(WriteError::Io)?;
write_edict_to(&self.edict, writer)?;
for brush in &self.brushes {
write_brush_to(brush, writer)?;
}
writer.write_all(b"}\r\n").map_err(WriteError::Io)?;
Ok(())
}
}
impl Default for Entity {
fn default() -> Self {
Entity::new()
}
}
impl CheckWritable for Entity {
fn check_writable(&self) -> ValidationResult {
self.edict.check_writable()?;
for brush in &self.brushes {
brush.check_writable()?
}
Ok(())
}
}
pub type Edict = Vec<(CString, CString)>;
impl CheckWritable for Edict {
fn check_writable(&self) -> ValidationResult {
for (k, v) in self {
check_writable_quoted(k)?;
check_writable_quoted(v)?;
}
Ok(())
}
}
pub type Brush = Vec<Surface>;
impl CheckWritable for Brush {
fn check_writable(&self) -> ValidationResult {
for surface in self {
surface.check_writable()?;
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Surface {
pub half_space: HalfSpace,
pub texture: CString,
pub alignment: Alignment,
pub q2ext: Quake2SurfaceExtension,
}
impl Surface {
#[cfg(feature = "std")]
fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
write_half_space_to(&self.half_space, writer)?;
writer.write_all(b" ").map_err(WriteError::Io)?;
write_texture_to(&self.texture, writer)?;
writer.write_all(b" ").map_err(WriteError::Io)?;
self.alignment.write_to(writer)?;
if !self.q2ext.is_zeroed() {
writer.write_all(b" ").map_err(WriteError::Io)?;
self.q2ext.write_to(writer)?;
}
Ok(())
}
}
impl CheckWritable for Surface {
fn check_writable(&self) -> ValidationResult {
self.half_space.check_writable()?;
check_writable_texture(&self.texture)?;
self.alignment.check_writable()
}
}
pub type HalfSpace = [Point; 3];
impl CheckWritable for HalfSpace {
fn check_writable(&self) -> ValidationResult {
for num in self.iter().flatten() {
check_writable_f64(*num)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub struct Alignment {
pub offset: Vec2,
pub rotation: f64,
pub scale: Vec2,
pub axes: Option<[Vec3; 2]>,
}
impl Alignment {
#[cfg(feature = "std")]
fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
match self.axes {
None => {
write!(
writer,
"{} {} {} {} {}",
self.offset[0],
self.offset[1],
self.rotation,
self.scale[0],
self.scale[1]
)
.map_err(WriteError::Io)?;
}
Some([u, v]) => {
write!(
writer,
"[ {} {} {} {} ] [ {} {} {} {} ] {} {} {}",
u[0],
u[1],
u[2],
self.offset[0],
v[0],
v[1],
v[2],
self.offset[1],
self.rotation,
self.scale[0],
self.scale[1]
)
.map_err(WriteError::Io)?;
}
}
Ok(())
}
}
#[derive(Clone, Copy, Debug)]
pub struct Quake2SurfaceExtension {
pub content_flags: i32,
pub surface_flags: i32,
pub surface_value: f64,
}
impl Quake2SurfaceExtension {
pub fn is_zeroed(&self) -> bool {
self.content_flags == 0
&& self.surface_flags == 0
&& self.surface_value == 0.0
}
#[cfg(feature = "std")]
fn write_to<W: io::Write>(&self, writer: &mut W) -> WriteAttempt {
write!(
writer,
"{} {} {}",
self.content_flags, self.surface_flags, self.surface_value,
)
.map_err(WriteError::Io)?;
Ok(())
}
}
impl Default for Quake2SurfaceExtension {
fn default() -> Self {
Self {
content_flags: 0,
surface_flags: 0,
surface_value: 0.0,
}
}
}
impl CheckWritable for Alignment {
fn check_writable(&self) -> ValidationResult {
check_writable_array(self.offset)?;
check_writable_f64(self.rotation)?;
check_writable_array(self.scale)?;
if let Some(axes) = self.axes {
for axis in axes {
check_writable_array(axis)?;
}
}
Ok(())
}
}
#[cfg(feature = "std")]
fn write_edict_to<W: io::Write>(edict: &Edict, writer: &mut W) -> WriteAttempt {
for (key, value) in edict {
writer.write_all(b"\"").map_err(WriteError::Io)?;
writer.write_all(key.as_bytes()).map_err(WriteError::Io)?;
writer.write_all(b"\" \"").map_err(WriteError::Io)?;
writer.write_all(value.as_bytes()).map_err(WriteError::Io)?;
writer.write_all(b"\"\r\n").map_err(WriteError::Io)?;
}
Ok(())
}
#[cfg(feature = "std")]
fn write_brush_to<W: io::Write>(brush: &Brush, writer: &mut W) -> WriteAttempt {
writer.write_all(b"{\r\n").map_err(WriteError::Io)?;
for surf in brush {
surf.write_to(writer)?;
writer.write_all(b"\r\n").map_err(WriteError::Io)?;
}
writer.write_all(b"}\r\n").map_err(WriteError::Io)?;
Ok(())
}
#[cfg(feature = "std")]
fn write_half_space_to<W: io::Write>(
half_space: &HalfSpace,
writer: &mut W,
) -> WriteAttempt {
for (index, pt) in half_space.iter().enumerate() {
writer.write_all(b"( ").map_err(WriteError::Io)?;
for element in pt.iter() {
write!(writer, "{} ", element).map_err(WriteError::Io)?;
}
writer.write_all(b")").map_err(WriteError::Io)?;
if index != 2 {
writer.write_all(b" ").map_err(WriteError::Io)?;
}
}
Ok(())
}
#[cfg(feature = "std")]
fn write_texture_to<W: io::Write>(
texture: &CStr,
writer: &mut W,
) -> WriteAttempt {
let needs_quotes =
texture.to_bytes().iter().any(|c| c.is_ascii_whitespace())
|| texture.to_bytes().is_empty();
if needs_quotes {
writer.write_all(b"\"").map_err(WriteError::Io)?;
}
writer
.write_all(texture.to_bytes())
.map_err(WriteError::Io)?;
if needs_quotes {
writer.write_all(b"\"").map_err(WriteError::Io)?;
}
Ok(())
}
fn check_writable_array<const N: usize>(arr: [f64; N]) -> ValidationResult {
for num in arr {
check_writable_f64(num)?;
}
Ok(())
}
fn check_writable_f64(num: f64) -> ValidationResult {
if num.is_finite() {
Ok(())
} else {
Err(format!("Non-finite number ({})", num))
}
}
fn check_writable_texture(s: &CStr) -> ValidationResult {
if check_writable_unquoted(s).is_ok() {
return Ok(());
}
match check_writable_quoted(s) {
Ok(_) => Ok(()),
Err(_) => Err(format!(
"Cannot write texture {:?}, not quotable and contains whitespace",
s
)),
}
}
fn check_writable_quoted(s: &CStr) -> ValidationResult {
let bad_chars = [b'"', b'\r', b'\n'];
for c in s.to_bytes() {
if bad_chars.contains(c) {
return Err(format!(
"Cannot write quote-wrapped string, contains {:?}",
char::from(*c)
));
}
}
Ok(())
}
fn check_writable_unquoted(s: &CStr) -> ValidationResult {
let s_bytes = s.to_bytes();
if s_bytes.is_empty() {
return Err(String::from("Cannot write unquoted empty string"));
}
if s_bytes[0] == b'"' {
return Err(String::from("Cannot lead unquoted string with quote"));
}
if contains_ascii_whitespace(s) {
Err(String::from(
"Cannot write unquoted string, contains whitespace",
))
} else {
Ok(())
}
}
fn contains_ascii_whitespace(s: &CStr) -> bool {
s.to_bytes().iter().any(|c| c.is_ascii_whitespace())
}