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