#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
use std::{
fs::{self, File},
io::{ErrorKind, Read},
path::PathBuf,
};
use bytes::Bytes;
use glam::{IVec3, Vec3};
use insim_core::{Decode, Encode};
use thiserror::Error;
#[non_exhaustive]
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum Error {
#[error("IO Error: {kind}: {message}")]
IO { kind: ErrorKind, message: String },
#[error("ReadWriteBuf Err {0:?}")]
Encode(#[from] insim_core::EncodeError),
#[error("ReadWriteBuf Err {0:?}")]
Decode(#[from] insim_core::DecodeError),
}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Self {
Error::IO {
kind: e.kind(),
message: e.to_string(),
}
}
}
#[derive(Debug, Copy, Clone, Default, PartialEq, insim_core::Decode, insim_core::Encode)]
pub struct Limit {
pub left: f32,
pub right: f32,
}
#[derive(Debug, Copy, Clone, Default, PartialEq, insim_core::Decode, insim_core::Encode)]
pub struct Node {
pub center: IVec3,
pub direction: Vec3,
pub outer_limit: Limit,
pub road_limit: Limit,
}
impl Node {
pub fn get_center(&self, scale: Option<f32>) -> Vec3 {
let scale = scale.unwrap_or(1.0);
Vec3 {
x: self.center.x as f32 / scale,
y: self.center.y as f32 / scale,
z: self.center.z as f32 / scale,
}
}
pub fn get_road_limit(&self, scale: Option<f32>) -> (Vec3, Vec3) {
self.calculate_limit_position(&self.road_limit, scale)
}
pub fn get_outer_limit(&self, scale: Option<f32>) -> (Vec3, Vec3) {
self.calculate_limit_position(&self.outer_limit, scale)
}
fn calculate_limit_position(&self, limit: &Limit, scale: Option<f32>) -> (Vec3, Vec3) {
let center = self.get_center(scale);
let dx = self.direction.y;
let dy = self.direction.x;
let length = dx.hypot(dy);
if length.abs() < f32::EPSILON {
return (Vec3::ZERO, Vec3::ZERO);
}
let cos_theta = dx / length;
let sin_theta = dy / length;
let left = Vec3 {
x: center.x + limit.left * cos_theta,
y: center.y + limit.left * -sin_theta,
z: center.z,
};
let right = Vec3 {
x: center.x + limit.right * cos_theta,
y: center.y + limit.right * -sin_theta,
z: center.z,
};
(left, right)
}
}
#[derive(Debug, Default, PartialEq)]
pub struct Pth {
pub version: u8,
pub revision: u8,
pub finish_line_node: i32,
pub nodes: Vec<Node>,
}
impl Decode for Pth {
fn decode(buf: &mut Bytes) -> Result<Self, insim_core::DecodeError> {
let magic = <[u8; 6]>::decode(buf)?;
if &magic != b"LFSPTH" {
unimplemented!("Not a LFS PTH file");
}
let version = u8::decode(buf)?;
let revision = u8::decode(buf)?;
let mut num_nodes = i32::decode(buf)?;
let finish_line_node = i32::decode(buf)?;
let mut nodes = Vec::new();
while num_nodes > 0 {
nodes.push(Node::decode(buf)?);
num_nodes -= 1;
}
Ok(Self {
version,
revision,
finish_line_node,
nodes,
})
}
}
impl Encode for Pth {
fn encode(&self, buf: &mut bytes::BytesMut) -> Result<(), insim_core::EncodeError> {
buf.extend_from_slice(b"LFSPTH");
self.version.encode(buf)?;
self.revision.encode(buf)?;
if self.nodes.len() > (i32::MAX as usize) {
return Err(insim_core::EncodeError::TooLarge);
}
(self.nodes.len() as i32).encode(buf)?;
self.finish_line_node.encode(buf)?;
for i in self.nodes.iter() {
i.encode(buf)?;
}
Ok(())
}
}
impl Pth {
pub fn from_file(i: &mut File) -> Result<Self, Error> {
let mut data = vec![];
let _ = i.read_to_end(&mut data)?;
let mut buf = Bytes::from(data);
Pth::decode(&mut buf).map_err(Error::from)
}
pub fn from_pathbuf(i: &PathBuf) -> Result<Self, Error> {
if !i.exists() {
return Err(Error::IO {
kind: std::io::ErrorKind::NotFound,
message: format!("Path {i:?} does not exist"),
});
}
let mut input = fs::File::open(i).map_err(Error::from)?;
Self::from_file(&mut input)
}
}
#[cfg(test)]
mod test {
use bytes::BytesMut;
use super::*;
fn assert_valid_as1_pth(p: &Pth) {
assert_eq!(p.version, 0);
assert_eq!(p.revision, 0);
assert_eq!(p.finish_line_node, 250);
}
#[test]
fn test_pth_decode_from_pathbuf() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./tests/AS1.pth");
let p = Pth::from_pathbuf(&path).expect("Expected PTH file to be parsed");
assert_valid_as1_pth(&p)
}
#[test]
fn test_pth_decode_from_file() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./tests/AS1.pth");
let mut file = File::open(path).expect("Expected Autocross_3DH.smx to exist");
let p = Pth::from_file(&mut file).expect("Expected PTH file to be parsed");
assert_valid_as1_pth(&p)
}
#[test]
fn test_pth_encode() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("./tests/AS1.pth");
let p = Pth::from_pathbuf(&path).expect("Expected SMX file to be parsed");
let mut file = File::open(path).expect("Expected AS1.pth to exist");
let mut raw: Vec<u8> = Vec::new();
let _ = file
.read_to_end(&mut raw)
.expect("Expected to read whole file");
let mut inner = BytesMut::new();
p.encode(&mut inner)
.expect("Should not fail to write pth file");
assert_eq!(inner.as_ref(), raw);
}
}