1use serde::Serialize;
6use serde_json::Value;
7
8use crate::{Component, Entity, JsonError, Transform, World};
9
10#[derive(Clone, Debug, PartialEq, Serialize)]
15pub struct Marker {
16 pub kind: String,
19 pub ground: Option<[f32; 2]>,
22 #[serde(skip_serializing_if = "Option::is_none")]
25 pub yaw: Option<f32>,
26}
27
28#[derive(Clone, Debug, PartialEq, Serialize)]
34pub struct Curve {
35 pub kind: String,
37 pub points: Vec<[f32; 3]>,
38 pub closed: bool,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub tangents: Option<Vec<[[f32; 3]; 2]>>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub up: Option<Vec<[f32; 3]>>,
45}
46
47pub fn kind_is_valid(kind: &str) -> bool {
50 !kind.is_empty()
51 && kind
52 .bytes()
53 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
54}
55
56fn check_kind(kind: &str) -> Result<(), String> {
57 if kind_is_valid(kind) {
58 Ok(())
59 } else {
60 Err(format!("kind {kind:?} is not a word of a-z, 0-9 and _"))
61 }
62}
63
64impl Component for Marker {
65 const NAME: &'static str = "core.marker";
66 const GEOMETRY: crate::Geometry = crate::Geometry::None;
67 const VERSION: u32 = 2;
69 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
71 crate::world::Linearity::Invariant
72 }
73 fn to_json(&self) -> Result<Option<Value>, JsonError> {
74 crate::json::to_value(self).map(Some)
75 }
76 fn verify(&self, e: Entity, w: &World) -> Result<(), String> {
78 check_kind(&self.kind)?;
79 match (self.ground, w.has::<Transform>(e)) {
80 (Some(_), true) => Err("both a ground position and a transform".into()),
81 (None, false) => Err("neither a ground position nor a transform".into()),
82 (Some(g), false) if !g.iter().all(|v| v.is_finite()) => {
83 Err(format!("ground position {g:?} is not finite"))
84 }
85 _ if self.yaw.is_some() && self.ground.is_none() => {
86 Err("a yaw without a ground position (a transform gives the facing)".into())
87 }
88 _ if self.yaw.is_some_and(|y| !y.is_finite()) => Err("a yaw that is not finite".into()),
89 _ => Ok(()),
90 }
91 }
92}
93
94impl Component for Curve {
95 const NAME: &'static str = "core.curve";
96 const GEOMETRY: crate::Geometry = crate::Geometry::None;
97 const VERSION: u32 = 2;
99 fn apply_linear(&self, _l: &crate::Linear) -> crate::world::Linearity<Self> {
101 crate::world::Linearity::Invariant
102 }
103 fn to_json(&self) -> Result<Option<Value>, JsonError> {
104 crate::json::to_value(self).map(Some)
105 }
106 fn verify(&self, _e: Entity, _w: &World) -> Result<(), String> {
110 check_kind(&self.kind)?;
111 let n = self.points.len();
112 if n < 2 {
113 return Err(format!("{n} points; a curve has at least 2"));
114 }
115 if self.closed && self.tangents.is_none() && n < 3 {
116 return Err(format!("{n} points; a closed polyline has at least 3"));
117 }
118 if let Some(i) = self.points.iter().position(|p| !p.iter().all(|v| v.is_finite())) {
119 return Err(format!("point {i} {:?} is not finite", self.points[i]));
120 }
121 if let Some(t) = &self.tangents {
122 if t.len() != n {
123 return Err(format!("{} tangent pairs for {n} points", t.len()));
124 }
125 if let Some(i) = t.iter().position(|h| !h.iter().flatten().all(|v| v.is_finite())) {
126 return Err(format!("point {i}'s handles {:?} are not finite", t[i]));
127 }
128 }
129 if let Some(up) = &self.up {
130 if up.len() != n {
131 return Err(format!("{} up directions for {n} points", up.len()));
132 }
133 let unit = |u: &[f32; 3]| {
134 let l = u.iter().map(|v| v * v).sum::<f32>().sqrt();
135 l.is_finite() && (l - 1.0).abs() <= 1e-3
136 };
137 if let Some(i) = up.iter().position(|u| !unit(u)) {
138 return Err(format!("point {i}'s up {:?} is not a unit direction", up[i]));
139 }
140 }
141 Ok(())
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn kinds_are_lower_case_words() {
151 for good in ["spawn", "trigger_effect", "rail2"] {
152 assert!(kind_is_valid(good), "{good}");
153 }
154 for bad in ["", "Spawn", "fast travel", "a:b", "é"] {
155 assert!(!kind_is_valid(bad), "{bad}");
156 }
157 }
158
159 #[test]
160 fn a_marker_is_placed_by_a_transform_or_a_ground_position() {
161 let mut w = World::new();
162 let placed = w.spawn();
163 w.insert(placed, Transform::IDENTITY);
164 w.insert(
165 placed,
166 Marker {
167 kind: "spawn".into(),
168 ground: None,
169 yaw: None,
170 },
171 );
172 let snapped = w.spawn();
173 w.insert(
174 snapped,
175 Marker {
176 kind: "travel".into(),
177 ground: Some([12., 40.]),
178 yaw: None,
179 },
180 );
181 assert!(w.verify_components().is_empty());
182 let both = w.spawn();
183 w.insert(both, Transform::IDENTITY);
184 w.insert(
185 both,
186 Marker {
187 kind: "travel".into(),
188 ground: Some([0., 0.]),
189 yaw: None,
190 },
191 );
192 let neither = w.spawn();
193 w.insert(
194 neither,
195 Marker {
196 kind: "travel".into(),
197 ground: None,
198 yaw: None,
199 },
200 );
201 let nan = w.spawn();
202 w.insert(
203 nan,
204 Marker {
205 kind: "travel".into(),
206 ground: Some([f32::NAN, 0.]),
207 yaw: None,
208 },
209 );
210 let bad_kind = w.spawn();
211 w.insert(
212 bad_kind,
213 Marker {
214 kind: "Travel".into(),
215 ground: Some([0., 0.]),
216 yaw: None,
217 },
218 );
219 let found: Vec<(Entity, String)> = w
220 .verify_components()
221 .into_iter()
222 .map(|(_, e, m)| (e, m))
223 .collect();
224 assert_eq!(found.len(), 4, "{found:?}");
225 assert_eq!(found[0], (both, "both a ground position and a transform".into()));
226 assert_eq!(
227 found[1],
228 (neither, "neither a ground position nor a transform".into())
229 );
230 assert!(found[2].1.contains("not finite"), "{found:?}");
231 assert!(found[3].1.contains("is not a word"), "{found:?}");
232 }
233
234 #[test]
235 fn a_curve_has_two_finite_points() {
236 let mut w = World::new();
237 let curve = |points: Vec<[f32; 3]>| Curve {
238 kind: "rail".into(),
239 points,
240 closed: false,
241 tangents: None,
242 up: None,
243 };
244 let good = w.spawn();
245 w.insert(good, curve(vec![[0.; 3], [1., 0., 0.]]));
246 let short = w.spawn();
247 w.insert(short, curve(vec![[0.; 3]]));
248 let nan = w.spawn();
249 w.insert(nan, curve(vec![[0.; 3], [f32::INFINITY, 0., 0.]]));
250 let closed_two = w.spawn();
252 w.insert(
253 closed_two,
254 Curve {
255 closed: true,
256 ..curve(vec![[0.; 3], [1., 0., 0.]])
257 },
258 );
259 let closed_three = w.spawn();
260 w.insert(
261 closed_three,
262 Curve {
263 closed: true,
264 ..curve(vec![[0.; 3], [1., 0., 0.], [0., 0., 1.]])
265 },
266 );
267 let bezier_two = w.spawn();
268 w.insert(
269 bezier_two,
270 Curve {
271 closed: true,
272 tangents: Some(vec![[[0., 0., -1.], [0., 0., 1.]], [[1., 0., 1.], [1., 0., -1.]]]),
273 ..curve(vec![[0.; 3], [1., 0., 0.]])
274 },
275 );
276 let found: Vec<Entity> = w.verify_components().into_iter().map(|(_, e, _)| e).collect();
277 assert_eq!(found, [short, nan, closed_two]);
278 assert_eq!(
279 w.get::<Curve>(good).unwrap().to_json().unwrap(),
280 Some(
281 serde_json::json!({"kind": "rail", "points": [[0., 0., 0.], [1., 0., 0.]], "closed": false})
282 )
283 );
284 }
285}