|
| 1 | +//! Wireframe gizmos for `CollisionShapeData` so colliders are visible in the editor viewport. |
| 2 | +//! |
| 3 | +//! Drawn every frame for every entity with a `CollisionShapeData` + `GlobalTransform`. |
| 4 | +//! Uses the same `OverlayGizmoGroup` config as the other line-based gizmos so it |
| 5 | +//! respects depth bias and render layer 1. |
| 6 | +
|
| 7 | +use bevy::prelude::*; |
| 8 | +use bevy::camera::primitives::Aabb; |
| 9 | + |
| 10 | +use renzora_editor_framework::EditorSelection; |
| 11 | +use renzora_physics::{CollisionShapeData, CollisionShapeType}; |
| 12 | + |
| 13 | +use crate::OverlayGizmoGroup; |
| 14 | + |
| 15 | +const COLOR_STATIC: Color = Color::srgb(0.30, 0.85, 0.40); |
| 16 | +const COLOR_DYNAMIC: Color = Color::srgb(1.0, 0.55, 0.15); |
| 17 | +const COLOR_SENSOR: Color = Color::srgb(0.30, 0.70, 1.0); |
| 18 | + |
| 19 | +pub fn draw_collider_gizmos( |
| 20 | + mut gizmos: Gizmos<OverlayGizmoGroup>, |
| 21 | + selection: Res<EditorSelection>, |
| 22 | + query: Query<( |
| 23 | + Entity, |
| 24 | + &CollisionShapeData, |
| 25 | + &GlobalTransform, |
| 26 | + Option<&renzora_physics::PhysicsBodyData>, |
| 27 | + Option<&Aabb>, |
| 28 | + )>, |
| 29 | +) { |
| 30 | + for (entity, shape, gt, body, aabb) in &query { |
| 31 | + if !selection.is_selected(entity) { |
| 32 | + continue; |
| 33 | + } |
| 34 | + let color = if shape.is_sensor { |
| 35 | + COLOR_SENSOR |
| 36 | + } else { |
| 37 | + match body.map(|b| b.body_type) { |
| 38 | + Some(renzora_physics::PhysicsBodyType::StaticBody) => COLOR_STATIC, |
| 39 | + _ => COLOR_DYNAMIC, |
| 40 | + } |
| 41 | + }; |
| 42 | + |
| 43 | + let (scale, rot, trans) = gt.to_scale_rotation_translation(); |
| 44 | + let center = trans + rot * (scale * shape.offset); |
| 45 | + let iso = Isometry3d::new(center, rot); |
| 46 | + |
| 47 | + match shape.shape_type { |
| 48 | + CollisionShapeType::Box => { |
| 49 | + let size = shape.half_extents * 2.0 * scale; |
| 50 | + let xform = Transform { |
| 51 | + translation: center, |
| 52 | + rotation: rot, |
| 53 | + scale: size, |
| 54 | + }; |
| 55 | + gizmos.cube(xform, color); |
| 56 | + } |
| 57 | + CollisionShapeType::Sphere => { |
| 58 | + let r = shape.radius * scale.max_element(); |
| 59 | + gizmos.sphere(iso, r, color); |
| 60 | + } |
| 61 | + CollisionShapeType::Capsule => { |
| 62 | + let r = shape.radius * scale.x.max(scale.z); |
| 63 | + let hh = shape.half_height * scale.y; |
| 64 | + draw_capsule(&mut gizmos, center, rot, r, hh, color); |
| 65 | + } |
| 66 | + CollisionShapeType::Cylinder => { |
| 67 | + let r = shape.radius * scale.x.max(scale.z); |
| 68 | + let hh = shape.half_height * scale.y; |
| 69 | + draw_cylinder(&mut gizmos, center, rot, r, hh, color); |
| 70 | + } |
| 71 | + CollisionShapeType::Mesh => { |
| 72 | + if let Some(aabb) = aabb { |
| 73 | + let size = Vec3::from(aabb.half_extents) * 2.0 * scale; |
| 74 | + let aabb_center = trans + rot * (scale * Vec3::from(aabb.center)); |
| 75 | + gizmos.cube(Transform { translation: aabb_center, rotation: rot, scale: size }, color); |
| 76 | + } |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +fn draw_capsule( |
| 83 | + gizmos: &mut Gizmos<OverlayGizmoGroup>, |
| 84 | + center: Vec3, |
| 85 | + rot: Quat, |
| 86 | + radius: f32, |
| 87 | + half_height: f32, |
| 88 | + color: Color, |
| 89 | +) { |
| 90 | + let up = rot * Vec3::Y; |
| 91 | + let right = rot * Vec3::X; |
| 92 | + let fwd = rot * Vec3::Z; |
| 93 | + let top = center + up * half_height; |
| 94 | + let bot = center - up * half_height; |
| 95 | + |
| 96 | + // Equator circles at the cap joins. |
| 97 | + gizmos.circle(Isometry3d::new(top, rot * Quat::from_rotation_x(std::f32::consts::FRAC_PI_2)), radius, color); |
| 98 | + gizmos.circle(Isometry3d::new(bot, rot * Quat::from_rotation_x(std::f32::consts::FRAC_PI_2)), radius, color); |
| 99 | + |
| 100 | + // Vertical connecting lines between the cap joins. |
| 101 | + gizmos.line(top + right * radius, bot + right * radius, color); |
| 102 | + gizmos.line(top - right * radius, bot - right * radius, color); |
| 103 | + gizmos.line(top + fwd * radius, bot + fwd * radius, color); |
| 104 | + gizmos.line(top - fwd * radius, bot - fwd * radius, color); |
| 105 | + |
| 106 | + // Hemisphere arcs — drawn by hand as line segments for reliability across |
| 107 | + // Bevy versions. Two arcs per cap (one in XY plane, one in ZY plane of the |
| 108 | + // capsule's local space), each spanning 180°. |
| 109 | + draw_hemi_arc(gizmos, top, up, right, radius, color); |
| 110 | + draw_hemi_arc(gizmos, top, up, fwd, radius, color); |
| 111 | + draw_hemi_arc(gizmos, bot, -up, right, radius, color); |
| 112 | + draw_hemi_arc(gizmos, bot, -up, fwd, radius, color); |
| 113 | +} |
| 114 | + |
| 115 | +/// Draw a 180° arc from `center - side*radius` up over `center + up*radius` to |
| 116 | +/// `center + side*radius`, using segmented lines. |
| 117 | +fn draw_hemi_arc( |
| 118 | + gizmos: &mut Gizmos<OverlayGizmoGroup>, |
| 119 | + center: Vec3, |
| 120 | + up: Vec3, |
| 121 | + side: Vec3, |
| 122 | + radius: f32, |
| 123 | + color: Color, |
| 124 | +) { |
| 125 | + const SEGS: usize = 16; |
| 126 | + let mut prev = center - side * radius; |
| 127 | + for i in 1..=SEGS { |
| 128 | + let t = i as f32 / SEGS as f32; |
| 129 | + let angle = std::f32::consts::PI * t; |
| 130 | + // Starts at -side (angle=0) → +up at angle=PI/2 → +side at angle=PI. |
| 131 | + let p = center + (-side * angle.cos() + up * angle.sin()) * radius; |
| 132 | + gizmos.line(prev, p, color); |
| 133 | + prev = p; |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +fn draw_cylinder( |
| 138 | + gizmos: &mut Gizmos<OverlayGizmoGroup>, |
| 139 | + center: Vec3, |
| 140 | + rot: Quat, |
| 141 | + radius: f32, |
| 142 | + half_height: f32, |
| 143 | + color: Color, |
| 144 | +) { |
| 145 | + let up = rot * Vec3::Y; |
| 146 | + let top = center + up * half_height; |
| 147 | + let bot = center - up * half_height; |
| 148 | + |
| 149 | + let cap_rot = rot * Quat::from_rotation_x(std::f32::consts::FRAC_PI_2); |
| 150 | + gizmos.circle(Isometry3d::new(top, cap_rot), radius, color); |
| 151 | + gizmos.circle(Isometry3d::new(bot, cap_rot), radius, color); |
| 152 | + |
| 153 | + let right = rot * Vec3::X; |
| 154 | + let fwd = rot * Vec3::Z; |
| 155 | + gizmos.line(top + right * radius, bot + right * radius, color); |
| 156 | + gizmos.line(top - right * radius, bot - right * radius, color); |
| 157 | + gizmos.line(top + fwd * radius, bot + fwd * radius, color); |
| 158 | + gizmos.line(top - fwd * radius, bot - fwd * radius, color); |
| 159 | +} |
0 commit comments