all_is_cubes/character/
spawn.rs

1use alloc::vec::Vec;
2
3use euclid::{Point3D, Vector3D};
4
5use crate::camera::eye_for_look_at;
6use crate::inv::Slot;
7use crate::math::{Cube, Face6, FreeCoordinate, FreePoint, FreeVector, GridAab, NotNan};
8#[cfg(feature = "save")]
9use crate::save::schema;
10use crate::universe::{HandleVisitor, VisitHandles};
11
12#[cfg(doc)]
13use crate::space::Space;
14
15/// Defines the initial state of a [`Character`] that is being created or moved into a [`Space`].
16///
17/// TODO: This is lacking a full set of accessor methods to be viewable+editable.
18///
19#[doc = include_str!("../save/serde-warning.md")]
20///
21/// [`Character`]: super::Character
22#[derive(Clone, Debug, Eq, PartialEq)]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24pub struct Spawn {
25    /// Volume which is permitted to be occupied.
26    pub(super) bounds: GridAab,
27
28    /// Desired eye position, in cube coordinates.
29    pub(super) eye_position: Option<Point3D<NotNan<FreeCoordinate>, Cube>>,
30
31    /// Direction the character should be facing, or looking at.
32    ///
33    /// TODO: Should we represent a full rotation (quaternion) instead?
34    /// Or something that can't be zero? Nonzero integers, perhaps?
35    pub(super) look_direction: Vector3D<NotNan<FreeCoordinate>, Cube>,
36
37    /// Initial inventory contents, created from nothing.
38    pub(super) inventory: Vec<Slot>,
39}
40
41impl Spawn {
42    /// Create the default Spawn configuration for a Space.
43    ///
44    /// TODO: There is no good default, really: we don't know if it is better to be
45    /// outside the space looking in or to be within it at some particular position.
46    /// Come up with some kind of hint that we can use to configure this better without
47    /// necessarily mandating a specification.
48    pub fn default_for_new_space(bounds: GridAab) -> Self {
49        Spawn {
50            bounds: bounds.abut(Face6::PZ, 40).unwrap_or(bounds),
51            eye_position: None,
52            look_direction: Vector3D::new(NotNan::from(0), NotNan::from(0), NotNan::from(-1)),
53            inventory: vec![],
54        }
55    }
56
57    /// Constructs a [`Spawn`] point located outside the [`Space`] and with its bounds in
58    /// frame.
59    ///
60    /// `direction` gives the direction in which the character will lie relative to the
61    /// center of the space.
62    ///
63    /// TODO: This needs better-defined FOV/distance considerations before making it public
64    #[doc(hidden)]
65    pub fn looking_at_space(space_bounds: GridAab, direction: impl Into<FreeVector>) -> Self {
66        let direction = direction.into();
67        let mut spawn = Self::default_for_new_space(space_bounds);
68        spawn.set_eye_position(eye_for_look_at(space_bounds, direction));
69        spawn.set_look_direction(-direction);
70        spawn
71    }
72
73    /// Sets the position at which the character will appear, in terms of its viewpoint.
74    pub fn set_eye_position(&mut self, position: impl Into<FreePoint>) {
75        // TODO: accept None for clearing
76        // TODO: If we're going to suppress NaN, then it makes sense to suppress infinities too; come up with a general theory of how we want all-is-cubes to handle unreasonable positions.
77        self.eye_position = Some(position.into().map(notnan_or_zero));
78    }
79
80    /// Sets the bounds within which the character may be placed is allowed.
81    pub fn set_bounds(&mut self, bounds: GridAab) {
82        self.bounds = bounds;
83    }
84
85    /// Sets the direction the character should be facing, or looking at.
86    ///
87    /// The results are unspecified but harmless if the direction is zero or NaN.
88    pub fn set_look_direction(&mut self, direction: impl Into<FreeVector>) {
89        self.look_direction = direction.into().map(notnan_or_zero);
90    }
91
92    /// Sets the starting inventory items.
93    pub fn set_inventory(&mut self, inventory: Vec<Slot>) {
94        self.inventory = inventory;
95    }
96}
97
98fn notnan_or_zero(value: FreeCoordinate) -> NotNan<FreeCoordinate> {
99    NotNan::new(value).unwrap_or_else(|_| NotNan::from(0))
100}
101
102impl VisitHandles for Spawn {
103    fn visit_handles(&self, visitor: &mut dyn HandleVisitor) {
104        let Self {
105            inventory,
106            bounds: _,
107            eye_position: _,
108            look_direction: _,
109        } = self;
110        inventory.visit_handles(visitor);
111    }
112}
113
114#[cfg(feature = "save")]
115impl serde::Serialize for Spawn {
116    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
117    where
118        S: serde::Serializer,
119    {
120        let Spawn {
121            bounds,
122            eye_position,
123            look_direction,
124            ref inventory,
125        } = *self;
126
127        schema::SpawnSer::SpawnV1 {
128            bounds,
129            eye_position: eye_position.map(|p| p.into()),
130            look_direction: look_direction.into(),
131            inventory: inventory.iter().map(|slot| slot.into()).collect(),
132        }
133        .serialize(serializer)
134    }
135}
136
137#[cfg(feature = "save")]
138impl<'de> serde::Deserialize<'de> for Spawn {
139    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140    where
141        D: serde::Deserializer<'de>,
142    {
143        match schema::SpawnSer::deserialize(deserializer)? {
144            schema::SpawnSer::SpawnV1 {
145                bounds,
146                eye_position,
147                look_direction,
148                inventory,
149            } => Ok(Spawn {
150                bounds,
151                eye_position: eye_position.map(|p| p.into()),
152                look_direction: look_direction.into(),
153                inventory: inventory.into_iter().map(|slot| slot.into()).collect(),
154            }),
155        }
156    }
157}