272 lines
6.3 KiB
Rust
Raw Normal View History

2023-12-25 10:30:27 -08:00
use anyhow::{bail, Context, Result};
2024-01-12 22:47:40 -08:00
use galactica_util::to_radians;
use nalgebra::{Point2, Point3};
2023-12-25 10:30:27 -08:00
use std::collections::{HashMap, HashSet};
2023-12-24 23:03:00 -08:00
2024-01-12 14:34:31 -08:00
use crate::{
handle::SpriteHandle, util::Polar, Content, ContentBuildContext, SystemHandle,
SystemObjectHandle,
};
2023-12-24 23:03:00 -08:00
2023-12-30 16:57:03 -08:00
pub(crate) mod syntax {
2023-12-24 23:03:00 -08:00
use serde::Deserialize;
2023-12-31 18:48:35 -08:00
use std::collections::HashMap;
2023-12-27 19:51:58 -08:00
// Raw serde syntax structs.
// These are never seen by code outside this crate.
2023-12-24 23:03:00 -08:00
#[derive(Debug, Deserialize)]
pub struct System {
2023-12-27 19:51:58 -08:00
pub object: HashMap<String, Object>,
2023-12-24 23:03:00 -08:00
}
#[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>,
2024-01-13 18:56:50 -08:00
pub landable: Option<bool>,
2024-01-17 10:23:42 -08:00
pub name: Option<String>,
pub desc: Option<String>,
2023-12-25 10:30:27 -08:00
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum Position {
Polar(PolarCoords),
Cartesian(CoordinatesThree),
2023-12-25 10:30:27 -08:00
}
#[derive(Debug, Deserialize)]
pub struct PolarCoords {
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)]
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
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),
}
}
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum CoordinatesThree {
Label(String),
2023-12-25 16:22:44 -08:00
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),
}
}
}
2023-12-24 23:03:00 -08:00
}
2023-12-27 19:51:58 -08:00
// Processed data structs.
// These are exported.
2023-12-29 15:46:09 -08:00
/// Represents a star system
2024-01-11 20:21:07 -08:00
#[derive(Debug, Clone)]
2023-12-24 23:03:00 -08:00
pub struct System {
2023-12-29 15:46:09 -08:00
/// This star system's name
2023-12-25 09:01:12 -08:00
pub name: String,
2023-12-29 15:46:09 -08:00
2024-01-12 14:34:31 -08:00
/// This star system's handle
pub handle: SystemHandle,
2023-12-29 15:46:09 -08:00
/// Objects in this system
2024-01-12 14:34:31 -08:00
pub objects: Vec<SystemObject>,
2023-12-24 23:03:00 -08:00
}
2023-12-29 15:46:09 -08:00
/// 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.
2024-01-11 20:21:07 -08:00
#[derive(Debug, Clone)]
2024-01-12 14:34:31 -08:00
pub struct SystemObject {
2023-12-29 15:46:09 -08:00
/// This object's sprite
pub sprite: SpriteHandle,
2023-12-29 15:46:09 -08:00
2024-01-12 14:34:31 -08:00
/// This object's handle
pub handle: SystemObjectHandle,
2023-12-29 15:46:09 -08:00
/// This object's size.
/// Measured as height in game units.
/// This value is scaled for distance
/// (i.e, the z-value of position)
2023-12-25 16:22:44 -08:00
pub size: f32,
2023-12-29 15:46:09 -08:00
/// This object's position, in game coordinates,
/// relative to the system's center (0, 0).
2024-01-09 21:14:57 -08:00
pub pos: Point3<f32>,
2023-12-29 15:46:09 -08:00
2024-01-12 22:47:40 -08:00
/// This object's sprite's angle, in radians
pub angle: f32,
2024-01-13 18:56:50 -08:00
/// If true, ships may land on this object
pub landable: bool,
2024-01-17 10:23:42 -08:00
/// The display name of this object
pub name: String,
/// The description of this object
pub desc: String,
2023-12-24 23:03:00 -08:00
}
2023-12-27 19:51:58 -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-27 19:51:58 -08:00
objects: &HashMap<String, syntax::Object>,
cor: &syntax::CoordinatesThree,
2023-12-25 10:30:27 -08:00
mut cycle_detector: HashSet<String>,
) -> Result<Point3<f32>> {
2023-12-25 10:30:27 -08:00
match cor {
2023-12-27 19:51:58 -08:00
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()),
syntax::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-27 19:51:58 -08:00
/// Given an object, resolve its position as a Point3.
2023-12-25 10:30:27 -08:00
fn resolve_position(
2023-12-27 19:51:58 -08:00
objects: &HashMap<String, syntax::Object>,
obj: &syntax::Object,
2023-12-25 10:30:27 -08:00
cycle_detector: HashSet<String>,
) -> Result<Point3<f32>> {
2023-12-25 10:30:27 -08:00
match &obj.position {
2023-12-27 19:51:58 -08:00
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
syntax::Position::Polar(p) => {
2023-12-25 16:15:09 -08:00
let three = match &p.center {
2023-12-27 19:51:58 -08:00
syntax::CoordinatesTwo::Label(s) => syntax::CoordinatesThree::Label(s.clone()),
syntax::CoordinatesTwo::Coords(v) => {
syntax::CoordinatesThree::Coords([v[0], v[1], f32::NAN])
2023-12-25 16:15:09 -08:00
}
};
let r = resolve_coordinates(&objects, &three, cycle_detector)?;
let plane = Polar {
2024-01-12 22:47:40 -08:00
center: Point2::new(r.x, r.y),
radius: p.radius,
2024-01-12 22:47:40 -08:00
angle: to_radians(p.angle),
}
.to_cartesian();
2024-01-12 22:47:40 -08:00
Ok(Point3::new(plane.x, plane.y, p.z))
2023-12-25 10:30:27 -08:00
}
2023-12-24 23:03:00 -08:00
}
}
2023-12-30 16:57:03 -08:00
impl crate::Build for System {
type InputSyntaxType = HashMap<String, syntax::System>;
2024-01-05 12:09:59 -08:00
fn build(
system: Self::InputSyntaxType,
_build_context: &mut ContentBuildContext,
content: &mut Content,
) -> Result<()> {
2023-12-27 20:13:39 -08:00
for (system_name, system) in system {
2023-12-27 19:51:58 -08:00
let mut objects = Vec::new();
2023-12-24 23:03:00 -08:00
2024-01-12 14:34:31 -08:00
let system_handle = SystemHandle {
index: content.systems.len(),
};
2023-12-27 19:51:58 -08:00
for (label, obj) in &system.object {
let mut cycle_detector = HashSet::new();
cycle_detector.insert(label.clone());
2024-01-12 14:34:31 -08:00
let sprite_handle = match content.sprite_index.get(&obj.sprite) {
None => bail!(
"In system `{}`: sprite `{}` doesn't exist",
system_name,
obj.sprite
),
Some(t) => *t,
};
2023-12-27 19:51:58 -08:00
2024-01-12 14:34:31 -08:00
objects.push(SystemObject {
sprite: sprite_handle,
2024-01-09 21:14:57 -08:00
pos: resolve_position(&system.object, &obj, cycle_detector)
2023-12-27 19:51:58 -08:00
.with_context(|| format!("In object {:#?}", label))?,
size: obj.size,
2024-01-12 22:47:40 -08:00
angle: to_radians(obj.angle.unwrap_or(0.0)),
2024-01-12 14:34:31 -08:00
handle: SystemObjectHandle {
system_handle,
body_index: 0,
},
2024-01-13 18:56:50 -08:00
landable: obj.landable.unwrap_or(false),
2024-01-17 10:23:42 -08:00
name: obj
.name
.as_ref()
.map(|x| x.clone())
.unwrap_or("".to_string()),
// TODO: better linebreaks, handle double spaces
2024-01-17 10:25:03 -08:00
// Tabs
2024-01-17 10:23:42 -08:00
desc: obj
.desc
.as_ref()
.map(|x| x.replace("\n", " ").replace("<br>", "\n"))
.unwrap_or("".to_string()),
2023-12-27 19:51:58 -08:00
});
}
2023-12-24 23:03:00 -08:00
2024-01-12 14:34:31 -08:00
// Sort by z-distance. This is important, since these are
// rendered in this order. We need far objects to be behind
// near objects!
2024-01-11 22:10:36 -08:00
objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z));
2024-01-12 14:34:31 -08:00
// Update object handles
let mut i = 0;
for o in &mut objects {
o.handle.body_index = i;
i += 1;
}
2024-01-05 12:09:59 -08:00
content.systems.push(Self {
2024-01-12 14:34:31 -08:00
handle: system_handle,
name: system_name,
2023-12-27 19:51:58 -08:00
objects,
2023-12-24 23:03:00 -08:00
});
}
return Ok(());
2023-12-24 23:03:00 -08:00
}
}