2023-12-25 10:30:27 -08:00
|
|
|
use anyhow::{bail, Context, Result};
|
2023-12-25 15:56:27 -08:00
|
|
|
use cgmath::{Deg, Point3};
|
2023-12-25 10:30:27 -08:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-12-24 23:03:00 -08:00
|
|
|
|
2023-12-25 16:22:44 -08:00
|
|
|
use crate::physics::Polar;
|
2023-12-24 23:03:00 -08:00
|
|
|
|
|
|
|
/// Toml file syntax
|
|
|
|
pub(in crate::content) mod toml {
|
|
|
|
use serde::Deserialize;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
pub struct SystemRoot {
|
|
|
|
pub system: System,
|
|
|
|
pub object: HashMap<String, Object>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
pub struct System {
|
|
|
|
pub name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
pub struct Object {
|
|
|
|
pub sprite: String,
|
2023-12-25 10:30:27 -08:00
|
|
|
pub position: Position,
|
2023-12-24 23:03:00 -08:00
|
|
|
|
2023-12-25 16:22:44 -08:00
|
|
|
pub size: f32,
|
2023-12-25 09:01:12 -08:00
|
|
|
|
2023-12-25 16:22:44 -08:00
|
|
|
pub radius: Option<f32>,
|
|
|
|
pub angle: Option<f32>,
|
2023-12-25 10:30:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum Position {
|
|
|
|
Polar(PolarCoords),
|
2023-12-25 15:56:27 -08:00
|
|
|
Cartesian(CoordinatesThree),
|
2023-12-25 10:30:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
pub struct PolarCoords {
|
2023-12-25 15:56:27 -08:00
|
|
|
pub center: CoordinatesTwo,
|
2023-12-25 16:22:44 -08:00
|
|
|
pub radius: f32,
|
|
|
|
pub angle: f32,
|
|
|
|
pub z: f32,
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
2023-12-25 15:56:27 -08:00
|
|
|
pub enum CoordinatesTwo {
|
2023-12-24 23:03:00 -08:00
|
|
|
Label(String),
|
2023-12-25 16:22:44 -08:00
|
|
|
Coords([f32; 2]),
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
2023-12-25 09:01:12 -08:00
|
|
|
|
2023-12-25 15:56:27 -08:00
|
|
|
impl ToString for CoordinatesTwo {
|
2023-12-25 09:01:12 -08:00
|
|
|
fn to_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Self::Label(s) => s.to_owned(),
|
|
|
|
Self::Coords(v) => format!("{:?}", v),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-25 15:56:27 -08:00
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum CoordinatesThree {
|
|
|
|
Label(String),
|
2023-12-25 16:22:44 -08:00
|
|
|
Coords([f32; 3]),
|
2023-12-25 15:56:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ToString for CoordinatesThree {
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
Self::Label(s) => s.to_owned(),
|
|
|
|
Self::Coords(v) => format!("{:?}", v),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct System {
|
2023-12-25 09:01:12 -08:00
|
|
|
pub name: String,
|
|
|
|
pub objects: Vec<Object>,
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2023-12-25 09:01:12 -08:00
|
|
|
pub struct Object {
|
|
|
|
pub sprite: String,
|
2023-12-25 15:56:27 -08:00
|
|
|
pub position: Point3<f32>,
|
2023-12-25 16:22:44 -08:00
|
|
|
pub size: f32,
|
|
|
|
pub angle: Deg<f32>,
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
|
|
|
|
2023-12-25 15:56:27 -08:00
|
|
|
// Helper function for resolve_position, never called on its own.
|
2023-12-25 10:30:27 -08:00
|
|
|
fn resolve_coordinates(
|
2023-12-24 23:03:00 -08:00
|
|
|
objects: &HashMap<String, toml::Object>,
|
2023-12-25 15:56:27 -08:00
|
|
|
cor: &toml::CoordinatesThree,
|
2023-12-25 10:30:27 -08:00
|
|
|
mut cycle_detector: HashSet<String>,
|
2023-12-25 15:56:27 -08:00
|
|
|
) -> Result<Point3<f32>> {
|
2023-12-25 10:30:27 -08:00
|
|
|
match cor {
|
2023-12-25 15:56:27 -08:00
|
|
|
toml::CoordinatesThree::Coords(c) => Ok((*c).into()),
|
|
|
|
toml::CoordinatesThree::Label(l) => {
|
2023-12-25 10:30:27 -08:00
|
|
|
if cycle_detector.contains(l) {
|
|
|
|
bail!(
|
|
|
|
"Found coordinate cycle: `{}`",
|
|
|
|
cycle_detector.iter().fold(String::new(), |sum, a| {
|
|
|
|
if sum.is_empty() {
|
|
|
|
a.to_string()
|
|
|
|
} else {
|
|
|
|
sum + " -> " + a
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
2023-12-25 09:01:12 -08:00
|
|
|
}
|
2023-12-25 10:30:27 -08:00
|
|
|
cycle_detector.insert(l.to_owned());
|
|
|
|
|
|
|
|
let p = match objects.get(l) {
|
|
|
|
Some(p) => p,
|
|
|
|
None => bail!("Could not resolve coordinate label `{l}`"),
|
|
|
|
};
|
|
|
|
Ok(resolve_position(&objects, &p, cycle_detector)
|
|
|
|
.with_context(|| format!("In object {:#?}", l))?)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-25 15:56:27 -08:00
|
|
|
/// Given an object, resolve it's position as a Point3.
|
2023-12-25 10:30:27 -08:00
|
|
|
fn resolve_position(
|
|
|
|
objects: &HashMap<String, toml::Object>,
|
|
|
|
obj: &toml::Object,
|
|
|
|
cycle_detector: HashSet<String>,
|
2023-12-25 15:56:27 -08:00
|
|
|
) -> Result<Point3<f32>> {
|
2023-12-25 10:30:27 -08:00
|
|
|
match &obj.position {
|
2023-12-25 15:56:27 -08:00
|
|
|
toml::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
|
|
|
|
toml::Position::Polar(p) => {
|
2023-12-25 16:15:09 -08:00
|
|
|
let three = match &p.center {
|
|
|
|
toml::CoordinatesTwo::Label(s) => toml::CoordinatesThree::Label(s.clone()),
|
|
|
|
toml::CoordinatesTwo::Coords(v) => {
|
|
|
|
toml::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let r = resolve_coordinates(&objects, &three, cycle_detector)?;
|
2023-12-25 15:56:27 -08:00
|
|
|
let plane = Polar {
|
|
|
|
center: (r.x, r.y).into(),
|
|
|
|
radius: p.radius,
|
|
|
|
angle: Deg(p.angle),
|
|
|
|
}
|
|
|
|
.to_cartesian();
|
|
|
|
Ok(Point3 {
|
|
|
|
x: plane.x,
|
|
|
|
y: plane.y,
|
|
|
|
z: p.z,
|
|
|
|
})
|
2023-12-25 10:30:27 -08:00
|
|
|
}
|
2023-12-24 23:03:00 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl System {
|
|
|
|
pub fn parse(value: toml::SystemRoot) -> Result<Self> {
|
|
|
|
let mut objects = Vec::new();
|
|
|
|
|
2023-12-25 09:01:12 -08:00
|
|
|
for (label, obj) in &value.object {
|
2023-12-25 10:30:27 -08:00
|
|
|
let mut cycle_detector = HashSet::new();
|
|
|
|
cycle_detector.insert(label.to_owned());
|
2023-12-24 23:03:00 -08:00
|
|
|
|
|
|
|
objects.push(Object {
|
|
|
|
sprite: obj.sprite.clone(),
|
2023-12-25 10:30:27 -08:00
|
|
|
position: resolve_position(&value.object, &obj, cycle_detector)
|
|
|
|
.with_context(|| format!("In object {:#?}", label))?,
|
2023-12-25 09:01:12 -08:00
|
|
|
size: obj.size,
|
2023-12-25 10:30:27 -08:00
|
|
|
angle: Deg(obj.angle.unwrap_or(0.0)),
|
2023-12-24 23:03:00 -08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return Ok(Self {
|
|
|
|
name: value.system.name.clone(),
|
|
|
|
objects,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|