Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_gizmos/src/rounded_box.rs
6595 views
1
//! Additional [`GizmoBuffer`] Functions -- Rounded cuboids and rectangles
2
//!
3
//! Includes the implementation of [`GizmoBuffer::rounded_rect`], [`GizmoBuffer::rounded_rect_2d`] and [`GizmoBuffer::rounded_cuboid`].
4
//! and assorted support items.
5
6
use core::f32::consts::FRAC_PI_2;
7
8
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
9
use bevy_color::Color;
10
use bevy_math::{Isometry2d, Isometry3d, Quat, Vec2, Vec3};
11
use bevy_transform::components::Transform;
12
13
/// A builder returned by [`GizmoBuffer::rounded_rect`] and [`GizmoBuffer::rounded_rect_2d`]
14
pub struct RoundedRectBuilder<'a, Config, Clear>
15
where
16
Config: GizmoConfigGroup,
17
Clear: 'static + Send + Sync,
18
{
19
size: Vec2,
20
gizmos: &'a mut GizmoBuffer<Config, Clear>,
21
config: RoundedBoxConfig,
22
}
23
/// A builder returned by [`GizmoBuffer::rounded_cuboid`]
24
pub struct RoundedCuboidBuilder<'a, Config, Clear>
25
where
26
Config: GizmoConfigGroup,
27
Clear: 'static + Send + Sync,
28
{
29
size: Vec3,
30
gizmos: &'a mut GizmoBuffer<Config, Clear>,
31
config: RoundedBoxConfig,
32
}
33
struct RoundedBoxConfig {
34
isometry: Isometry3d,
35
color: Color,
36
corner_radius: f32,
37
arc_resolution: u32,
38
}
39
40
impl<Config, Clear> RoundedRectBuilder<'_, Config, Clear>
41
where
42
Config: GizmoConfigGroup,
43
Clear: 'static + Send + Sync,
44
{
45
/// Change the radius of the corners to be `corner_radius`.
46
/// The default corner radius is [min axis of size] / 10.0
47
pub fn corner_radius(mut self, corner_radius: f32) -> Self {
48
self.config.corner_radius = corner_radius;
49
self
50
}
51
52
/// Change the resolution of the arcs at the corners of the rectangle.
53
/// The default value is 8
54
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
55
self.config.arc_resolution = arc_resolution;
56
self
57
}
58
}
59
60
impl<Config, Clear> RoundedCuboidBuilder<'_, Config, Clear>
61
where
62
Config: GizmoConfigGroup,
63
Clear: 'static + Send + Sync,
64
{
65
/// Change the radius of the edges to be `edge_radius`.
66
/// The default edge radius is [min axis of size] / 10.0
67
pub fn edge_radius(mut self, edge_radius: f32) -> Self {
68
self.config.corner_radius = edge_radius;
69
self
70
}
71
72
/// Change the resolution of the arcs at the edges of the cuboid.
73
/// The default value is 8
74
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
75
self.config.arc_resolution = arc_resolution;
76
self
77
}
78
}
79
80
impl<Config, Clear> Drop for RoundedRectBuilder<'_, Config, Clear>
81
where
82
Config: GizmoConfigGroup,
83
Clear: 'static + Send + Sync,
84
{
85
fn drop(&mut self) {
86
if !self.gizmos.enabled {
87
return;
88
}
89
let config = &self.config;
90
91
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
92
let mut outer_half_size = self.size.abs() / 2.0;
93
let inner_half_size =
94
(outer_half_size - Vec2::splat(config.corner_radius.abs())).max(Vec2::ZERO);
95
let corner_radius = (outer_half_size - inner_half_size).min_element();
96
let mut inner_half_size = outer_half_size - Vec2::splat(corner_radius);
97
98
if config.corner_radius < 0. {
99
core::mem::swap(&mut outer_half_size, &mut inner_half_size);
100
}
101
102
// Handle cases where the rectangle collapses into simpler shapes
103
if outer_half_size.x * outer_half_size.y == 0. {
104
self.gizmos.line(
105
config.isometry * -outer_half_size.extend(0.),
106
config.isometry * outer_half_size.extend(0.),
107
config.color,
108
);
109
return;
110
}
111
if corner_radius == 0. {
112
self.gizmos.rect(config.isometry, self.size, config.color);
113
return;
114
}
115
116
let vertices = [
117
// top right
118
Vec3::new(inner_half_size.x, outer_half_size.y, 0.),
119
Vec3::new(inner_half_size.x, inner_half_size.y, 0.),
120
Vec3::new(outer_half_size.x, inner_half_size.y, 0.),
121
// bottom right
122
Vec3::new(outer_half_size.x, -inner_half_size.y, 0.),
123
Vec3::new(inner_half_size.x, -inner_half_size.y, 0.),
124
Vec3::new(inner_half_size.x, -outer_half_size.y, 0.),
125
// bottom left
126
Vec3::new(-inner_half_size.x, -outer_half_size.y, 0.),
127
Vec3::new(-inner_half_size.x, -inner_half_size.y, 0.),
128
Vec3::new(-outer_half_size.x, -inner_half_size.y, 0.),
129
// top left
130
Vec3::new(-outer_half_size.x, inner_half_size.y, 0.),
131
Vec3::new(-inner_half_size.x, inner_half_size.y, 0.),
132
Vec3::new(-inner_half_size.x, outer_half_size.y, 0.),
133
]
134
.map(|vec3| config.isometry * vec3);
135
136
for chunk in vertices.chunks_exact(3) {
137
self.gizmos
138
.short_arc_3d_between(chunk[1], chunk[0], chunk[2], config.color)
139
.resolution(config.arc_resolution);
140
}
141
142
let edges = if config.corner_radius > 0. {
143
[
144
(vertices[2], vertices[3]),
145
(vertices[5], vertices[6]),
146
(vertices[8], vertices[9]),
147
(vertices[11], vertices[0]),
148
]
149
} else {
150
[
151
(vertices[0], vertices[5]),
152
(vertices[3], vertices[8]),
153
(vertices[6], vertices[11]),
154
(vertices[9], vertices[2]),
155
]
156
};
157
158
for (start, end) in edges {
159
self.gizmos.line(start, end, config.color);
160
}
161
}
162
}
163
164
impl<Config, Clear> Drop for RoundedCuboidBuilder<'_, Config, Clear>
165
where
166
Config: GizmoConfigGroup,
167
Clear: 'static + Send + Sync,
168
{
169
fn drop(&mut self) {
170
if !self.gizmos.enabled {
171
return;
172
}
173
let config = &self.config;
174
175
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
176
let outer_half_size = self.size.abs() / 2.0;
177
let inner_half_size =
178
(outer_half_size - Vec3::splat(config.corner_radius.abs())).max(Vec3::ZERO);
179
let mut edge_radius = (outer_half_size - inner_half_size).min_element();
180
let inner_half_size = outer_half_size - Vec3::splat(edge_radius);
181
edge_radius *= config.corner_radius.signum();
182
183
// Handle cases where the rounded cuboid collapses into simpler shapes
184
if edge_radius == 0.0 {
185
let transform = Transform::from_translation(config.isometry.translation.into())
186
.with_rotation(config.isometry.rotation)
187
.with_scale(self.size);
188
self.gizmos.cuboid(transform, config.color);
189
return;
190
}
191
192
let rects = [
193
(
194
Vec3::X,
195
Vec2::new(self.size.z, self.size.y),
196
Quat::from_rotation_y(FRAC_PI_2),
197
),
198
(
199
Vec3::Y,
200
Vec2::new(self.size.x, self.size.z),
201
Quat::from_rotation_x(FRAC_PI_2),
202
),
203
(Vec3::Z, Vec2::new(self.size.x, self.size.y), Quat::IDENTITY),
204
];
205
206
for (position, size, rotation) in rects {
207
let local_position = position * inner_half_size;
208
self.gizmos
209
.rounded_rect(
210
config.isometry * Isometry3d::new(local_position, rotation),
211
size,
212
config.color,
213
)
214
.arc_resolution(config.arc_resolution)
215
.corner_radius(edge_radius);
216
217
self.gizmos
218
.rounded_rect(
219
config.isometry * Isometry3d::new(-local_position, rotation),
220
size,
221
config.color,
222
)
223
.arc_resolution(config.arc_resolution)
224
.corner_radius(edge_radius);
225
}
226
}
227
}
228
229
impl<Config, Clear> GizmoBuffer<Config, Clear>
230
where
231
Config: GizmoConfigGroup,
232
Clear: 'static + Send + Sync,
233
{
234
/// Draw a wireframe rectangle with rounded corners in 3D.
235
///
236
/// This should be called for each frame the rectangle needs to be rendered.
237
///
238
/// # Arguments
239
///
240
/// - `isometry` defines the translation and rotation of the rectangle.
241
/// - the translation specifies the center of the rectangle
242
/// - defines orientation of the rectangle, by default we assume the rectangle is contained in
243
/// a plane parallel to the XY plane.
244
/// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box.
245
/// - `color`: color of the rectangle
246
///
247
/// # Builder methods
248
///
249
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
250
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
251
/// `.arc_resolution(...)` method.
252
///
253
/// # Example
254
/// ```
255
/// # use bevy_gizmos::prelude::*;
256
/// # use bevy_math::prelude::*;
257
/// # use bevy_color::palettes::css::GREEN;
258
/// fn system(mut gizmos: Gizmos) {
259
/// gizmos.rounded_rect(
260
/// Isometry3d::IDENTITY,
261
/// Vec2::ONE,
262
/// GREEN
263
/// )
264
/// .corner_radius(0.25)
265
/// .arc_resolution(10);
266
/// }
267
/// # bevy_ecs::system::assert_is_system(system);
268
/// ```
269
pub fn rounded_rect(
270
&mut self,
271
isometry: impl Into<Isometry3d>,
272
size: Vec2,
273
color: impl Into<Color>,
274
) -> RoundedRectBuilder<'_, Config, Clear> {
275
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
276
RoundedRectBuilder {
277
gizmos: self,
278
config: RoundedBoxConfig {
279
isometry: isometry.into(),
280
color: color.into(),
281
corner_radius,
282
arc_resolution: DEFAULT_ARC_RESOLUTION,
283
},
284
size,
285
}
286
}
287
288
/// Draw a wireframe rectangle with rounded corners in 2D.
289
///
290
/// This should be called for each frame the rectangle needs to be rendered.
291
///
292
/// # Arguments
293
///
294
/// - `isometry` defines the translation and rotation of the rectangle.
295
/// - the translation specifies the center of the rectangle
296
/// - defines orientation of the rectangle, by default we assume the rectangle aligned with all axes.
297
/// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box.
298
/// - `color`: color of the rectangle
299
///
300
/// # Builder methods
301
///
302
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
303
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
304
/// `.arc_resolution(...)` method.
305
///
306
/// # Example
307
/// ```
308
/// # use bevy_gizmos::prelude::*;
309
/// # use bevy_math::prelude::*;
310
/// # use bevy_color::palettes::css::GREEN;
311
/// fn system(mut gizmos: Gizmos) {
312
/// gizmos.rounded_rect_2d(
313
/// Isometry2d::IDENTITY,
314
/// Vec2::ONE,
315
/// GREEN
316
/// )
317
/// .corner_radius(0.25)
318
/// .arc_resolution(10);
319
/// }
320
/// # bevy_ecs::system::assert_is_system(system);
321
/// ```
322
pub fn rounded_rect_2d(
323
&mut self,
324
isometry: impl Into<Isometry2d>,
325
size: Vec2,
326
color: impl Into<Color>,
327
) -> RoundedRectBuilder<'_, Config, Clear> {
328
let isometry = isometry.into();
329
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
330
RoundedRectBuilder {
331
gizmos: self,
332
config: RoundedBoxConfig {
333
isometry: Isometry3d::new(
334
isometry.translation.extend(0.0),
335
Quat::from_rotation_z(isometry.rotation.as_radians()),
336
),
337
color: color.into(),
338
corner_radius,
339
arc_resolution: DEFAULT_ARC_RESOLUTION,
340
},
341
size,
342
}
343
}
344
345
/// Draw a wireframe cuboid with rounded corners in 3D.
346
///
347
/// This should be called for each frame the cuboid needs to be rendered.
348
///
349
/// # Arguments
350
///
351
/// - `isometry` defines the translation and rotation of the cuboid.
352
/// - the translation specifies the center of the cuboid
353
/// - defines orientation of the cuboid, by default we assume the cuboid aligned with all axes.
354
/// - `size`: defines the size of the cuboid. This refers to the 'outer size', similar to a bounding box.
355
/// - `color`: color of the cuboid
356
///
357
/// # Builder methods
358
///
359
/// - The edge radius can be adjusted with the `.edge_radius(...)` method.
360
/// - The resolution of the arcs at each edge (i.e. the level of detail) can be adjusted with the
361
/// `.arc_resolution(...)` method.
362
///
363
/// # Example
364
/// ```
365
/// # use bevy_gizmos::prelude::*;
366
/// # use bevy_math::prelude::*;
367
/// # use bevy_color::palettes::css::GREEN;
368
/// fn system(mut gizmos: Gizmos) {
369
/// gizmos.rounded_cuboid(
370
/// Isometry3d::IDENTITY,
371
/// Vec3::ONE,
372
/// GREEN
373
/// )
374
/// .edge_radius(0.25)
375
/// .arc_resolution(10);
376
/// }
377
/// # bevy_ecs::system::assert_is_system(system);
378
/// ```
379
pub fn rounded_cuboid(
380
&mut self,
381
isometry: impl Into<Isometry3d>,
382
size: Vec3,
383
color: impl Into<Color>,
384
) -> RoundedCuboidBuilder<'_, Config, Clear> {
385
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
386
RoundedCuboidBuilder {
387
gizmos: self,
388
config: RoundedBoxConfig {
389
isometry: isometry.into(),
390
color: color.into(),
391
corner_radius,
392
arc_resolution: DEFAULT_ARC_RESOLUTION,
393
},
394
size,
395
}
396
}
397
}
398
399
const DEFAULT_ARC_RESOLUTION: u32 = 8;
400
const DEFAULT_CORNER_RADIUS: f32 = 0.1;
401
402