1use crate::{
28 asset::{Resource, ResourceData},
29 core::{
30 algebra::{Matrix4, Point2, Point3, Vector2},
31 reflect::prelude::*,
32 type_traits::prelude::*,
33 visitor::prelude::*,
34 },
35};
36use std::{
37 error::Error,
38 fmt::{Debug, Display, Formatter},
39 num::{ParseFloatError, ParseIntError},
40 path::Path,
41 str::FromStr,
42};
43use strum_macros::{AsRefStr, EnumString, VariantNames};
44
45use super::*;
46
47#[derive(
49 Clone,
50 Hash,
51 PartialEq,
52 Eq,
53 Default,
54 Visit,
55 Reflect,
56 AsRefStr,
57 EnumString,
58 VariantNames,
59 TypeUuidProvider,
60)]
61#[type_uuid(id = "04a44fec-394f-4497-97d5-fe9e6f915831")]
62pub enum TileCollider {
63 #[default]
65 None,
66 Rectangle,
68 Custom(CustomTileColliderResource),
70 Mesh,
72}
73
74impl Default for &TileCollider {
75 fn default() -> Self {
76 &TileCollider::None
77 }
78}
79
80impl Debug for TileCollider {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 match self {
83 Self::None => write!(f, "None"),
84 Self::Rectangle => write!(f, "Rectangle"),
85 Self::Custom(r) => {
86 if r.is_ok() {
87 write!(f, "Custom({})", r.data_ref().deref())
88 } else {
89 f.write_str("Custom(unloaded)")
90 }
91 }
92 Self::Mesh => write!(f, "Mesh"),
93 }
94 }
95}
96
97impl OrthoTransform for TileCollider {
98 fn x_flipped(self) -> Self {
99 if let Self::Custom(collider) = self {
100 let collider = collider.data_ref().clone();
101 Self::Custom(Resource::new_ok(
102 Uuid::new_v4(),
103 ResourceKind::Embedded,
104 collider.x_flipped(),
105 ))
106 } else {
107 self
108 }
109 }
110 fn rotated(self, amount: i8) -> Self {
111 if let Self::Custom(collider) = self {
112 let collider = collider.data_ref().clone();
113 Self::Custom(Resource::new_ok(
114 Uuid::new_v4(),
115 ResourceKind::Embedded,
116 collider.rotated(amount),
117 ))
118 } else {
119 self
120 }
121 }
122}
123
124impl TileCollider {
125 pub fn is_none(&self) -> bool {
127 matches!(self, TileCollider::None)
128 }
129 pub fn is_rectangle(&self) -> bool {
131 matches!(self, TileCollider::Rectangle)
132 }
133 pub fn is_custom(&self) -> bool {
135 matches!(self, TileCollider::Custom(_))
136 }
137
138 pub fn build_collider_shape(
140 &self,
141 transform: &Matrix4<f32>,
142 position: Vector3<f32>,
143 vertices: &mut Vec<Point2<f32>>,
144 triangles: &mut Vec<[u32; 3]>,
145 ) {
146 match self {
147 TileCollider::None => (),
148 TileCollider::Rectangle => {
149 let origin = vertices.len() as u32;
150 for (dx, dy) in [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)] {
151 let offset = Vector3::new(dx, dy, 0.0);
152 let point = Point3::from(position + offset);
153 vertices.push(transform.transform_point(&point).xy());
154 }
155
156 triangles.push([origin, origin + 1, origin + 2]);
157 triangles.push([origin, origin + 2, origin + 3]);
158 }
159 TileCollider::Custom(resource) => {
160 if resource.is_ok() {
161 resource
162 .data_ref()
163 .build_collider_shape(transform, position, vertices, triangles);
164 }
165 }
166 TileCollider::Mesh => (), }
168 }
169}
170
171pub type CustomTileColliderResource = Resource<CustomTileCollider>;
173#[derive(Clone, PartialEq, Debug, Default, Visit, Reflect, TypeUuidProvider)]
182#[type_uuid(id = "118da556-a444-4bd9-bd88-12d78d26107f")]
183pub struct CustomTileCollider {
184 pub vertices: Vec<Vector2<f32>>,
186 pub triangles: Vec<TriangleDefinition>,
188}
189
190impl ResourceData for CustomTileCollider {
191 fn type_uuid(&self) -> Uuid {
192 <Self as TypeUuidProvider>::type_uuid()
193 }
194
195 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
196 let mut visitor = Visitor::new();
197 self.visit("CustomTileCollider", &mut visitor)?;
198 visitor.save_ascii_to_file(path)?;
199 Ok(())
200 }
201
202 fn can_be_saved(&self) -> bool {
203 false
204 }
205
206 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
207 Some(Box::new(self.clone()))
208 }
209}
210
211impl OrthoTransform for CustomTileCollider {
212 fn x_flipped(self) -> Self {
213 Self {
214 vertices: self
215 .vertices
216 .iter()
217 .map(|v| Vector2::new(1.0 - v.x, v.y))
218 .collect(),
219 ..self
220 }
221 }
222
223 fn rotated(self, amount: i8) -> Self {
224 let center = Vector2::new(0.5, 0.5);
225 Self {
226 vertices: self
227 .vertices
228 .iter()
229 .map(|v| (v - center).rotated(amount) + center)
230 .collect(),
231 ..self
232 }
233 }
234}
235
236impl CustomTileCollider {
237 pub fn build_collider_shape(
243 &self,
244 transform: &Matrix4<f32>,
245 position: Vector3<f32>,
246 vertices: &mut Vec<Point2<f32>>,
247 triangles: &mut Vec<[u32; 3]>,
248 ) {
249 let origin = vertices.len() as u32;
250 triangles.extend(self.triangles.iter().map(|d| d.0.map(|i| i + origin)));
251 vertices.extend(self.vertices.iter().map(|p| {
252 transform
253 .transform_point(&Point3::from(position + p.to_homogeneous()))
254 .xy()
255 }));
256 }
257}
258
259impl Display for CustomTileCollider {
260 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
261 let mut first = true;
262 for v in self.vertices.iter() {
263 if !first {
264 write!(f, " ")?;
265 }
266 first = false;
267 write!(f, "({}, {})", v.x, v.y)?;
268 }
269 for TriangleDefinition(t) in self.triangles.iter() {
270 if !first {
271 write!(f, " ")?;
272 }
273 first = false;
274 write!(f, "[{}, {}, {}]", t[0], t[1], t[2])?;
275 }
276 Ok(())
277 }
278}
279
280#[derive(Debug)]
282pub enum CustomTileColliderStrError {
283 GroupTooShort,
285 GroupTooLong(usize),
287 MissingNumber,
289 IndexOutOfBounds(u32),
292 IndexParseError(ParseIntError),
294 CoordinateParseError(ParseFloatError),
296}
297
298impl From<ParseIntError> for CustomTileColliderStrError {
299 fn from(value: ParseIntError) -> Self {
300 Self::IndexParseError(value)
301 }
302}
303
304impl From<ParseFloatError> for CustomTileColliderStrError {
305 fn from(value: ParseFloatError) -> Self {
306 Self::CoordinateParseError(value)
307 }
308}
309
310impl Error for CustomTileColliderStrError {}
311
312impl Display for CustomTileColliderStrError {
313 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
314 match self {
315 CustomTileColliderStrError::GroupTooShort => {
316 write!(f, "Each group must have at least 2 numbers.")
317 }
318 CustomTileColliderStrError::GroupTooLong(n) => {
319 write!(f, "A group has {n} numbers. No group may be longer than 3.")
320 }
321 CustomTileColliderStrError::IndexOutOfBounds(n) => {
322 write!(
323 f,
324 "Triangle index {n} does not match any of the given vertices."
325 )
326 }
327 CustomTileColliderStrError::MissingNumber => {
328 write!(f, "Numbers in a group must be separated by commas.")
329 }
330 CustomTileColliderStrError::IndexParseError(parse_int_error) => {
331 write!(f, "Index parse failure: {parse_int_error}")
332 }
333 CustomTileColliderStrError::CoordinateParseError(parse_float_error) => {
334 write!(f, "Coordinate parse failure: {parse_float_error}")
335 }
336 }
337 }
338}
339
340impl FromStr for CustomTileCollider {
341 type Err = CustomTileColliderStrError;
342
343 fn from_str(s: &str) -> Result<Self, Self::Err> {
344 let mut group = Vec::<&str>::default();
345 let mut ready = true;
346 let mut vertices = Vec::<Vector2<f32>>::default();
347 let mut triangles = Vec::<TriangleDefinition>::default();
348 for token in TokenIter::new(s) {
349 if ready {
350 if token != "," {
351 group.push(token);
352 ready = false;
353 } else {
354 return Err(CustomTileColliderStrError::MissingNumber);
355 }
356 } else if token != "," {
357 process_group(&group, &mut vertices, &mut triangles)?;
358 group.clear();
359 group.push(token);
360 } else {
361 ready = true;
362 }
363 }
364 if !group.is_empty() {
365 process_group(&group, &mut vertices, &mut triangles)?;
366 }
367 let len = vertices.len() as u32;
368 for TriangleDefinition(tri) in triangles.iter() {
369 for &n in tri.iter() {
370 if n >= len {
371 return Err(CustomTileColliderStrError::IndexOutOfBounds(n));
372 }
373 }
374 }
375 Ok(Self {
376 vertices,
377 triangles,
378 })
379 }
380}
381
382fn process_group(
383 group: &[&str],
384 vertices: &mut Vec<Vector2<f32>>,
385 triangles: &mut Vec<TriangleDefinition>,
386) -> Result<(), CustomTileColliderStrError> {
387 use CustomTileColliderStrError as Error;
388 let len = group.len();
389 if len < 2 {
390 return Err(Error::GroupTooShort);
391 } else if len > 3 {
392 return Err(Error::GroupTooLong(group.len()));
393 } else if len == 2 {
394 let v = Vector2::new(parse_f32(group[0])?, parse_f32(group[1])?);
395 vertices.push(v);
396 } else if len == 3 {
397 let t = TriangleDefinition([
398 u32::from_str(group[0])?,
399 u32::from_str(group[1])?,
400 u32::from_str(group[2])?,
401 ]);
402 triangles.push(t);
403 }
404 Ok(())
405}
406
407fn parse_f32(source: &str) -> Result<f32, ParseFloatError> {
408 let value = f32::from_str(source)?;
409 f32::from_str(&format!("{value:.3}"))
410}
411
412struct TokenIter<'a> {
413 source: &'a str,
414 position: usize,
415}
416
417impl<'a> TokenIter<'a> {
418 fn new(source: &'a str) -> Self {
419 Self {
420 source,
421 position: 0,
422 }
423 }
424}
425
426fn is_number_char(c: char) -> bool {
427 c.is_numeric() || c == '.' || c == '-'
428}
429
430fn is_ignore_char(c: char) -> bool {
431 !is_number_char(c) && c != ','
432}
433
434impl<'a> Iterator for TokenIter<'a> {
435 type Item = &'a str;
436
437 fn next(&mut self) -> Option<Self::Item> {
438 let rest = self.source.get(self.position..)?;
439 if rest.is_empty() {
440 return None;
441 }
442 let mut initial_ignore = true;
443 let mut start = 0;
444 for (i, c) in rest.char_indices() {
445 if initial_ignore {
446 if is_ignore_char(c) {
447 continue;
448 } else {
449 initial_ignore = false;
450 start = i;
451 }
452 }
453 if c == ',' {
454 if i == start {
455 self.position += i + 1;
456 return Some(&rest[start..i + 1]);
457 } else {
458 self.position += i;
459 return Some(&rest[start..i]);
460 }
461 } else if is_ignore_char(c) {
462 self.position += i + 1;
463 return Some(&rest[start..i]);
464 }
465 }
466 if initial_ignore {
467 return None;
468 }
469 self.position = self.source.len();
470 Some(&rest[start..])
471 }
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn empty() {
480 let mut iter = TokenIter::new("");
481 assert_eq!(iter.next(), None);
482 }
483 #[test]
484 fn empty2() {
485 let mut iter = TokenIter::new(" ");
486 assert_eq!(iter.next(), None);
487 }
488
489 #[test]
490 fn comma() {
491 let mut iter = TokenIter::new("0,1");
492 assert_eq!(iter.next().unwrap(), "0");
493 assert_eq!(iter.next().unwrap(), ",");
494 assert_eq!(iter.next().unwrap(), "1");
495 assert_eq!(iter.next(), None);
496 }
497 #[test]
498 fn comma2() {
499 let mut iter = TokenIter::new(" 0.4 , -1 ");
500 assert_eq!(iter.next().unwrap(), "0.4");
501 assert_eq!(iter.next().unwrap(), ",");
502 assert_eq!(iter.next().unwrap(), "-1");
503 assert_eq!(iter.next(), None);
504 }
505 #[test]
506 fn comma3() {
507 let mut iter = TokenIter::new(",, ,");
508 assert_eq!(iter.next().unwrap(), ",");
509 assert_eq!(iter.next().unwrap(), ",");
510 assert_eq!(iter.next().unwrap(), ",");
511 assert_eq!(iter.next(), None);
512 }
513 #[test]
514 fn number() {
515 let mut iter = TokenIter::new("0");
516 assert_eq!(iter.next().unwrap(), "0");
517 assert_eq!(iter.next(), None);
518 }
519 #[test]
520 fn number2() {
521 let mut iter = TokenIter::new("-3.14");
522 assert_eq!(iter.next().unwrap(), "-3.14");
523 assert_eq!(iter.next(), None);
524 }
525 #[test]
526 fn number3() {
527 let mut iter = TokenIter::new(" -3.14 ");
528 assert_eq!(iter.next().unwrap(), "-3.14");
529 assert_eq!(iter.next(), None);
530 }
531 #[test]
532 fn collider() {
533 let col = CustomTileCollider::from_str("0,0; 1,1; 1,0; 0,1,2").unwrap();
534 assert_eq!(col.vertices.len(), 3);
535 assert_eq!(col.vertices[0], Vector2::new(0.0, 0.0));
536 assert_eq!(col.vertices[1], Vector2::new(1.0, 1.0));
537 assert_eq!(col.vertices[2], Vector2::new(1.0, 0.0));
538 assert_eq!(col.triangles.len(), 1);
539 assert_eq!(col.triangles[0], TriangleDefinition([0, 1, 2]));
540 }
541 #[test]
542 fn collider_display() {
543 let col = CustomTileCollider::from_str("0,0; 1,1; 1,0.333; 0,1,2").unwrap();
544 assert_eq!(col.to_string(), "(0, 0) (1, 1) (1, 0.333) [0, 1, 2]");
545 }
546}