mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Immediate Mode Line/Gizmo Drawing (#6529)
# Objective Add a convenient immediate mode drawing API for visual debugging. Fixes #5619 Alternative to #1625 Partial alternative to #5734 Based off https://github.com/Toqozz/bevy_debug_lines with some changes: * Simultaneous support for 2D and 3D. * Methods for basic shapes; circles, spheres, rectangles, boxes, etc. * 2D methods. * Removed durations. Seemed niche, and can be handled by users. <details> <summary>Performance</summary> Stress tested using Bevy's recommended optimization settings for the dev profile with the following command. ```bash cargo run --example many_debug_lines \ --config "profile.dev.package.\"*\".opt-level=3" \ --config "profile.dev.opt-level=1" ``` I dipped to 65-70 FPS at 300,000 lines CPU: 3700x RAM Speed: 3200 Mhz GPU: 2070 super - probably not very relevant, mostly cpu/memory bound </details> <details> <summary>Fancy bloom screenshot</summary> ![Screenshot_20230207_155033](https://user-images.githubusercontent.com/29694403/217291980-f1e0500e-7a14-4131-8c96-eaaaf52596ae.png) </details> ## Changelog * Added `GizmoPlugin` * Added `Gizmos` system parameter for drawing lines and wireshapes. ### TODO - [ ] Update changelog - [x] Update performance numbers - [x] Add credit to PR description ### Future work - Cache rendering primitives instead of constructing them out of line segments each frame. - Support for drawing solid meshes - Interactions. (See [bevy_mod_gizmos](https://github.com/LiamGallagher737/bevy_mod_gizmos)) - Fancier line drawing. (See [bevy_polyline](https://github.com/ForesightMiningSoftwareCorporation/bevy_polyline)) - Support for `RenderLayers` - Display gizmos for a certain duration. Currently everything displays for one frame (ie. immediate mode) - Changing settings per drawn item like drawing on top or drawing to different `RenderLayers` Co-Authored By: @lassade <felipe.jorge.pereira@gmail.com> Co-Authored By: @The5-1 <agaku@hotmail.de> Co-Authored By: @Toqozz <toqoz@hotmail.com> Co-Authored By: @nicopap <nico@nicopap.ch> --------- Co-authored-by: Robert Swain <robert.swain@gmail.com> Co-authored-by: IceSentry <c.giguere42@gmail.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
d58ed67fa4
commit
6a85eb3d7e
17 changed files with 1205 additions and 1 deletions
34
Cargo.toml
34
Cargo.toml
|
@ -49,6 +49,7 @@ default = [
|
|||
"vorbis",
|
||||
"x11",
|
||||
"filesystem_watcher",
|
||||
"bevy_gizmos",
|
||||
"android_shared_stdcxx",
|
||||
"tonemapping_luts",
|
||||
]
|
||||
|
@ -98,6 +99,9 @@ bevy_ui = ["bevy_internal/bevy_ui", "bevy_core_pipeline", "bevy_text", "bevy_spr
|
|||
# winit window and input backend
|
||||
bevy_winit = ["bevy_internal/bevy_winit"]
|
||||
|
||||
# Adds support for rendering gizmos
|
||||
bevy_gizmos = ["bevy_internal/bevy_gizmos"]
|
||||
|
||||
# Tracing support, saving a file in Chrome Tracing format
|
||||
trace_chrome = ["trace", "bevy_internal/trace_chrome"]
|
||||
|
||||
|
@ -309,6 +313,16 @@ description = "Renders a rectangle, circle, and hexagon"
|
|||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "2d_gizmos"
|
||||
path = "examples/2d/2d_gizmos.rs"
|
||||
|
||||
[package.metadata.example.2d_gizmos]
|
||||
name = "2D Gizmos"
|
||||
description = "A scene showcasing 2D gizmos"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "sprite"
|
||||
path = "examples/2d/sprite.rs"
|
||||
|
@ -400,6 +414,16 @@ description = "A scene showcasing the built-in 3D shapes"
|
|||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "3d_gizmos"
|
||||
path = "examples/3d/3d_gizmos.rs"
|
||||
|
||||
[package.metadata.example.3d_gizmos]
|
||||
name = "3D Gizmos"
|
||||
description = "A scene showcasing 3D gizmos"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "atmospheric_fog"
|
||||
path = "examples/3d/atmospheric_fog.rs"
|
||||
|
@ -1553,6 +1577,16 @@ description = "Simple benchmark to test per-entity draw overhead. Run with the `
|
|||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_gizmos"
|
||||
path = "examples/stress_tests/many_gizmos.rs"
|
||||
|
||||
[package.metadata.example.many_gizmos]
|
||||
name = "Many Gizmos"
|
||||
description = "Test rendering of many gizmos"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_foxes"
|
||||
path = "examples/stress_tests/many_foxes.rs"
|
||||
|
|
23
crates/bevy_gizmos/Cargo.toml
Normal file
23
crates/bevy_gizmos/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "bevy_gizmos"
|
||||
version = "0.11.0-dev"
|
||||
edition = "2021"
|
||||
description = "Provides gizmos for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# Bevy
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.11.0-dev", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.11.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.11.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.11.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.11.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.11.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.11.0-dev" }
|
349
crates/bevy_gizmos/src/gizmos.rs
Normal file
349
crates/bevy_gizmos/src/gizmos.rs
Normal file
|
@ -0,0 +1,349 @@
|
|||
use std::{f32::consts::TAU, iter};
|
||||
|
||||
use bevy_ecs::{
|
||||
system::{Deferred, Resource, SystemBuffer, SystemMeta},
|
||||
world::World,
|
||||
};
|
||||
use bevy_math::{Mat2, Quat, Vec2, Vec3};
|
||||
use bevy_render::prelude::Color;
|
||||
|
||||
type PositionItem = [f32; 3];
|
||||
type ColorItem = [f32; 4];
|
||||
|
||||
const DEFAULT_CIRCLE_SEGMENTS: usize = 32;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct GizmoStorage {
|
||||
pub list_positions: Vec<PositionItem>,
|
||||
pub list_colors: Vec<ColorItem>,
|
||||
pub strip_positions: Vec<PositionItem>,
|
||||
pub strip_colors: Vec<ColorItem>,
|
||||
}
|
||||
|
||||
pub type Gizmos<'s> = Deferred<'s, GizmoBuffer>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GizmoBuffer {
|
||||
list_positions: Vec<PositionItem>,
|
||||
list_colors: Vec<ColorItem>,
|
||||
strip_positions: Vec<PositionItem>,
|
||||
strip_colors: Vec<ColorItem>,
|
||||
}
|
||||
|
||||
impl SystemBuffer for GizmoBuffer {
|
||||
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
|
||||
let mut storage = world.resource_mut::<GizmoStorage>();
|
||||
storage.list_positions.append(&mut self.list_positions);
|
||||
storage.list_colors.append(&mut self.list_colors);
|
||||
storage.strip_positions.append(&mut self.strip_positions);
|
||||
storage.strip_colors.append(&mut self.strip_colors);
|
||||
}
|
||||
}
|
||||
|
||||
impl GizmoBuffer {
|
||||
#[inline]
|
||||
pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) {
|
||||
self.extend_list_positions([start, end]);
|
||||
self.add_list_color(color, 2);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_gradient(&mut self, start: Vec3, end: Vec3, start_color: Color, end_color: Color) {
|
||||
self.extend_list_positions([start, end]);
|
||||
self.extend_list_colors([start_color, end_color]);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) {
|
||||
self.line(start, start + vector, color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_gradient(
|
||||
&mut self,
|
||||
start: Vec3,
|
||||
vector: Vec3,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient(start, start + vector, start_color, end_color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip(&mut self, positions: impl IntoIterator<Item = Vec3>, color: Color) {
|
||||
self.extend_strip_positions(positions.into_iter());
|
||||
self.strip_colors
|
||||
.resize(self.strip_positions.len() - 1, color.as_linear_rgba_f32());
|
||||
self.strip_colors.push([f32::NAN; 4]);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_gradient(&mut self, points: impl IntoIterator<Item = (Vec3, Color)>) {
|
||||
let points = points.into_iter();
|
||||
|
||||
let (min, _) = points.size_hint();
|
||||
self.strip_positions.reserve(min);
|
||||
self.strip_colors.reserve(min);
|
||||
|
||||
for (position, color) in points {
|
||||
self.strip_positions.push(position.to_array());
|
||||
self.strip_colors.push(color.as_linear_rgba_f32());
|
||||
}
|
||||
|
||||
self.strip_positions.push([f32::NAN; 3]);
|
||||
self.strip_colors.push([f32::NAN; 4]);
|
||||
}
|
||||
|
||||
/// Draw a circle at `position` with the flat side facing `normal`.
|
||||
#[inline]
|
||||
pub fn circle(
|
||||
&mut self,
|
||||
position: Vec3,
|
||||
normal: Vec3,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
) -> CircleBuilder {
|
||||
CircleBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
normal,
|
||||
radius,
|
||||
color,
|
||||
segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a sphere.
|
||||
#[inline]
|
||||
pub fn sphere(
|
||||
&mut self,
|
||||
position: Vec3,
|
||||
rotation: Quat,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
) -> SphereBuilder {
|
||||
SphereBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
rotation,
|
||||
radius,
|
||||
color,
|
||||
circle_segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle.
|
||||
#[inline]
|
||||
pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: Color) {
|
||||
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2.extend(0.));
|
||||
self.linestrip([tl, tr, br, bl, tl], color);
|
||||
}
|
||||
|
||||
/// Draw a box.
|
||||
#[inline]
|
||||
pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) {
|
||||
let rect = rect_inner(size.truncate());
|
||||
// Front
|
||||
let [tlf, trf, brf, blf] = rect.map(|vec2| position + rotation * vec2.extend(size.z / 2.));
|
||||
// Back
|
||||
let [tlb, trb, brb, blb] = rect.map(|vec2| position + rotation * vec2.extend(-size.z / 2.));
|
||||
|
||||
let positions = [
|
||||
tlf, trf, trf, brf, brf, blf, blf, tlf, // Front
|
||||
tlb, trb, trb, brb, brb, blb, blb, tlb, // Back
|
||||
tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back
|
||||
];
|
||||
self.extend_list_positions(positions);
|
||||
self.add_list_color(color, 24);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: Color) {
|
||||
self.line(start.extend(0.), end.extend(0.), color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_gradient_2d(
|
||||
&mut self,
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_2d(&mut self, positions: impl IntoIterator<Item = Vec2>, color: Color) {
|
||||
self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_gradient_2d(&mut self, positions: impl IntoIterator<Item = (Vec2, Color)>) {
|
||||
self.linestrip_gradient(
|
||||
positions
|
||||
.into_iter()
|
||||
.map(|(vec2, color)| (vec2.extend(0.), color)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: Color) {
|
||||
self.line_2d(start, start + vector, color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_gradient_2d(
|
||||
&mut self,
|
||||
start: Vec2,
|
||||
vector: Vec2,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient_2d(start, start + vector, start_color, end_color);
|
||||
}
|
||||
|
||||
// Draw a circle.
|
||||
#[inline]
|
||||
pub fn circle_2d(&mut self, position: Vec2, radius: f32, color: Color) -> Circle2dBuilder {
|
||||
Circle2dBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
radius,
|
||||
color,
|
||||
segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle.
|
||||
#[inline]
|
||||
pub fn rect_2d(&mut self, position: Vec2, rotation: f32, size: Vec2, color: Color) {
|
||||
let rotation = Mat2::from_angle(rotation);
|
||||
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2);
|
||||
self.linestrip_2d([tl, tr, br, bl, tl], color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_list_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
|
||||
self.list_positions
|
||||
.extend(positions.into_iter().map(|vec3| vec3.to_array()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_list_colors(&mut self, colors: impl IntoIterator<Item = Color>) {
|
||||
self.list_colors
|
||||
.extend(colors.into_iter().map(|color| color.as_linear_rgba_f32()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_list_color(&mut self, color: Color, count: usize) {
|
||||
self.list_colors
|
||||
.extend(iter::repeat(color.as_linear_rgba_f32()).take(count));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_strip_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
|
||||
self.strip_positions.extend(
|
||||
positions
|
||||
.into_iter()
|
||||
.map(|vec3| vec3.to_array())
|
||||
.chain(iter::once([f32::NAN; 3])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CircleBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec3,
|
||||
normal: Vec3,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
segments: usize,
|
||||
}
|
||||
|
||||
impl<'a> CircleBuilder<'a> {
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for CircleBuilder<'a> {
|
||||
fn drop(&mut self) {
|
||||
let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal);
|
||||
let positions = circle_inner(self.radius, self.segments)
|
||||
.map(|vec2| (self.position + rotation * vec2.extend(0.)));
|
||||
self.buffer.linestrip(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SphereBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec3,
|
||||
rotation: Quat,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
circle_segments: usize,
|
||||
}
|
||||
|
||||
impl SphereBuilder<'_> {
|
||||
pub fn circle_segments(mut self, segments: usize) -> Self {
|
||||
self.circle_segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SphereBuilder<'_> {
|
||||
fn drop(&mut self) {
|
||||
for axis in Vec3::AXES {
|
||||
self.buffer
|
||||
.circle(self.position, self.rotation * axis, self.radius, self.color)
|
||||
.segments(self.circle_segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Circle2dBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec2,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
segments: usize,
|
||||
}
|
||||
|
||||
impl Circle2dBuilder<'_> {
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Circle2dBuilder<'_> {
|
||||
fn drop(&mut self) {
|
||||
let positions = circle_inner(self.radius, self.segments).map(|vec2| (vec2 + self.position));
|
||||
self.buffer.linestrip_2d(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
fn circle_inner(radius: f32, segments: usize) -> impl Iterator<Item = Vec2> {
|
||||
(0..segments + 1).map(move |i| {
|
||||
let angle = i as f32 * TAU / segments as f32;
|
||||
Vec2::from(angle.sin_cos()) * radius
|
||||
})
|
||||
}
|
||||
|
||||
fn rect_inner(size: Vec2) -> [Vec2; 4] {
|
||||
let half_size = size / 2.;
|
||||
let tl = Vec2::new(-half_size.x, half_size.y);
|
||||
let tr = Vec2::new(half_size.x, half_size.y);
|
||||
let bl = Vec2::new(-half_size.x, -half_size.y);
|
||||
let br = Vec2::new(half_size.x, -half_size.y);
|
||||
[tl, tr, br, bl]
|
||||
}
|
187
crates/bevy_gizmos/src/lib.rs
Normal file
187
crates/bevy_gizmos/src/lib.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
use std::mem;
|
||||
|
||||
use bevy_app::{Last, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, DetectChanges},
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
mesh::Mesh,
|
||||
render_phase::AddRenderCommand,
|
||||
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
use bevy_pbr::MeshUniform;
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
use bevy_sprite::{Mesh2dHandle, Mesh2dUniform};
|
||||
|
||||
pub mod gizmos;
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
mod pipeline_2d;
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
mod pipeline_3d;
|
||||
|
||||
use crate::gizmos::GizmoStorage;
|
||||
|
||||
/// The `bevy_gizmos` prelude.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{gizmos::Gizmos, GizmoConfig};
|
||||
}
|
||||
|
||||
const LINE_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7414812689238026784);
|
||||
|
||||
pub struct GizmoPlugin;
|
||||
|
||||
impl Plugin for GizmoPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.init_resource::<MeshHandles>()
|
||||
.init_resource::<GizmoConfig>()
|
||||
.init_resource::<GizmoStorage>()
|
||||
.add_systems(Last, update_gizmo_meshes);
|
||||
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
render_app.add_systems(ExtractSchedule, extract_gizmo_data);
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
{
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use pipeline_2d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoLinePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoLinePipeline>>()
|
||||
.add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
{
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use pipeline_3d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoPipeline>>()
|
||||
.add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone, Copy)]
|
||||
pub struct GizmoConfig {
|
||||
/// Set to `false` to stop drawing gizmos.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
pub enabled: bool,
|
||||
/// Draw gizmos on top of everything else, ignoring depth.
|
||||
///
|
||||
/// This setting only affects 3D. In 2D, gizmos are always drawn on top.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub on_top: bool,
|
||||
}
|
||||
|
||||
impl Default for GizmoConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
on_top: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MeshHandles {
|
||||
list: Handle<Mesh>,
|
||||
strip: Handle<Mesh>,
|
||||
}
|
||||
|
||||
impl FromWorld for MeshHandles {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let mut meshes = world.resource_mut::<Assets<Mesh>>();
|
||||
|
||||
MeshHandles {
|
||||
list: meshes.add(Mesh::new(PrimitiveTopology::LineList)),
|
||||
strip: meshes.add(Mesh::new(PrimitiveTopology::LineStrip)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct GizmoMesh;
|
||||
|
||||
fn update_gizmo_meshes(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
handles: Res<MeshHandles>,
|
||||
mut storage: ResMut<GizmoStorage>,
|
||||
) {
|
||||
let list_mesh = meshes.get_mut(&handles.list).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.list_positions);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.list_colors);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
|
||||
let strip_mesh = meshes.get_mut(&handles.strip).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.strip_positions);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.strip_colors);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
}
|
||||
|
||||
fn extract_gizmo_data(
|
||||
mut commands: Commands,
|
||||
handles: Extract<Res<MeshHandles>>,
|
||||
config: Extract<Res<GizmoConfig>>,
|
||||
) {
|
||||
if config.is_changed() {
|
||||
commands.insert_resource(**config);
|
||||
}
|
||||
|
||||
if !config.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = Mat4::IDENTITY;
|
||||
let inverse_transpose_model = transform.inverse().transpose();
|
||||
commands.spawn_batch([&handles.list, &handles.strip].map(|handle| {
|
||||
(
|
||||
GizmoMesh,
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
(
|
||||
handle.clone(),
|
||||
MeshUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
(
|
||||
Mesh2dHandle(handle.clone()),
|
||||
Mesh2dUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
)
|
||||
}));
|
||||
}
|
44
crates/bevy_gizmos/src/lines.wgsl
Normal file
44
crates/bevy_gizmos/src/lines.wgsl
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifdef GIZMO_LINES_3D
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#else
|
||||
#import bevy_sprite::mesh2d_view_bindings
|
||||
#endif
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) pos: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) pos: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct FragmentOutput {
|
||||
@builtin(frag_depth) depth: f32,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
out.pos = view.view_proj * vec4<f32>(in.pos, 1.0);
|
||||
out.color = in.color;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> FragmentOutput {
|
||||
var out: FragmentOutput;
|
||||
|
||||
#ifdef DEPTH_TEST
|
||||
out.depth = in.pos.z;
|
||||
#else
|
||||
out.depth = 1.0;
|
||||
#endif
|
||||
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
121
crates/bevy_gizmos/src/pipeline_2d.rs
Normal file
121
crates/bevy_gizmos/src/pipeline_2d.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use bevy_ecs::{
|
||||
prelude::Entity,
|
||||
query::With,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::Msaa,
|
||||
};
|
||||
use bevy_sprite::*;
|
||||
use bevy_utils::FloatOrd;
|
||||
|
||||
use crate::{GizmoMesh, LINE_SHADER_HANDLE};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoLinePipeline {
|
||||
mesh_pipeline: Mesh2dPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoLinePipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoLinePipeline {
|
||||
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoLinePipeline {
|
||||
type Key = Mesh2dPipelineKey;
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: vec![],
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: TextureFormat::bevy_default(),
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: vec![self.mesh_pipeline.view_layout.clone()],
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_2d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
SetItemPipeline,
|
||||
SetMesh2dViewBindGroup<0>,
|
||||
SetMesh2dBindGroup<1>,
|
||||
DrawMesh2d,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_2d(
|
||||
draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
pipeline: Res<GizmoLinePipeline>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_pipelines: ResMut<SpecializedMeshPipelines<GizmoLinePipeline>>,
|
||||
gpu_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Mesh2dHandle), With<GizmoMesh>>,
|
||||
mut views: Query<&mut RenderPhase<Transparent2d>>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for mut phase in &mut views {
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
let Some(mesh) = gpu_meshes.get(&mesh_handle.0) else { continue; };
|
||||
|
||||
let key = key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = specialized_pipelines
|
||||
.specialize(&pipeline_cache, &pipeline, key, &mesh.layout)
|
||||
.unwrap();
|
||||
phase.add(Transparent2d {
|
||||
entity,
|
||||
draw_function,
|
||||
pipeline,
|
||||
sort_key: FloatOrd(f32::MAX),
|
||||
batch_range: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
164
crates/bevy_gizmos/src/pipeline_3d.rs
Normal file
164
crates/bevy_gizmos/src/pipeline_3d.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::With,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_pbr::*;
|
||||
use bevy_render::{
|
||||
mesh::Mesh,
|
||||
render_resource::Shader,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::Msaa,
|
||||
};
|
||||
|
||||
use crate::{GizmoConfig, GizmoMesh, LINE_SHADER_HANDLE};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoPipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoPipeline {
|
||||
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoPipeline {
|
||||
type Key = (bool, MeshPipelineKey);
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(depth_test, key): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut shader_defs = Vec::new();
|
||||
shader_defs.push("GIZMO_LINES_3D".into());
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"MAX_DIRECTIONAL_LIGHTS".to_string(),
|
||||
MAX_DIRECTIONAL_LIGHTS as i32,
|
||||
));
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"MAX_CASCADES_PER_LIGHT".to_string(),
|
||||
MAX_CASCADES_PER_LIGHT as i32,
|
||||
));
|
||||
if depth_test {
|
||||
shader_defs.push("DEPTH_TEST".into());
|
||||
}
|
||||
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
let bind_group_layout = match key.msaa_samples() {
|
||||
1 => vec![self.mesh_pipeline.view_layout.clone()],
|
||||
_ => {
|
||||
shader_defs.push("MULTISAMPLED".into());
|
||||
vec![self.mesh_pipeline.view_layout_multisampled.clone()]
|
||||
}
|
||||
};
|
||||
|
||||
let format = if key.contains(MeshPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
};
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: bind_group_layout,
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Greater,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_3d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_3d(
|
||||
draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
pipeline: Res<GizmoPipeline>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<GizmoPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Handle<Mesh>), With<GizmoMesh>>,
|
||||
config: Res<GizmoConfig>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, mut phase) in &mut views {
|
||||
let key = key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
(!config.on_top, key),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function,
|
||||
distance: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -71,11 +71,14 @@ subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
|
|||
webgl = ["bevy_core_pipeline?/webgl", "bevy_pbr?/webgl", "bevy_render?/webgl"]
|
||||
|
||||
# enable systems that allow for automated testing on CI
|
||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]
|
||||
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render?/ci_limits"]
|
||||
|
||||
# Enable animation support, and glTF animation loading
|
||||
animation = ["bevy_animation", "bevy_gltf?/bevy_animation"]
|
||||
|
||||
bevy_sprite = ["dep:bevy_sprite", "bevy_gizmos?/bevy_sprite"]
|
||||
bevy_pbr = ["dep:bevy_pbr", "bevy_gizmos?/bevy_pbr"]
|
||||
|
||||
# Used to disable code that is unsupported when Bevy is dynamically linked
|
||||
dynamic_linking = ["bevy_diagnostic/dynamic_linking"]
|
||||
|
||||
|
@ -122,3 +125,4 @@ bevy_text = { path = "../bevy_text", optional = true, version = "0.11.0-dev" }
|
|||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.11.0-dev" }
|
||||
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.11.0-dev" }
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.11.0-dev" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.11.0-dev", default-features = false }
|
||||
|
|
|
@ -135,6 +135,11 @@ impl PluginGroup for DefaultPlugins {
|
|||
group = group.add(bevy_animation::AnimationPlugin::default());
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_gizmos")]
|
||||
{
|
||||
group = group.add(bevy_gizmos::GizmoPlugin);
|
||||
}
|
||||
|
||||
group
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,12 @@ pub mod winit {
|
|||
pub use bevy_winit::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_gizmos")]
|
||||
pub mod gizmos {
|
||||
//! Immediate mode debug drawing.
|
||||
pub use bevy_gizmos::*;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_dynamic_plugin")]
|
||||
pub mod dynamic_plugin {
|
||||
//! Dynamic linking of plugins
|
||||
|
|
|
@ -51,6 +51,10 @@ pub use crate::ui::prelude::*;
|
|||
#[cfg(feature = "bevy_dynamic_plugin")]
|
||||
pub use crate::dynamic_plugin::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_gizmos")]
|
||||
pub use crate::gizmos::prelude::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_gilrs")]
|
||||
pub use crate::gilrs::*;
|
||||
|
|
|
@ -18,6 +18,7 @@ The default feature set enables most of the expected features of a game engine,
|
|||
|bevy_audio|Provides audio functionality|
|
||||
|bevy_core_pipeline|Provides cameras and other basic render pipeline features|
|
||||
|bevy_gilrs|Adds gamepad support|
|
||||
|bevy_gizmos|Adds support for rendering gizmos|
|
||||
|bevy_gltf|[glTF](https://www.khronos.org/gltf/) support|
|
||||
|bevy_pbr|Adds PBR rendering|
|
||||
|bevy_render|Provides rendering functionality|
|
||||
|
|
41
examples/2d/2d_gizmos.rs
Normal file
41
examples/2d/2d_gizmos.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, system)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
}
|
||||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
let sin = time.elapsed_seconds().sin() * 50.;
|
||||
gizmos.line_2d(Vec2::Y * -sin, Vec2::splat(-80.), Color::RED);
|
||||
gizmos.ray_2d(Vec2::Y * sin, Vec2::splat(80.), Color::GREEN);
|
||||
|
||||
// Triangle
|
||||
gizmos.linestrip_gradient_2d([
|
||||
(Vec2::Y * 300., Color::BLUE),
|
||||
(Vec2::new(-255., -155.), Color::RED),
|
||||
(Vec2::new(255., -155.), Color::GREEN),
|
||||
(Vec2::Y * 300., Color::BLUE),
|
||||
]);
|
||||
|
||||
gizmos.rect_2d(
|
||||
Vec2::ZERO,
|
||||
time.elapsed_seconds() / 3.,
|
||||
Vec2::splat(300.),
|
||||
Color::BLACK,
|
||||
);
|
||||
|
||||
// The circles have 32 line-segments by default.
|
||||
gizmos.circle_2d(Vec2::ZERO, 120., Color::BLACK);
|
||||
// You may want to increase this for larger circles.
|
||||
gizmos.circle_2d(Vec2::ZERO, 300., Color::NAVY).segments(64);
|
||||
}
|
108
examples/3d/3d_gizmos.rs
Normal file
108
examples/3d/3d_gizmos.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (system, rotate_camera, update_config))
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
// plane
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Plane::from_size(5.0))),
|
||||
material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
|
||||
..default()
|
||||
});
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
|
||||
material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 1500.0,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
});
|
||||
// text
|
||||
commands.spawn(TextBundle::from_section(
|
||||
"Press 't' to toggle drawing gizmos on top of everything else in the scene",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 24.,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn system(mut gizmos: Gizmos, time: Res<Time>) {
|
||||
gizmos.cuboid(
|
||||
Vec3::Y * -0.5,
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(5., 1., 2.),
|
||||
Color::BLACK,
|
||||
);
|
||||
gizmos.rect(
|
||||
Vec3::new(time.elapsed_seconds().cos() * 2.5, 1., 0.),
|
||||
Quat::from_rotation_y(PI / 2.),
|
||||
Vec2::splat(2.),
|
||||
Color::GREEN,
|
||||
);
|
||||
|
||||
gizmos.sphere(
|
||||
Vec3::new(1., 0.5, 0.),
|
||||
Quat::IDENTITY,
|
||||
0.5,
|
||||
Color::RED.with_a(0.5),
|
||||
);
|
||||
|
||||
for y in [0., 0.5, 1.] {
|
||||
gizmos.ray(
|
||||
Vec3::new(1., y, 0.),
|
||||
Vec3::new(-3., (time.elapsed_seconds() * 3.).sin(), 0.),
|
||||
Color::BLUE,
|
||||
);
|
||||
}
|
||||
|
||||
// Circles have 32 line-segments by default.
|
||||
gizmos.circle(Vec3::ZERO, Vec3::Y, 3., Color::BLACK);
|
||||
// You may want to increase this for larger circles or spheres.
|
||||
gizmos
|
||||
.circle(Vec3::ZERO, Vec3::Y, 3.1, Color::NAVY)
|
||||
.segments(64);
|
||||
gizmos
|
||||
.sphere(Vec3::ZERO, Quat::IDENTITY, 3.2, Color::BLACK)
|
||||
.circle_segments(64);
|
||||
}
|
||||
|
||||
fn rotate_camera(mut query: Query<&mut Transform, With<Camera>>, time: Res<Time>) {
|
||||
let mut transform = query.single_mut();
|
||||
|
||||
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(time.delta_seconds() / 2.));
|
||||
}
|
||||
|
||||
fn update_config(mut gizmo_config: ResMut<GizmoConfig>, keyboard: Res<Input<KeyCode>>) {
|
||||
if keyboard.just_pressed(KeyCode::T) {
|
||||
gizmo_config.on_top = !gizmo_config.on_top;
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@ Example | Description
|
|||
Example | Description
|
||||
--- | ---
|
||||
[2D Bloom](../examples/2d/bloom_2d.rs) | Illustrates bloom post-processing in 2d
|
||||
[2D Gizmos](../examples/2d/2d_gizmos.rs) | A scene showcasing 2D gizmos
|
||||
[2D Rotation](../examples/2d/rotation.rs) | Demonstrates rotating entities in 2D with quaternions
|
||||
[2D Shapes](../examples/2d/2d_shapes.rs) | Renders a rectangle, circle, and hexagon
|
||||
[Manual Mesh 2D](../examples/2d/mesh2d_manual.rs) | Renders a custom mesh "manually" with "mid-level" renderer apis
|
||||
|
@ -107,6 +108,7 @@ Example | Description
|
|||
Example | Description
|
||||
--- | ---
|
||||
[3D Bloom](../examples/3d/bloom_3d.rs) | Illustrates bloom configuration using HDR and emissive materials
|
||||
[3D Gizmos](../examples/3d/3d_gizmos.rs) | A scene showcasing 3D gizmos
|
||||
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
|
||||
[3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes
|
||||
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
|
||||
|
@ -300,6 +302,7 @@ Example | Description
|
|||
[Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements
|
||||
[Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling
|
||||
[Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000
|
||||
[Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos
|
||||
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
|
||||
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
|
||||
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
|
||||
|
|
109
examples/stress_tests/many_gizmos.rs
Normal file
109
examples/stress_tests/many_gizmos.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use std::f32::consts::TAU;
|
||||
|
||||
use bevy::{
|
||||
diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin},
|
||||
prelude::*,
|
||||
window::PresentMode,
|
||||
};
|
||||
|
||||
const SYSTEM_COUNT: u32 = 10;
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Many Debug Lines".to_string(),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_plugin(FrameTimeDiagnosticsPlugin::default())
|
||||
.insert_resource(Config {
|
||||
line_count: 50_000,
|
||||
fancy: false,
|
||||
})
|
||||
.insert_resource(GizmoConfig {
|
||||
on_top: false,
|
||||
..default()
|
||||
})
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (input, ui_system));
|
||||
|
||||
for _ in 0..SYSTEM_COUNT {
|
||||
app.add_systems(Update, system);
|
||||
}
|
||||
|
||||
app.run();
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug)]
|
||||
struct Config {
|
||||
line_count: u32,
|
||||
fancy: bool,
|
||||
}
|
||||
|
||||
fn input(mut config: ResMut<Config>, input: Res<Input<KeyCode>>) {
|
||||
if input.just_pressed(KeyCode::Up) {
|
||||
config.line_count += 10_000;
|
||||
}
|
||||
if input.just_pressed(KeyCode::Down) {
|
||||
config.line_count = config.line_count.saturating_sub(10_000);
|
||||
}
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
config.fancy = !config.fancy;
|
||||
}
|
||||
}
|
||||
|
||||
fn system(config: Res<Config>, time: Res<Time>, mut draw: Gizmos) {
|
||||
if !config.fancy {
|
||||
for _ in 0..(config.line_count / SYSTEM_COUNT) {
|
||||
draw.line(Vec3::NEG_Y, Vec3::Y, Color::BLACK);
|
||||
}
|
||||
} else {
|
||||
for i in 0..(config.line_count / SYSTEM_COUNT) {
|
||||
let angle = i as f32 / (config.line_count / SYSTEM_COUNT) as f32 * TAU;
|
||||
|
||||
let vector = Vec2::from(angle.sin_cos()).extend(time.elapsed_seconds().sin());
|
||||
let start_color = Color::rgb(vector.x, vector.z, 0.5);
|
||||
let end_color = Color::rgb(-vector.z, -vector.y, 0.5);
|
||||
|
||||
draw.line_gradient(vector, -vector, start_color, end_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
warn!(include_str!("warning_string.txt"));
|
||||
|
||||
commands.spawn(Camera3dBundle {
|
||||
transform: Transform::from_xyz(3., 1., 5.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn(TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
font_size: 30.,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn ui_system(mut query: Query<&mut Text>, config: Res<Config>, diag: Res<Diagnostics>) {
|
||||
let mut text = query.single_mut();
|
||||
|
||||
let Some(fps) = diag.get(FrameTimeDiagnosticsPlugin::FPS).and_then(|fps| fps.smoothed()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
text.sections[0].value = format!(
|
||||
"Line count: {}\n\
|
||||
FPS: {:.0}\n\n\
|
||||
Controls:\n\
|
||||
Up/Down: Raise or lower the line count.\n\
|
||||
Spacebar: Toggle fancy mode.",
|
||||
config.line_count, fps,
|
||||
);
|
||||
}
|
|
@ -34,6 +34,7 @@ crates=(
|
|||
bevy_gltf
|
||||
bevy_scene
|
||||
bevy_sprite
|
||||
bevy_gizmos
|
||||
bevy_text
|
||||
bevy_a11y
|
||||
bevy_ui
|
||||
|
|
Loading…
Reference in a new issue