1#![allow(clippy::unnecessary_cast)]
2
3use crate::prelude::*;
4use bevy::prelude::*;
5#[cfg(all(
6 feature = "default-collider",
7 any(feature = "parry-f32", feature = "parry-f64")
8))]
9use parry::shape::{SharedShape, TypedShape};
10
11pub trait PhysicsGizmoExt {
13 fn draw_line(&mut self, a: Vector, b: Vector, color: Color);
15
16 fn draw_line_strip(
18 &mut self,
19 points: Vec<Vector>,
20 position: impl Into<Position>,
21 rotation: impl Into<Rotation>,
22 closed: bool,
23 color: Color,
24 );
25
26 fn draw_polyline(
28 &mut self,
29 vertices: &[Vector],
30 indices: &[[u32; 2]],
31 position: impl Into<Position>,
32 rotation: impl Into<Rotation>,
33 color: Color,
34 );
35
36 fn draw_arrow(&mut self, a: Vector, b: Vector, head_length: Scalar, color: Color);
38
39 #[cfg(all(
41 feature = "default-collider",
42 any(feature = "parry-f32", feature = "parry-f64")
43 ))]
44 fn draw_collider(
45 &mut self,
46 collider: &Collider,
47 position: impl Into<Position>,
48 rotation: impl Into<Rotation>,
49 color: Color,
50 );
51
52 #[allow(clippy::too_many_arguments)]
54 fn draw_raycast(
55 &mut self,
56 origin: Vector,
57 direction: Dir,
58 max_distance: Scalar,
59 hits: &[RayHitData],
60 ray_color: Color,
61 point_color: Color,
62 normal_color: Color,
63 length_unit: Scalar,
64 );
65
66 #[allow(clippy::too_many_arguments)]
68 #[cfg(all(
69 feature = "default-collider",
70 any(feature = "parry-f32", feature = "parry-f64")
71 ))]
72 fn draw_shapecast(
73 &mut self,
74 shape: &Collider,
75 origin: Vector,
76 shape_rotation: impl Into<Rotation>,
77 direction: Dir,
78 max_distance: Scalar,
79 hits: &[ShapeHitData],
80 ray_color: Color,
81 shape_color: Color,
82 point_color: Color,
83 normal_color: Color,
84 length_unit: Scalar,
85 );
86}
87
88impl PhysicsGizmoExt for Gizmos<'_, '_, PhysicsGizmos> {
89 fn draw_line(&mut self, a: Vector, b: Vector, color: Color) {
91 #[cfg(feature = "2d")]
92 self.line_2d(a.f32(), b.f32(), color);
93 #[cfg(feature = "3d")]
94 self.line(a.f32(), b.f32(), color);
95 }
96
97 fn draw_line_strip(
99 &mut self,
100 points: Vec<Vector>,
101 position: impl Into<Position>,
102 rotation: impl Into<Rotation>,
103 closed: bool,
104 color: Color,
105 ) {
106 let position: Position = position.into();
107 let rotation: Rotation = rotation.into();
108
109 let pos = position.f32();
110 #[cfg(feature = "2d")]
111 self.linestrip_2d(points.iter().map(|p| pos + (rotation * p).f32()), color);
112 #[cfg(feature = "3d")]
113 self.linestrip(points.iter().map(|p| pos + (rotation * p).f32()), color);
114
115 if closed && points.len() > 2 {
116 let a = position.0 + rotation * points[0];
117 let b = position.0 + rotation * points.last().unwrap();
118 self.draw_line(a, b, color);
119 }
120 }
121
122 fn draw_polyline(
124 &mut self,
125 vertices: &[Vector],
126 indices: &[[u32; 2]],
127 position: impl Into<Position>,
128 rotation: impl Into<Rotation>,
129 color: Color,
130 ) {
131 let position: Position = position.into();
132 let rotation: Rotation = rotation.into();
133
134 for [i1, i2] in indices {
135 let a = position.0 + rotation * vertices[*i1 as usize];
136 let b = position.0 + rotation * vertices[*i2 as usize];
137 self.draw_line(a, b, color);
138 }
139 }
140
141 fn draw_arrow(&mut self, a: Vector, b: Vector, head_length: Scalar, color: Color) {
144 #[cfg(feature = "2d")]
145 {
146 self.arrow_2d(a.f32(), b.f32(), color)
147 .with_tip_length(head_length as f32);
148 }
149
150 #[cfg(feature = "3d")]
151 {
152 self.arrow(a.f32(), b.f32(), color)
153 .with_tip_length(head_length as f32);
154 }
155 }
156
157 #[cfg(all(
159 feature = "default-collider",
160 any(feature = "parry-f32", feature = "parry-f64")
161 ))]
162 fn draw_collider(
163 &mut self,
164 collider: &Collider,
165 position: impl Into<Position>,
166 rotation: impl Into<Rotation>,
167 color: Color,
168 ) {
169 let position: Position = position.into();
170 let rotation: Rotation = rotation.into();
171
172 let nalgebra_to_glam =
173 |points: &[_]| points.iter().map(|p| Vector::from(*p)).collect::<Vec<_>>();
174 match collider.shape_scaled().as_typed_shape() {
175 #[cfg(feature = "2d")]
176 TypedShape::Ball(s) => {
177 self.circle_2d(position.f32(), s.radius as f32, color);
178 }
179 #[cfg(feature = "3d")]
180 TypedShape::Ball(s) => {
181 self.sphere(
182 Isometry3d::new(position.f32(), rotation.f32()),
183 s.radius as f32,
184 color,
185 );
186 }
187 #[cfg(feature = "2d")]
188 TypedShape::Cuboid(s) => {
189 self.rect_2d(
190 Isometry2d::new(
191 position.f32(),
192 Rot2::from_sin_cos(rotation.sin as f32, rotation.cos as f32),
193 ),
194 2.0 * Vector::from(s.half_extents).f32(),
195 color,
196 );
197 }
198 #[cfg(feature = "3d")]
199 TypedShape::Cuboid(s) => {
200 use bevy_math::bounding::Aabb3d;
201
202 self.aabb_3d(
203 Aabb3d::new(Vec3A::ZERO, Vector::from(s.half_extents).f32()),
204 Transform::from_translation(position.f32()).with_rotation(rotation.f32()),
205 color,
206 );
207 }
208 #[cfg(feature = "2d")]
209 TypedShape::Capsule(s) => {
210 self.draw_line_strip(
211 nalgebra_to_glam(&s.to_polyline(32)),
212 position,
213 rotation,
214 true,
215 color,
216 );
217 }
218 #[cfg(feature = "3d")]
219 TypedShape::Capsule(s) => {
220 let (vertices, indices) = s.to_outline(32);
221 self.draw_polyline(
222 &nalgebra_to_glam(&vertices),
223 &indices,
224 position,
225 rotation,
226 color,
227 );
228 }
229 TypedShape::Segment(s) => self.draw_line_strip(
230 vec![s.a.into(), s.b.into()],
231 position,
232 rotation,
233 false,
234 color,
235 ),
236 TypedShape::Triangle(s) => self.draw_line_strip(
237 vec![s.a.into(), s.b.into(), s.c.into()],
238 position,
239 rotation,
240 true,
241 color,
242 ),
243 TypedShape::TriMesh(s) => {
244 for tri in s.triangles() {
245 self.draw_collider(
246 &Collider::from(SharedShape::new(tri)),
247 position,
248 rotation,
249 color,
250 );
251 }
252 }
253 TypedShape::Polyline(s) => self.draw_polyline(
254 &nalgebra_to_glam(s.vertices()),
255 s.indices(),
256 position,
257 rotation,
258 color,
259 ),
260 #[cfg(feature = "2d")]
261 TypedShape::HalfSpace(s) => {
262 let basis = Vector::new(-s.normal.y, s.normal.x);
263 let a = basis * 10_000.0;
264 let b = basis * -10_000.0;
265 self.draw_line_strip(vec![a, b], position, rotation, false, color);
266 }
267 #[cfg(feature = "3d")]
268 TypedShape::HalfSpace(s) => {
269 let n = s.normal;
270 let sign = n.z.signum();
271 let a = -1.0 / (sign + n.z);
272 let b = n.x * n.y * a;
273 let basis1 = Vector::new(1.0 + sign * n.x * n.x * a, sign * b, -sign * n.x);
274 let basis2 = Vector::new(b, sign + n.y * n.y * a, -n.y);
275 let a = basis1 * 10_000.0;
276 let b = basis1 * -10_000.0;
277 let c = basis2 * 10_000.0;
278 let d = basis2 * -10_000.0;
279 self.draw_polyline(&[a, b, c, d], &[[0, 1], [2, 3]], position, rotation, color);
280 }
281 TypedShape::Voxels(v) => {
282 #[cfg(feature = "2d")]
283 let (vertices, indices) = v.to_polyline();
284 #[cfg(feature = "3d")]
285 let (vertices, indices) = v.to_outline();
286 self.draw_polyline(
287 &nalgebra_to_glam(&vertices),
288 &indices,
289 position,
290 rotation,
291 color,
292 );
293 }
294 TypedShape::HeightField(s) => {
295 #[cfg(feature = "2d")]
296 for segment in s.segments() {
297 self.draw_collider(
298 &Collider::from(SharedShape::new(segment)),
299 position,
300 rotation,
301 color,
302 );
303 }
304 #[cfg(feature = "3d")]
305 for triangle in s.triangles() {
306 self.draw_collider(
307 &Collider::from(SharedShape::new(triangle)),
308 position,
309 rotation,
310 color,
311 );
312 }
313 }
314 TypedShape::Compound(s) => {
315 for (sub_pos, shape) in s.shapes() {
316 let pos = Position(position.0 + rotation * Vector::from(sub_pos.translation));
317 #[cfg(feature = "2d")]
318 let rot = rotation * Rotation::radians(sub_pos.rotation.angle());
319 #[cfg(feature = "3d")]
320 let rot = Rotation((rotation.mul_quat(sub_pos.rotation.into())).normalize());
321 self.draw_collider(&Collider::from(shape.to_owned()), pos, rot, color);
322 }
323 }
324 #[cfg(feature = "2d")]
325 TypedShape::ConvexPolygon(s) => {
326 self.draw_line_strip(
327 nalgebra_to_glam(s.points()),
328 position,
329 rotation,
330 true,
331 color,
332 );
333 }
334 #[cfg(feature = "3d")]
335 TypedShape::ConvexPolyhedron(s) => {
336 let indices = s
337 .edges()
338 .iter()
339 .map(|e| [e.vertices.x, e.vertices.y])
340 .collect::<Vec<_>>();
341 self.draw_polyline(
342 &nalgebra_to_glam(s.points()),
343 &indices,
344 position,
345 rotation,
346 color,
347 );
348 }
349 #[cfg(feature = "3d")]
350 TypedShape::Cylinder(s) => {
351 let (vertices, indices) = s.to_outline(32);
352 self.draw_polyline(
353 &nalgebra_to_glam(&vertices),
354 &indices,
355 position,
356 rotation,
357 color,
358 );
359 }
360 #[cfg(feature = "3d")]
361 TypedShape::Cone(s) => {
362 let (vertices, indices) = s.to_outline(32);
363 self.draw_polyline(
364 &nalgebra_to_glam(&vertices),
365 &indices,
366 position,
367 rotation,
368 color,
369 );
370 }
371 #[cfg(feature = "2d")]
375 TypedShape::RoundCuboid(s) => {
376 self.draw_line_strip(
377 nalgebra_to_glam(&s.to_polyline(32)),
378 position,
379 rotation,
380 true,
381 color,
382 );
383 }
384 #[cfg(feature = "3d")]
385 TypedShape::RoundCuboid(s) => {
386 let (vertices, indices) = s.to_outline(32);
387 self.draw_polyline(
388 &nalgebra_to_glam(&vertices),
389 &indices,
390 position,
391 rotation,
392 color,
393 );
394 }
395 TypedShape::RoundTriangle(s) => {
396 self.draw_collider(
399 &Collider::from(SharedShape::new(s.inner_shape)),
400 position,
401 rotation,
402 color,
403 );
404 }
405 #[cfg(feature = "2d")]
406 TypedShape::RoundConvexPolygon(s) => {
407 self.draw_line_strip(
408 nalgebra_to_glam(&s.to_polyline(32)),
409 position,
410 rotation,
411 true,
412 color,
413 );
414 }
415 #[cfg(feature = "3d")]
416 TypedShape::RoundConvexPolyhedron(s) => {
417 let (vertices, indices) = s.to_outline(32);
418 self.draw_polyline(
419 &nalgebra_to_glam(&vertices),
420 &indices,
421 position,
422 rotation,
423 color,
424 );
425 }
426 #[cfg(feature = "3d")]
427 TypedShape::RoundCylinder(s) => {
428 let (vertices, indices) = s.to_outline(32, 32);
429 self.draw_polyline(
430 &nalgebra_to_glam(&vertices),
431 &indices,
432 position,
433 rotation,
434 color,
435 );
436 }
437 #[cfg(feature = "3d")]
438 TypedShape::RoundCone(s) => {
439 let (vertices, indices) = s.to_outline(32, 32);
440 self.draw_polyline(
441 &nalgebra_to_glam(&vertices),
442 &indices,
443 position,
444 rotation,
445 color,
446 );
447 }
448 TypedShape::Custom(_id) => {
449 #[cfg(feature = "2d")]
450 {
451 use crate::collision::collider::{
452 EllipseColliderShape, RegularPolygonColliderShape,
453 };
454
455 if let Some(ellipse) =
456 collider.shape_scaled().as_shape::<EllipseColliderShape>()
457 {
458 let isometry = Isometry2d::new(
459 position.f32(),
460 Rot2::from_sin_cos(rotation.sin as f32, rotation.cos as f32),
461 );
462 self.primitive_2d(&ellipse.0, isometry, color);
463 } else if let Some(polygon) = collider
464 .shape_scaled()
465 .as_shape::<RegularPolygonColliderShape>()
466 {
467 let isometry = Isometry2d::new(
468 position.f32(),
469 Rot2::from_sin_cos(rotation.sin as f32, rotation.cos as f32),
470 );
471 self.primitive_2d(&polygon.0, isometry, color);
472 }
473 }
474 }
475 }
476 }
477
478 #[allow(clippy::too_many_arguments)]
480 fn draw_raycast(
481 &mut self,
482 origin: Vector,
483 direction: Dir,
484 max_distance: Scalar,
485 hits: &[RayHitData],
486 ray_color: Color,
487 point_color: Color,
488 normal_color: Color,
489 length_unit: Scalar,
490 ) {
491 let max_distance = hits
492 .iter()
493 .max_by(|a, b| a.distance.total_cmp(&b.distance))
494 .map_or(max_distance, |hit| hit.distance);
495
496 self.draw_arrow(
498 origin,
499 origin + direction.adjust_precision() * max_distance,
500 0.1 * length_unit,
501 ray_color,
502 );
503
504 for hit in hits {
506 let point = origin + direction.adjust_precision() * hit.distance;
507
508 #[cfg(feature = "2d")]
510 self.circle_2d(point.f32(), 0.1 * length_unit as f32, point_color);
511 #[cfg(feature = "3d")]
512 self.sphere(point.f32(), 0.1 * length_unit as f32, point_color);
513
514 self.draw_arrow(
516 point,
517 point + hit.normal * 0.5 * length_unit,
518 0.1 * length_unit,
519 normal_color,
520 );
521 }
522 }
523
524 #[cfg(all(
526 feature = "default-collider",
527 any(feature = "parry-f32", feature = "parry-f64")
528 ))]
529 #[allow(clippy::too_many_arguments)]
530 fn draw_shapecast(
531 &mut self,
532 shape: &Collider,
533 origin: Vector,
534 shape_rotation: impl Into<Rotation>,
535 direction: Dir,
536 max_distance: Scalar,
537 hits: &[ShapeHitData],
538 ray_color: Color,
539 shape_color: Color,
540 point_color: Color,
541 normal_color: Color,
542 length_unit: Scalar,
543 ) {
544 let shape_rotation = shape_rotation.into();
545 #[cfg(feature = "3d")]
546 let shape_rotation = Rotation(shape_rotation.normalize());
547
548 let max_distance = hits
549 .iter()
550 .max_by(|a, b| a.distance.total_cmp(&b.distance))
551 .map_or(max_distance, |hit| hit.distance);
552
553 self.draw_collider(shape, origin, shape_rotation, shape_color);
555
556 self.draw_arrow(
559 origin,
560 origin + max_distance * direction.adjust_precision(),
561 0.1 * length_unit,
562 ray_color,
563 );
564
565 for hit in hits {
567 #[cfg(feature = "2d")]
569 self.circle_2d(hit.point1.f32(), 0.1 * length_unit as f32, point_color);
570 #[cfg(feature = "3d")]
571 self.sphere(hit.point1.f32(), 0.1 * length_unit as f32, point_color);
572
573 self.draw_arrow(
575 hit.point1,
576 hit.point1 + hit.normal1 * 0.5 * length_unit,
577 0.1 * length_unit,
578 normal_color,
579 );
580
581 self.draw_collider(
583 shape,
584 origin + hit.distance * direction.adjust_precision(),
585 shape_rotation,
586 shape_color.with_alpha(0.3),
587 );
588 }
589 }
590}