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.cuboid(
190 Transform::from_scale(Vector::from(s.half_extents).extend(0.0).f32() * 2.0)
191 .with_translation(position.extend(0.0).f32())
192 .with_rotation(Quaternion::from(rotation).f32()),
193 color,
194 );
195 }
196 #[cfg(feature = "3d")]
197 TypedShape::Cuboid(s) => {
198 self.cuboid(
199 Transform::from_scale(Vector::from(s.half_extents).f32() * 2.0)
200 .with_translation(position.f32())
201 .with_rotation(rotation.f32()),
202 color,
203 );
204 }
205 #[cfg(feature = "2d")]
206 TypedShape::Capsule(s) => {
207 self.draw_line_strip(
208 nalgebra_to_glam(&s.to_polyline(32)),
209 position,
210 rotation,
211 true,
212 color,
213 );
214 }
215 #[cfg(feature = "3d")]
216 TypedShape::Capsule(s) => {
217 let (vertices, indices) = s.to_outline(32);
218 self.draw_polyline(
219 &nalgebra_to_glam(&vertices),
220 &indices,
221 position,
222 rotation,
223 color,
224 );
225 }
226 TypedShape::Segment(s) => self.draw_line_strip(
227 vec![s.a.into(), s.b.into()],
228 position,
229 rotation,
230 false,
231 color,
232 ),
233 TypedShape::Triangle(s) => self.draw_line_strip(
234 vec![s.a.into(), s.b.into(), s.c.into()],
235 position,
236 rotation,
237 true,
238 color,
239 ),
240 TypedShape::TriMesh(s) => {
241 for tri in s.triangles() {
242 self.draw_collider(
243 &Collider::from(SharedShape::new(tri)),
244 position,
245 rotation,
246 color,
247 );
248 }
249 }
250 TypedShape::Polyline(s) => self.draw_polyline(
251 &nalgebra_to_glam(s.vertices()),
252 s.indices(),
253 position,
254 rotation,
255 color,
256 ),
257 #[cfg(feature = "2d")]
258 TypedShape::HalfSpace(s) => {
259 let basis = Vector::new(-s.normal.y, s.normal.x);
260 let a = basis * 10_000.0;
261 let b = basis * -10_000.0;
262 self.draw_line_strip(vec![a, b], position, rotation, false, color);
263 }
264 #[cfg(feature = "3d")]
265 TypedShape::HalfSpace(s) => {
266 let n = s.normal;
267 let sign = n.z.signum();
268 let a = -1.0 / (sign + n.z);
269 let b = n.x * n.y * a;
270 let basis1 = Vector::new(1.0 + sign * n.x * n.x * a, sign * b, -sign * n.x);
271 let basis2 = Vector::new(b, sign + n.y * n.y * a, -n.y);
272 let a = basis1 * 10_000.0;
273 let b = basis1 * -10_000.0;
274 let c = basis2 * 10_000.0;
275 let d = basis2 * -10_000.0;
276 self.draw_polyline(&[a, b, c, d], &[[0, 1], [2, 3]], position, rotation, color);
277 }
278 TypedShape::Voxels(v) => {
279 #[cfg(feature = "2d")]
280 let (vertices, indices) = v.to_polyline();
281 #[cfg(feature = "3d")]
282 let (vertices, indices) = v.to_outline();
283 self.draw_polyline(
284 &nalgebra_to_glam(&vertices),
285 &indices,
286 position,
287 rotation,
288 color,
289 );
290 }
291 TypedShape::HeightField(s) => {
292 #[cfg(feature = "2d")]
293 for segment in s.segments() {
294 self.draw_collider(
295 &Collider::from(SharedShape::new(segment)),
296 position,
297 rotation,
298 color,
299 );
300 }
301 #[cfg(feature = "3d")]
302 for triangle in s.triangles() {
303 self.draw_collider(
304 &Collider::from(SharedShape::new(triangle)),
305 position,
306 rotation,
307 color,
308 );
309 }
310 }
311 TypedShape::Compound(s) => {
312 for (sub_pos, shape) in s.shapes() {
313 let pos = Position(position.0 + rotation * Vector::from(sub_pos.translation));
314 #[cfg(feature = "2d")]
315 let rot = rotation * Rotation::radians(sub_pos.rotation.angle());
316 #[cfg(feature = "3d")]
317 let rot = Rotation((rotation.mul_quat(sub_pos.rotation.into())).normalize());
318 self.draw_collider(&Collider::from(shape.to_owned()), pos, rot, color);
319 }
320 }
321 #[cfg(feature = "2d")]
322 TypedShape::ConvexPolygon(s) => {
323 self.draw_line_strip(
324 nalgebra_to_glam(s.points()),
325 position,
326 rotation,
327 true,
328 color,
329 );
330 }
331 #[cfg(feature = "3d")]
332 TypedShape::ConvexPolyhedron(s) => {
333 let indices = s
334 .edges()
335 .iter()
336 .map(|e| [e.vertices.x, e.vertices.y])
337 .collect::<Vec<_>>();
338 self.draw_polyline(
339 &nalgebra_to_glam(s.points()),
340 &indices,
341 position,
342 rotation,
343 color,
344 );
345 }
346 #[cfg(feature = "3d")]
347 TypedShape::Cylinder(s) => {
348 let (vertices, indices) = s.to_outline(32);
349 self.draw_polyline(
350 &nalgebra_to_glam(&vertices),
351 &indices,
352 position,
353 rotation,
354 color,
355 );
356 }
357 #[cfg(feature = "3d")]
358 TypedShape::Cone(s) => {
359 let (vertices, indices) = s.to_outline(32);
360 self.draw_polyline(
361 &nalgebra_to_glam(&vertices),
362 &indices,
363 position,
364 rotation,
365 color,
366 );
367 }
368 #[cfg(feature = "2d")]
372 TypedShape::RoundCuboid(s) => {
373 self.draw_line_strip(
374 nalgebra_to_glam(&s.to_polyline(32)),
375 position,
376 rotation,
377 true,
378 color,
379 );
380 }
381 #[cfg(feature = "3d")]
382 TypedShape::RoundCuboid(s) => {
383 let (vertices, indices) = s.to_outline(32);
384 self.draw_polyline(
385 &nalgebra_to_glam(&vertices),
386 &indices,
387 position,
388 rotation,
389 color,
390 );
391 }
392 TypedShape::RoundTriangle(s) => {
393 self.draw_collider(
396 &Collider::from(SharedShape::new(s.inner_shape)),
397 position,
398 rotation,
399 color,
400 );
401 }
402 #[cfg(feature = "2d")]
403 TypedShape::RoundConvexPolygon(s) => {
404 self.draw_line_strip(
405 nalgebra_to_glam(&s.to_polyline(32)),
406 position,
407 rotation,
408 true,
409 color,
410 );
411 }
412 #[cfg(feature = "3d")]
413 TypedShape::RoundConvexPolyhedron(s) => {
414 let (vertices, indices) = s.to_outline(32);
415 self.draw_polyline(
416 &nalgebra_to_glam(&vertices),
417 &indices,
418 position,
419 rotation,
420 color,
421 );
422 }
423 #[cfg(feature = "3d")]
424 TypedShape::RoundCylinder(s) => {
425 let (vertices, indices) = s.to_outline(32, 32);
426 self.draw_polyline(
427 &nalgebra_to_glam(&vertices),
428 &indices,
429 position,
430 rotation,
431 color,
432 );
433 }
434 #[cfg(feature = "3d")]
435 TypedShape::RoundCone(s) => {
436 let (vertices, indices) = s.to_outline(32, 32);
437 self.draw_polyline(
438 &nalgebra_to_glam(&vertices),
439 &indices,
440 position,
441 rotation,
442 color,
443 );
444 }
445 TypedShape::Custom(_id) => {
446 #[cfg(feature = "2d")]
447 {
448 use crate::collision::collider::{
449 EllipseColliderShape, RegularPolygonColliderShape,
450 };
451
452 if let Some(ellipse) =
453 collider.shape_scaled().as_shape::<EllipseColliderShape>()
454 {
455 let isometry = Isometry2d::new(
456 position.f32(),
457 Rot2::from_sin_cos(rotation.sin as f32, rotation.cos as f32),
458 );
459 self.primitive_2d(&ellipse.0, isometry, color);
460 } else if let Some(polygon) = collider
461 .shape_scaled()
462 .as_shape::<RegularPolygonColliderShape>()
463 {
464 let isometry = Isometry2d::new(
465 position.f32(),
466 Rot2::from_sin_cos(rotation.sin as f32, rotation.cos as f32),
467 );
468 self.primitive_2d(&polygon.0, isometry, color);
469 }
470 }
471 }
472 }
473 }
474
475 #[allow(clippy::too_many_arguments)]
477 fn draw_raycast(
478 &mut self,
479 origin: Vector,
480 direction: Dir,
481 max_distance: Scalar,
482 hits: &[RayHitData],
483 ray_color: Color,
484 point_color: Color,
485 normal_color: Color,
486 length_unit: Scalar,
487 ) {
488 let max_distance = hits
489 .iter()
490 .max_by(|a, b| a.distance.total_cmp(&b.distance))
491 .map_or(max_distance, |hit| hit.distance);
492
493 self.draw_arrow(
495 origin,
496 origin + direction.adjust_precision() * max_distance,
497 0.1 * length_unit,
498 ray_color,
499 );
500
501 for hit in hits {
503 let point = origin + direction.adjust_precision() * hit.distance;
504
505 #[cfg(feature = "2d")]
507 self.circle_2d(point.f32(), 0.1 * length_unit as f32, point_color);
508 #[cfg(feature = "3d")]
509 self.sphere(point.f32(), 0.1 * length_unit as f32, point_color);
510
511 self.draw_arrow(
513 point,
514 point + hit.normal * 0.5 * length_unit,
515 0.1 * length_unit,
516 normal_color,
517 );
518 }
519 }
520
521 #[cfg(all(
523 feature = "default-collider",
524 any(feature = "parry-f32", feature = "parry-f64")
525 ))]
526 #[allow(clippy::too_many_arguments)]
527 fn draw_shapecast(
528 &mut self,
529 shape: &Collider,
530 origin: Vector,
531 shape_rotation: impl Into<Rotation>,
532 direction: Dir,
533 max_distance: Scalar,
534 hits: &[ShapeHitData],
535 ray_color: Color,
536 shape_color: Color,
537 point_color: Color,
538 normal_color: Color,
539 length_unit: Scalar,
540 ) {
541 let shape_rotation = shape_rotation.into();
542 #[cfg(feature = "3d")]
543 let shape_rotation = Rotation(shape_rotation.normalize());
544
545 let max_distance = hits
546 .iter()
547 .max_by(|a, b| a.distance.total_cmp(&b.distance))
548 .map_or(max_distance, |hit| hit.distance);
549
550 self.draw_collider(shape, origin, shape_rotation, shape_color);
552
553 self.draw_arrow(
556 origin,
557 origin + max_distance * direction.adjust_precision(),
558 0.1 * length_unit,
559 ray_color,
560 );
561
562 for hit in hits {
564 #[cfg(feature = "2d")]
566 self.circle_2d(hit.point1.f32(), 0.1 * length_unit as f32, point_color);
567 #[cfg(feature = "3d")]
568 self.sphere(hit.point1.f32(), 0.1 * length_unit as f32, point_color);
569
570 self.draw_arrow(
572 hit.point1,
573 hit.point1 + hit.normal1 * 0.5 * length_unit,
574 0.1 * length_unit,
575 normal_color,
576 );
577
578 self.draw_collider(
580 shape,
581 origin + hit.distance * direction.adjust_precision(),
582 shape_rotation,
583 shape_color.with_alpha(0.3),
584 );
585 }
586 }
587}