use anyhow::{bail, Context, Result}; use cgmath::{Deg, Point3, Rad}; use std::collections::{HashMap, HashSet}; use crate::{handle::SpriteHandle, util::Polar, Content, ContentBuildContext}; pub(crate) mod syntax { use serde::Deserialize; use std::collections::HashMap; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct System { pub object: HashMap, } #[derive(Debug, Deserialize)] pub struct Object { pub sprite: String, pub position: Position, pub size: f32, pub radius: Option, pub angle: Option, } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum Position { Polar(PolarCoords), Cartesian(CoordinatesThree), } #[derive(Debug, Deserialize)] pub struct PolarCoords { pub center: CoordinatesTwo, pub radius: f32, pub angle: f32, pub z: f32, } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum CoordinatesTwo { Label(String), Coords([f32; 2]), } impl ToString for CoordinatesTwo { fn to_string(&self) -> String { match self { Self::Label(s) => s.to_owned(), Self::Coords(v) => format!("{:?}", v), } } } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum CoordinatesThree { Label(String), Coords([f32; 3]), } impl ToString for CoordinatesThree { fn to_string(&self) -> String { match self { Self::Label(s) => s.to_owned(), Self::Coords(v) => format!("{:?}", v), } } } } // Processed data structs. // These are exported. /// Represents a star system #[derive(Debug)] pub struct System { /// This star system's name pub name: String, /// Objects in this system pub objects: Vec, } /// Represents an orbiting body in a star system /// (A star, planet, moon, satellite, etc) /// These may be landable and may be decorative. /// System objects to not interact with the physics engine. #[derive(Debug)] pub struct Object { /// This object's sprite pub sprite: SpriteHandle, /// This object's size. /// Measured as height in game units. /// This value is scaled for distance /// (i.e, the z-value of position) pub size: f32, /// This object's position, in game coordinates, /// relative to the system's center (0, 0). pub pos: Point3, /// This object's sprite's angle. pub angle: Rad, } /// Helper function for resolve_position, never called on its own. fn resolve_coordinates( objects: &HashMap, cor: &syntax::CoordinatesThree, mut cycle_detector: HashSet, ) -> Result> { match cor { syntax::CoordinatesThree::Coords(c) => Ok((*c).into()), syntax::CoordinatesThree::Label(l) => { 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 } }) ); } 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))?) } } } /// Given an object, resolve its position as a Point3. fn resolve_position( objects: &HashMap, obj: &syntax::Object, cycle_detector: HashSet, ) -> Result> { match &obj.position { syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?), syntax::Position::Polar(p) => { let three = match &p.center { syntax::CoordinatesTwo::Label(s) => syntax::CoordinatesThree::Label(s.clone()), syntax::CoordinatesTwo::Coords(v) => { syntax::CoordinatesThree::Coords([v[0], v[1], f32::NAN]) } }; let r = resolve_coordinates(&objects, &three, cycle_detector)?; let plane = Polar { center: (r.x, r.y).into(), radius: p.radius, angle: Deg(p.angle).into(), } .to_cartesian(); Ok(Point3 { x: plane.x, y: plane.y, z: p.z, }) } } } impl crate::Build for System { type InputSyntaxType = HashMap; fn build( system: Self::InputSyntaxType, _build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result<()> { for (system_name, system) in system { let mut objects = Vec::new(); for (label, obj) in &system.object { let mut cycle_detector = HashSet::new(); cycle_detector.insert(label.clone()); let handle = match content.sprite_index.get(&obj.sprite) { None => bail!( "In system `{}`: sprite `{}` doesn't exist", system_name, obj.sprite ), Some(t) => *t, }; objects.push(Object { sprite: handle, pos: resolve_position(&system.object, &obj, cycle_detector) .with_context(|| format!("In object {:#?}", label))?, size: obj.size, angle: Deg(obj.angle.unwrap_or(0.0)).into(), }); } objects.sort_by(|a, b| a.pos.z.total_cmp(&b.pos.z)); content.systems.push(Self { name: system_name, objects, }); } return Ok(()); } }