Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

View File

@@ -0,0 +1 @@
{"files":{"Cargo.lock":"ef1d7aaf8936c30b92b20d571c10f9b9935b0c57751375a29868c99b7d632c2f","Cargo.toml":"2106e01c9a2cd32263f9a28f3244ca4f81c9add155010e26371a4ab66e6ea67b","LICENSE-APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE-MIT":"508a77d2e7b51d98adeed32648ad124b7b30241a8e70b2e72c99f92d8e5874d1","README.md":"4bdd181a5fda15cc9501aa7659cb1096c0e459640523f189ef83ba1d6369e752","src/aabb.rs":"87319eb1a6aa2dd4ad3097eddcd8fa5c635645eaf21876210eef0dcf1f03cca9","src/arcs.rs":"0dc61c3a645ddaf6baf10ff7a8b50f9304ddc5e75b14a8c21fc745c796dee09f","src/arrows.rs":"b28ee09af2cbde6e7c4990e51fa7677b6e6f618389c93d880d9a31b28795a96a","src/circles.rs":"bc0ff51ed79323c7b43fca197adf451accb1530b8fe13b29c13c76b22c710c19","src/config.rs":"c684217b31bb43b7dec6cce7c1df76bae4a5ee76a0cbcbefb551b22808707f95","src/cross.rs":"79d55b8e08566e6a4b31b5f0bb5587dacdd202de36a1e07ae1793384e9df58f9","src/curves.rs":"2a6f8490c101df8876af714d5088cfea394ab80d64b041a4eb87de8260406da6","src/gizmos.rs":"8e2f76783e3a2fbc10911ae3ccd2380a13ce9d1d8e03b191273cb5f665ece289","src/grid.rs":"99c3d7dd3b70ca5f86955d950b08a54c087e8512748556331706e267d411f930","src/lib.rs":"43ddd8fedbb23b7f0cdd3a62e4cbcc8e27c5feaf42bd7861b5f373c7fd3dc6f9","src/light.rs":"e41068fa08978322a57c8ced67d2086c182b2bd560c7dd0b6183de9ecec63709","src/line_joints.wgsl":"15995fcc3a669bb89ff3d9ffa7442d4f3c4297447e3ac397191f6174c23ca1ec","src/lines.wgsl":"fca078c60d7a93595f2b591977c76cde16f1cebec8353fb98fadadb84fe21640","src/pipeline_2d.rs":"b3e66ba220e7f9a02e6e87ea5e6c0ad77cd6c49c4f6188d79c791e4bff86aa83","src/pipeline_3d.rs":"70d9e1a08c348747721658e736be3048ff9eea625f802de26052c2590d721ec8","src/primitives/dim2.rs":"7ac85f613a36c0c5e7798adfc5b889d6ba87d38e174c7ad34d225d6e792f8256","src/primitives/dim3.rs":"c85ff76ec0c2c455d892a8cf6bfd02b703be54bdeef93962b41f505da64dcef8","src/primitives/helpers.rs":"afbdfec8df09a50a2d565ce7f6882890848dbb532552c33f55c652e7a64dd26a","src/primitives/mod.rs":"dca3f74921c2b7e506e33911862cdf383621e650e90d1fa9353a32cd83707815","src/retained.rs":"d5c91d3c32982ad55d8824acd4cc0741ae2e9123d023a141d1a32cc4fc0e92fd","src/rounded_box.rs":"bb3285e8759eecf952d00acc906048350aea8071734b80326d5897169c024118"},"package":"7823154a9682128c261d8bddb3a4d7192a188490075c527af04520c2f0f8aad6"}

3237
vendor/bevy_gizmos/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

140
vendor/bevy_gizmos/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,140 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "bevy_gizmos"
version = "0.16.1"
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Provides gizmos for Bevy Engine"
homepage = "https://bevyengine.org"
readme = "README.md"
keywords = ["bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"-Zunstable-options",
"--generate-link-to-definition",
]
[features]
bevy_render = [
"dep:bevy_render",
"bevy_core_pipeline",
]
webgl = []
webgpu = []
[lib]
name = "bevy_gizmos"
path = "src/lib.rs"
[dependencies.bevy_app]
version = "0.16.1"
[dependencies.bevy_asset]
version = "0.16.1"
[dependencies.bevy_color]
version = "0.16.2"
[dependencies.bevy_core_pipeline]
version = "0.16.1"
optional = true
[dependencies.bevy_ecs]
version = "0.16.1"
[dependencies.bevy_gizmos_macros]
version = "0.16.1"
[dependencies.bevy_image]
version = "0.16.1"
[dependencies.bevy_math]
version = "0.16.1"
[dependencies.bevy_pbr]
version = "0.16.1"
optional = true
[dependencies.bevy_reflect]
version = "0.16.1"
[dependencies.bevy_render]
version = "0.16.1"
optional = true
[dependencies.bevy_sprite]
version = "0.16.1"
optional = true
[dependencies.bevy_time]
version = "0.16.1"
[dependencies.bevy_transform]
version = "0.16.1"
[dependencies.bevy_utils]
version = "0.16.1"
[dependencies.bytemuck]
version = "1.0"
[dependencies.tracing]
version = "0.1"
features = ["std"]
default-features = false
[lints.clippy]
alloc_instead_of_core = "warn"
allow_attributes = "warn"
allow_attributes_without_reason = "warn"
doc_markdown = "warn"
manual_let_else = "warn"
match_same_arms = "warn"
needless_lifetimes = "allow"
nonstandard_macro_braces = "warn"
print_stderr = "warn"
print_stdout = "warn"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
redundant_closure_for_method_calls = "warn"
redundant_else = "warn"
ref_as_ptr = "warn"
semicolon_if_nothing_returned = "warn"
std_instead_of_alloc = "warn"
std_instead_of_core = "warn"
too_long_first_doc_paragraph = "allow"
too_many_arguments = "allow"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
[lints.rust]
missing_docs = "warn"
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
unused_qualifications = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = ["cfg(docsrs_dep)"]

176
vendor/bevy_gizmos/LICENSE-APACHE vendored Normal file
View File

@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

19
vendor/bevy_gizmos/LICENSE-MIT vendored Normal file
View File

@@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/bevy_gizmos/README.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Bevy Gizmos
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy_gizmos.svg)](https://crates.io/crates/bevy_gizmos)
[![Downloads](https://img.shields.io/crates/d/bevy_gizmos.svg)](https://crates.io/crates/bevy_gizmos)
[![Docs](https://docs.rs/bevy_gizmos/badge.svg)](https://docs.rs/bevy_gizmos/latest/bevy_gizmos/)
[![Discord](https://img.shields.io/discord/691052431525675048.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/bevy)

110
vendor/bevy_gizmos/src/aabb.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
//! A module adding debug visualization of [`Aabb`]s.
use bevy_app::{Plugin, PostUpdate};
use bevy_color::{Color, Oklcha};
use bevy_ecs::{
component::Component,
entity::Entity,
query::Without,
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
system::{Query, Res},
};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::primitives::Aabb;
use bevy_transform::{
components::{GlobalTransform, Transform},
TransformSystem,
};
use crate::{
config::{GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
AppGizmoBuilder,
};
/// A [`Plugin`] that provides visualization of [`Aabb`]s for debugging.
pub struct AabbGizmoPlugin;
impl Plugin for AabbGizmoPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.register_type::<AabbGizmoConfigGroup>()
.init_gizmo_group::<AabbGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_aabbs,
draw_all_aabbs.run_if(|config: Res<GizmoConfigStore>| {
config.config::<AabbGizmoConfigGroup>().1.draw_all
}),
)
.after(bevy_render::view::VisibilitySystems::CalculateBounds)
.after(TransformSystem::TransformPropagate),
);
}
}
/// The [`GizmoConfigGroup`] used for debug visualizations of [`Aabb`] components on entities
#[derive(Clone, Default, Reflect, GizmoConfigGroup)]
#[reflect(Clone, Default)]
pub struct AabbGizmoConfigGroup {
/// Draws all bounding boxes in the scene when set to `true`.
///
/// To draw a specific entity's bounding box, you can add the [`ShowAabbGizmo`] component.
///
/// Defaults to `false`.
pub draw_all: bool,
/// The default color for bounding box gizmos.
///
/// A random color is chosen per box if `None`.
///
/// Defaults to `None`.
pub default_color: Option<Color>,
}
/// Add this [`Component`] to an entity to draw its [`Aabb`] component.
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component, Default, Debug)]
pub struct ShowAabbGizmo {
/// The color of the box.
///
/// The default color from the [`AabbGizmoConfigGroup`] config is used if `None`,
pub color: Option<Color>,
}
fn draw_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform, &ShowAabbGizmo)>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
for (entity, &aabb, &transform, gizmo) in &query {
let color = gizmo
.color
.or(gizmos.config_ext.default_color)
.unwrap_or_else(|| color_from_entity(entity));
gizmos.cuboid(aabb_transform(aabb, transform), color);
}
}
fn draw_all_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform), Without<ShowAabbGizmo>>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
for (entity, &aabb, &transform) in &query {
let color = gizmos
.config_ext
.default_color
.unwrap_or_else(|| color_from_entity(entity));
gizmos.cuboid(aabb_transform(aabb, transform), color);
}
}
fn color_from_entity(entity: Entity) -> Color {
Oklcha::sequential_dispersed(entity.index()).into()
}
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
transform
* GlobalTransform::from(
Transform::from_translation(aabb.center.into())
.with_scale((aabb.half_extents * 2.).into()),
)
}

529
vendor/bevy_gizmos/src/arcs.rs vendored Normal file
View File

@@ -0,0 +1,529 @@
//! Additional [`GizmoBuffer`] Functions -- Arcs
//!
//! Includes the implementation of [`GizmoBuffer::arc_2d`],
//! and assorted support items.
use crate::{circles::DEFAULT_CIRCLE_RESOLUTION, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Quat, Rot2, Vec2, Vec3};
use core::f32::consts::{FRAC_PI_2, TAU};
// === 2D ===
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arc, which is a part of the circumference of a circle, in 2D.
///
/// This should be called for each frame the arc needs to be rendered.
///
/// # Arguments
/// - `isometry` defines the translation and rotation of the arc.
/// - the translation specifies the center of the arc
/// - the rotation is counter-clockwise starting from `Vec2::Y`
/// - `arc_angle` sets the length of this arc, in radians.
/// - `radius` controls the distance from `position` to this arc, and thus its curvature.
/// - `color` sets the color to draw the arc.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use std::f32::consts::FRAC_PI_4;
/// # use bevy_color::palettes::basic::{GREEN, RED};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);
///
/// // Arcs have 32 line-segments by default.
/// // You may want to increase this for larger arcs.
/// gizmos
/// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn arc_2d(
&mut self,
isometry: impl Into<Isometry2d>,
arc_angle: f32,
radius: f32,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
Arc2dBuilder {
gizmos: self,
isometry: isometry.into(),
arc_angle,
radius,
color: color.into(),
resolution: None,
}
}
}
/// A builder returned by [`GizmoBuffer::arc_2d`].
pub struct Arc2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
arc_angle: f32,
radius: f32,
color: Color,
resolution: Option<u32>,
}
impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the geometry of this arc.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution.replace(resolution);
self
}
}
impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let resolution = self
.resolution
.unwrap_or_else(|| resolution_from_angle(self.arc_angle));
let positions =
arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);
self.gizmos.linestrip_2d(positions, self.color);
}
}
fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..=resolution)
.map(move |n| arc_angle * n as f32 / resolution as f32)
.map(|angle| angle + FRAC_PI_2)
.map(Vec2::from_angle)
.map(move |vec2| vec2 * radius)
}
// === 3D ===
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
/// this is drawing a standard arc. A standard arc is defined as
///
/// - an arc with a center at `Vec3::ZERO`
/// - starting at `Vec3::X`
/// - embedded in the XZ plane
/// - rotates counterclockwise
///
/// This should be called for each frame the arc needs to be rendered.
///
/// # Arguments
/// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
/// value should be in the range (-2 * PI..=2 * PI)
/// - `radius`: distance between the arc and its center point
/// - `isometry` defines the translation and rotation of the arc.
/// - the translation specifies the center of the arc
/// - the rotation is counter-clockwise starting from `Vec3::Y`
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use std::f32::consts::PI;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
/// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
///
/// gizmos
/// .arc_3d(
/// 270.0_f32.to_radians(),
/// 0.25,
/// Isometry3d::new(Vec3::ONE, rotation),
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn arc_3d(
&mut self,
angle: f32,
radius: f32,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
Arc3dBuilder {
gizmos: self,
start_vertex: Vec3::X,
isometry: isometry.into(),
angle,
radius,
color: color.into(),
resolution: None,
}
}
/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
///
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.short_arc_3d_between(
/// Vec3::ONE,
/// Vec3::ONE + Vec3::NEG_ONE,
/// Vec3::ZERO,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
#[inline]
pub fn short_arc_3d_between(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
self.arc_from_to(center, from, to, color, |x| x)
}
/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.long_arc_3d_between(
/// Vec3::ONE,
/// Vec3::ONE + Vec3::NEG_ONE,
/// Vec3::ZERO,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
#[inline]
pub fn long_arc_3d_between(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
self.arc_from_to(center, from, to, color, |angle| {
if angle > 0.0 {
TAU - angle
} else if angle < 0.0 {
-TAU - angle
} else {
0.0
}
})
}
#[inline]
fn arc_from_to(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
angle_fn: impl Fn(f32) -> f32,
) -> Arc3dBuilder<'_, Config, Clear> {
// `from` and `to` can be the same here since in either case nothing gets rendered and the
// orientation ambiguity of `up` doesn't matter
let from_axis = (from - center).normalize_or_zero();
let to_axis = (to - center).normalize_or_zero();
let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();
let angle = angle_fn(angle);
let radius = center.distance(from);
let rotation = Quat::from_rotation_arc(Vec3::Y, up);
let start_vertex = rotation.inverse() * from_axis;
Arc3dBuilder {
gizmos: self,
start_vertex,
isometry: Isometry3d::new(center, rotation),
angle,
radius,
color: color.into(),
resolution: None,
}
}
/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
///
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.short_arc_2d_between(
/// Vec2::ZERO,
/// Vec2::X,
/// Vec2::Y,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
#[inline]
pub fn short_arc_2d_between(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
self.arc_2d_from_to(center, from, to, color, core::convert::identity)
}
/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.long_arc_2d_between(
/// Vec2::ZERO,
/// Vec2::X,
/// Vec2::Y,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
#[inline]
pub fn long_arc_2d_between(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)
}
#[inline]
fn arc_2d_from_to(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
angle_fn: impl Fn(f32) -> f32,
) -> Arc2dBuilder<'_, Config, Clear> {
// `from` and `to` can be the same here since in either case nothing gets rendered and the
// orientation ambiguity of `up` doesn't matter
let from_axis = (from - center).normalize_or_zero();
let to_axis = (to - center).normalize_or_zero();
let rotation = Vec2::Y.angle_to(from_axis);
let arc_angle_raw = from_axis.angle_to(to_axis);
let arc_angle = angle_fn(arc_angle_raw);
let radius = center.distance(from);
Arc2dBuilder {
gizmos: self,
isometry: Isometry2d::new(center, Rot2::radians(rotation)),
arc_angle,
radius,
color: color.into(),
resolution: None,
}
}
}
/// A builder returned by [`GizmoBuffer::arc_2d`].
pub struct Arc3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
// always starting at Vec3::X. For the short/long arc methods we actually need a way to start
// at the from position and this is where this internal field comes into play. Some implicit
// assumptions:
//
// 1. This is always in the XZ plane
// 2. This is always normalized
//
// DO NOT expose this field to users as it is easy to mess this up
start_vertex: Vec3,
isometry: Isometry3d,
angle: f32,
radius: f32,
color: Color,
resolution: Option<u32>,
}
impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines for this arc.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution.replace(resolution);
self
}
}
impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let resolution = self
.resolution
.unwrap_or_else(|| resolution_from_angle(self.angle));
let positions = arc_3d_inner(
self.start_vertex,
self.isometry,
self.angle,
self.radius,
resolution,
);
self.gizmos.linestrip(positions, self.color);
}
}
fn arc_3d_inner(
start_vertex: Vec3,
isometry: Isometry3d,
angle: f32,
radius: f32,
resolution: u32,
) -> impl Iterator<Item = Vec3> {
// drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
// we won't see the overlap and we would just decrease the level of details since the resolution
// would be larger
let angle = angle.clamp(-TAU, TAU);
(0..=resolution)
.map(move |frac| frac as f32 / resolution as f32)
.map(move |percentage| angle * percentage)
.map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex)
.map(move |vec3| vec3 * radius)
.map(move |vec3| isometry * vec3)
}
// helper function for getting a default value for the resolution parameter
fn resolution_from_angle(angle: f32) -> u32 {
((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32
}

232
vendor/bevy_gizmos/src/arrows.rs vendored Normal file
View File

@@ -0,0 +1,232 @@
//! Additional [`GizmoBuffer`] Functions -- Arrows
//!
//! Includes the implementation of [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::{
palettes::basic::{BLUE, GREEN, RED},
Color,
};
use bevy_math::{Quat, Vec2, Vec3, Vec3Swizzles};
use bevy_transform::TransformPoint;
/// A builder returned by [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`]
pub struct ArrowBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
start: Vec3,
end: Vec3,
color: Color,
double_ended: bool,
tip_length: f32,
}
impl<Config, Clear> ArrowBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the length of the tips to be `length`.
/// The default tip length is [length of the arrow]/10.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN)
/// .with_tip_length(3.);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[doc(alias = "arrow_head_length")]
pub fn with_tip_length(mut self, length: f32) -> Self {
self.tip_length = length;
self
}
/// Adds another tip to the arrow, appended in the start point.
/// the default is only one tip at the end point.
pub fn with_double_end(mut self) -> Self {
self.double_ended = true;
self
}
}
impl<Config, Clear> Drop for ArrowBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draws the arrow, by drawing lines with the stored [`GizmoBuffer`]
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// first, draw the body of the arrow
self.gizmos.line(self.start, self.end, self.color);
// now the hard part is to draw the head in a sensible way
// put us in a coordinate system where the arrow is pointing towards +x and ends at the origin
let pointing_end = (self.end - self.start).normalize();
let rotation_end = Quat::from_rotation_arc(Vec3::X, pointing_end);
let tips = [
Vec3::new(-1., 1., 0.),
Vec3::new(-1., 0., 1.),
Vec3::new(-1., -1., 0.),
Vec3::new(-1., 0., -1.),
];
// - extend the vectors so their length is `tip_length`
// - rotate the world so +x is facing in the same direction as the arrow
// - translate over to the tip of the arrow
let tips_end = tips.map(|v| rotation_end * (v.normalize() * self.tip_length) + self.end);
for v in tips_end {
// then actually draw the tips
self.gizmos.line(self.end, v, self.color);
}
if self.double_ended {
let pointing_start = (self.start - self.end).normalize();
let rotation_start = Quat::from_rotation_arc(Vec3::X, pointing_start);
let tips_start =
tips.map(|v| rotation_start * (v.normalize() * self.tip_length) + self.start);
for v in tips_start {
// draw the start points tips
self.gizmos.line(self.start, v, self.color);
}
}
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convenient viewing from any direction.
///
/// This should be called for each frame the arrow needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn arrow(
&mut self,
start: Vec3,
end: Vec3,
color: impl Into<Color>,
) -> ArrowBuilder<'_, Config, Clear> {
let length = (end - start).length();
ArrowBuilder {
gizmos: self,
start,
end,
color: color.into(),
double_ended: false,
tip_length: length / 10.,
}
}
/// Draw an arrow in 2D (on the xy plane), from `start` to `end`.
///
/// This should be called for each frame the arrow needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow_2d(Vec2::ZERO, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn arrow_2d(
&mut self,
start: Vec2,
end: Vec2,
color: impl Into<Color>,
) -> ArrowBuilder<'_, Config, Clear> {
self.arrow(start.extend(0.), end.extend(0.), color)
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
/// of `base_length`.
///
/// This should be called for each frame the axes need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_transform::components::Transform;
/// # #[derive(Component)]
/// # struct MyComponent;
/// fn draw_axes(
/// mut gizmos: Gizmos,
/// query: Query<&Transform, With<MyComponent>>,
/// ) {
/// for &transform in &query {
/// gizmos.axes(transform, 1.);
/// }
/// }
/// # bevy_ecs::system::assert_is_system(draw_axes);
/// ```
pub fn axes(&mut self, transform: impl TransformPoint, base_length: f32) {
let start = transform.transform_point(Vec3::ZERO);
let end_x = transform.transform_point(base_length * Vec3::X);
let end_y = transform.transform_point(base_length * Vec3::Y);
let end_z = transform.transform_point(base_length * Vec3::Z);
self.arrow(start, end_x, RED);
self.arrow(start, end_y, GREEN);
self.arrow(start, end_z, BLUE);
}
/// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
/// of `base_length`.
///
/// This should be called for each frame the axes need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_transform::components::Transform;
/// # #[derive(Component)]
/// # struct AxesComponent;
/// fn draw_axes_2d(
/// mut gizmos: Gizmos,
/// query: Query<&Transform, With<AxesComponent>>,
/// ) {
/// for &transform in &query {
/// gizmos.axes_2d(transform, 1.);
/// }
/// }
/// # bevy_ecs::system::assert_is_system(draw_axes_2d);
/// ```
pub fn axes_2d(&mut self, transform: impl TransformPoint, base_length: f32) {
let start = transform.transform_point(Vec3::ZERO);
let end_x = transform.transform_point(base_length * Vec3::X);
let end_y = transform.transform_point(base_length * Vec3::Y);
self.arrow_2d(start.xy(), end_x.xy(), RED);
self.arrow_2d(start.xy(), end_y.xy(), GREEN);
}
}

363
vendor/bevy_gizmos/src/circles.rs vendored Normal file
View File

@@ -0,0 +1,363 @@
//! Additional [`GizmoBuffer`] Functions -- Circles
//!
//! Includes the implementation of [`GizmoBuffer::circle`] and [`GizmoBuffer::circle_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{ops, Isometry2d, Isometry3d, Quat, Vec2, Vec3};
use core::f32::consts::TAU;
pub(crate) const DEFAULT_CIRCLE_RESOLUTION: u32 = 32;
fn ellipse_inner(half_size: Vec2, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..resolution + 1).map(move |i| {
let angle = i as f32 * TAU / resolution as f32;
let (x, y) = ops::sin_cos(angle);
Vec2::new(x, y) * half_size
})
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an ellipse in 3D with the given `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the `half_sizes` are aligned with the `Vec3::X` and `Vec3::Y` axes.
///
/// This should be called for each frame the ellipse needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ellipse(Isometry3d::IDENTITY, Vec2::new(1., 2.), GREEN);
///
/// // Ellipses have 32 line-segments by default.
/// // You may want to increase this for larger ellipses.
/// gizmos
/// .ellipse(Isometry3d::IDENTITY, Vec2::new(5., 1.), RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse(
&mut self,
isometry: impl Into<Isometry3d>,
half_size: Vec2,
color: impl Into<Color>,
) -> EllipseBuilder<'_, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// Draw an ellipse in 2D with the given `isometry` applied.
///
/// If `isometry == Isometry2d::IDENTITY` then
///
/// - the center is at `Vec2::ZERO`
/// - the `half_sizes` are aligned with the `Vec2::X` and `Vec2::Y` axes.
///
/// This should be called for each frame the ellipse needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(2., 1.), GREEN);
///
/// // Ellipses have 32 line-segments by default.
/// // You may want to increase this for larger ellipses.
/// gizmos
/// .ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(5., 1.), RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse_2d(
&mut self,
isometry: impl Into<Isometry2d>,
half_size: Vec2,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// Draw a circle in 3D with the given `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the radius is aligned with the `Vec3::X` and `Vec3::Y` axes.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.circle(Isometry3d::IDENTITY, 1., GREEN);
///
/// // Circles have 32 line-segments by default.
/// // You may want to increase this for larger circles.
/// gizmos
/// .circle(Isometry3d::IDENTITY, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle(
&mut self,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> EllipseBuilder<'_, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry: isometry.into(),
half_size: Vec2::splat(radius),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// Draw a circle in 2D with the given `isometry` applied.
///
/// If `isometry == Isometry2d::IDENTITY` then
///
/// - the center is at `Vec2::ZERO`
/// - the radius is aligned with the `Vec2::X` and `Vec2::Y` axes.
///
/// This should be called for each frame the circle needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.circle_2d(Isometry2d::IDENTITY, 1., GREEN);
///
/// // Circles have 32 line-segments by default.
/// // You may want to increase this for larger circles.
/// gizmos
/// .circle_2d(Isometry2d::IDENTITY, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle_2d(
&mut self,
isometry: impl Into<Isometry2d>,
radius: f32,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry: isometry.into(),
half_size: Vec2::splat(radius),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// Draw a wireframe sphere in 3D made out of 3 circles around the axes with the given
/// `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the 3 circles are in the XY, YZ and XZ planes.
///
/// This should be called for each frame the sphere needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::Color;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.sphere(Isometry3d::IDENTITY, 1., Color::BLACK);
///
/// // Each circle has 32 line-segments by default.
/// // You may want to increase this for larger spheres.
/// gizmos
/// .sphere(Isometry3d::IDENTITY, 5., Color::BLACK)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn sphere(
&mut self,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> SphereBuilder<'_, Config, Clear> {
SphereBuilder {
gizmos: self,
radius,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
}
/// A builder returned by [`GizmoBuffer::ellipse`].
pub struct EllipseBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
half_size: Vec2,
color: Color,
resolution: u32,
}
impl<Config, Clear> EllipseBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the geometry of this ellipse.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for EllipseBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let positions = ellipse_inner(self.half_size, self.resolution)
.map(|vec2| self.isometry * vec2.extend(0.));
self.gizmos.linestrip(positions, self.color);
}
}
/// A builder returned by [`GizmoBuffer::ellipse_2d`].
pub struct Ellipse2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
half_size: Vec2,
color: Color,
resolution: u32,
}
impl<Config, Clear> Ellipse2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments used to approximate the geometry of this ellipse.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for Ellipse2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments for this ellipse.
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
};
let positions =
ellipse_inner(self.half_size, self.resolution).map(|vec2| self.isometry * vec2);
self.gizmos.linestrip_2d(positions, self.color);
}
}
/// A builder returned by [`GizmoBuffer::sphere`].
pub struct SphereBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the sphere
radius: f32,
isometry: Isometry3d,
// Color of the sphere
color: Color,
// Number of line-segments used to approximate the sphere geometry
resolution: u32,
}
impl<Config, Clear> SphereBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments used to approximate the sphere geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for SphereBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// draws one great circle around each of the local axes
Vec3::AXES.into_iter().for_each(|axis| {
let axis_rotation = Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, axis));
self.gizmos
.circle(self.isometry * axis_rotation, self.radius, self.color)
.resolution(self.resolution);
});
}
}

258
vendor/bevy_gizmos/src/config.rs vendored Normal file
View File

@@ -0,0 +1,258 @@
//! A module for the [`GizmoConfig<T>`] [`Resource`].
pub use bevy_gizmos_macros::GizmoConfigGroup;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component};
use bevy_ecs::{reflect::ReflectResource, resource::Resource};
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_utils::TypeIdMap;
use core::{
any::TypeId,
hash::Hash,
ops::{Deref, DerefMut},
panic,
};
/// An enum configuring how line joints will be drawn.
#[derive(Debug, Default, Copy, Clone, Reflect, PartialEq, Eq, Hash)]
#[reflect(Default, PartialEq, Hash, Clone)]
pub enum GizmoLineJoint {
/// Does not draw any line joints.
#[default]
None,
/// Extends both lines at the joining point until they meet in a sharp point.
Miter,
/// Draws a round corner with the specified resolution between the two lines.
///
/// The resolution determines the amount of triangles drawn per joint,
/// e.g. `GizmoLineJoint::Round(4)` will draw 4 triangles at each line joint.
Round(u32),
/// Draws a bevel, a straight line in this case, to connect the ends of both lines.
Bevel,
}
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
#[reflect(Default, PartialEq, Hash, Clone)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
#[default]
Solid,
/// A dotted line
Dotted,
/// A dashed line with configurable gap and line sizes
Dashed {
/// The length of the gap in `line_width`s
gap_scale: f32,
/// The length of the visible line in `line_width`s
line_scale: f32,
},
}
impl Eq for GizmoLineStyle {}
impl Hash for GizmoLineStyle {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Solid => {
0u64.hash(state);
}
Self::Dotted => 1u64.hash(state),
Self::Dashed {
gap_scale,
line_scale,
} => {
2u64.hash(state);
gap_scale.to_bits().hash(state);
line_scale.to_bits().hash(state);
}
}
}
}
/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
///
/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::<T>()`
pub trait GizmoConfigGroup: Reflect + TypePath + Default {}
/// The default gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup)]
#[reflect(Default)]
pub struct DefaultGizmoConfigGroup;
/// Used when the gizmo config group needs to be type-erased.
/// Also used for retained gizmos, which can't have a gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup, Debug, Clone)]
#[reflect(Default, Clone)]
pub struct ErasedGizmoConfigGroup;
/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs
///
/// Use `app.init_gizmo_group::<T>()` to register a custom config group.
#[derive(Reflect, Resource, Default)]
#[reflect(Resource, Default)]
pub struct GizmoConfigStore {
// INVARIANT: must map TypeId::of::<T>() to correct type T
#[reflect(ignore)]
store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,
}
impl GizmoConfigStore {
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> {
let (config, ext) = self.store.get(config_type_id)?;
Some((config, ext.deref()))
}
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any().downcast_ref().unwrap();
(config, ext)
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_mut_dyn(
&mut self,
config_type_id: &TypeId,
) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {
let (config, ext) = self.store.get_mut(config_type_id)?;
Some((config, ext.deref_mut()))
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any_mut().downcast_mut().unwrap();
(config, ext)
}
/// Returns an iterator over all [`GizmoConfig`]s.
pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
self.store
.iter()
.map(|(id, (config, ext))| (id, config, ext.deref()))
}
/// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.
pub fn iter_mut(
&mut self,
) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
self.store
.iter_mut()
.map(|(id, (config, ext))| (id, config, ext.deref_mut()))
}
/// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
// INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
self.store
.insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
}
pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {
self.insert(GizmoConfig::default(), T::default());
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
#[reflect(Clone, Default)]
pub struct GizmoConfig {
/// Set to `false` to stop drawing gizmos.
///
/// Defaults to `true`.
pub enabled: bool,
/// Line settings.
pub line: GizmoLineConfig,
/// How closer to the camera than real geometry the gizmos should be.
///
/// In 2D this setting has no effect and is effectively always -1.
///
/// Value between -1 and 1 (inclusive).
/// * 0 means that there is no change to the line position when rendering
/// * 1 means it is furthest away from camera as possible
/// * -1 means that it will always render in front of other things.
///
/// This is typically useful if you are drawing wireframes on top of polygons
/// and your wireframe is z-fighting (flickering on/off) with your main model.
/// You would set this value to a negative number close to 0.
pub depth_bias: f32,
/// Describes which rendering layers gizmos will be rendered to.
///
/// Gizmos will only be rendered to cameras with intersecting layers.
#[cfg(feature = "bevy_render")]
pub render_layers: bevy_render::view::RenderLayers,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
enabled: true,
line: Default::default(),
depth_bias: 0.,
#[cfg(feature = "bevy_render")]
render_layers: Default::default(),
}
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
#[reflect(Clone, Default)]
pub struct GizmoLineConfig {
/// Line width specified in pixels.
///
/// If `perspective` is `true` then this is the size in pixels at the camera's near plane.
///
/// Defaults to `2.0`.
pub width: f32,
/// Apply perspective to gizmo lines.
///
/// This setting only affects 3D, non-orthographic cameras.
///
/// Defaults to `false`.
pub perspective: bool,
/// Determine the style of gizmo lines.
pub style: GizmoLineStyle,
/// Describe how lines should join.
pub joints: GizmoLineJoint,
}
impl Default for GizmoLineConfig {
fn default() -> Self {
Self {
width: 2.,
perspective: false,
style: GizmoLineStyle::Solid,
joints: GizmoLineJoint::None,
}
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
#[derive(Component)]
pub(crate) struct GizmoMeshConfig {
pub line_perspective: bool,
pub line_style: GizmoLineStyle,
pub line_joints: GizmoLineJoint,
pub render_layers: bevy_render::view::RenderLayers,
pub handle: Handle<GizmoAsset>,
}

84
vendor/bevy_gizmos/src/cross.rs vendored Normal file
View File

@@ -0,0 +1,84 @@
//! Additional [`GizmoBuffer`] Functions -- Crosses
//!
//! Includes the implementation of [`GizmoBuffer::cross`] and [`GizmoBuffer::cross_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3};
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a cross in 3D with the given `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the `half_size`s are aligned with the `Vec3::X`, `Vec3::Y` and `Vec3::Z` axes.
///
/// This should be called for each frame the cross needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross(Isometry3d::IDENTITY, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross(
&mut self,
isometry: impl Into<Isometry3d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[Vec3::X, Vec3::Y, Vec3::Z]
.map(|axis| axis * half_size)
.into_iter()
.for_each(|axis| {
self.line(isometry * axis, isometry * (-axis), color);
});
}
/// Draw a cross in 2D with the given `isometry` applied.
///
/// If `isometry == Isometry2d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the `half_size`s are aligned with the `Vec3::X` and `Vec3::Y` axes.
///
/// This should be called for each frame the cross needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross_2d(Isometry2d::IDENTITY, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross_2d(
&mut self,
isometry: impl Into<Isometry2d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[Vec2::X, Vec2::Y]
.map(|axis| axis * half_size)
.into_iter()
.for_each(|axis| {
self.line_2d(isometry * axis, isometry * (-axis), color);
});
}
}

178
vendor/bevy_gizmos/src/curves.rs vendored Normal file
View File

@@ -0,0 +1,178 @@
//! Additional [`GizmoBuffer`] Functions -- Curves
//!
//! Includes the implementation of [`GizmoBuffer::curve_2d`],
//! [`GizmoBuffer::curve_3d`] and assorted support items.
use bevy_color::Color;
use bevy_math::{
curve::{Curve, CurveExt},
Vec2, Vec3,
};
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a curve, at the given time points, sampling in 2D.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s
/// - `times` some iterable type yielding `f32` which will be used for sampling the curve
/// - `color` the color of the curve
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_2d(
&mut self,
curve_2d: impl Curve<Vec2>,
times: impl IntoIterator<Item = f32>,
color: impl Into<Color>,
) {
self.linestrip_2d(curve_2d.sample_iter(times).flatten(), color);
}
/// Draw a curve, at the given time points, sampling in 3D.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s
/// - `times` some iterable type yielding `f32` which will be used for sampling the curve
/// - `color` the color of the curve
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });
/// gizmos.curve_3d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_3d(
&mut self,
curve_3d: impl Curve<Vec3>,
times: impl IntoIterator<Item = f32>,
color: impl Into<Color>,
) {
self.linestrip(curve_3d.sample_iter(times).flatten(), color);
}
/// Draw a curve, at the given time points, sampling in 2D, with a color gradient.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s
/// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling
/// the curve together with the color at this position
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_gradient_2d(
/// curve,
/// (0..=100).map(|n| n as f32 / 100.0)
/// .map(|t| (t, GREEN.mix(&RED, t)))
/// );
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_gradient_2d<C>(
&mut self,
curve_2d: impl Curve<Vec2>,
times_with_colors: impl IntoIterator<Item = (f32, C)>,
) where
C: Into<Color>,
{
self.linestrip_gradient_2d(
times_with_colors
.into_iter()
.filter_map(|(time, color)| curve_2d.sample(time).map(|sample| (sample, color))),
);
}
/// Draw a curve, at the given time points, sampling in 3D, with a color gradient.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s
/// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling
/// the curve together with the color at this position
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });
/// gizmos.curve_gradient_3d(
/// curve,
/// (0..=100).map(|n| n as f32 / 100.0)
/// .map(|t| (t, GREEN.mix(&RED, t)))
/// );
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_gradient_3d<C>(
&mut self,
curve_3d: impl Curve<Vec3>,
times_with_colors: impl IntoIterator<Item = (f32, C)>,
) where
C: Into<Color>,
{
self.linestrip_gradient(
times_with_colors
.into_iter()
.filter_map(|(time, color)| curve_3d.sample(time).map(|sample| (sample, color))),
);
}
}

844
vendor/bevy_gizmos/src/gizmos.rs vendored Normal file
View File

@@ -0,0 +1,844 @@
//! A module for the [`Gizmos`] [`SystemParam`].
use core::{
iter,
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
};
use bevy_color::{Color, LinearRgba};
use bevy_ecs::{
component::Tick,
resource::Resource,
system::{
Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam,
SystemParamValidationError,
},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::TransformPoint;
use bevy_utils::default;
use crate::{
config::{DefaultGizmoConfigGroup, GizmoConfigGroup, GizmoConfigStore},
prelude::GizmoConfig,
};
/// Storage of gizmo primitives.
#[derive(Resource)]
pub struct GizmoStorage<Config, Clear> {
pub(crate) list_positions: Vec<Vec3>,
pub(crate) list_colors: Vec<LinearRgba>,
pub(crate) strip_positions: Vec<Vec3>,
pub(crate) strip_colors: Vec<LinearRgba>,
marker: PhantomData<(Config, Clear)>,
}
impl<Config, Clear> Default for GizmoStorage<Config, Clear> {
fn default() -> Self {
Self {
list_positions: default(),
list_colors: default(),
strip_positions: default(),
strip_colors: default(),
marker: PhantomData,
}
}
}
impl<Config, Clear> GizmoStorage<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Combine the other gizmo storage with this one.
pub fn append_storage<OtherConfig, OtherClear>(
&mut self,
other: &GizmoStorage<OtherConfig, OtherClear>,
) {
self.list_positions.extend(other.list_positions.iter());
self.list_colors.extend(other.list_colors.iter());
self.strip_positions.extend(other.strip_positions.iter());
self.strip_colors.extend(other.strip_colors.iter());
}
pub(crate) fn swap<OtherConfig, OtherClear>(
&mut self,
other: &mut GizmoStorage<OtherConfig, OtherClear>,
) {
mem::swap(&mut self.list_positions, &mut other.list_positions);
mem::swap(&mut self.list_colors, &mut other.list_colors);
mem::swap(&mut self.strip_positions, &mut other.strip_positions);
mem::swap(&mut self.strip_colors, &mut other.strip_colors);
}
/// Clear this gizmo storage of any requested gizmos.
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
}
/// Swap buffer for a specific clearing context.
///
/// This is to stash/store the default/requested gizmos so another context can
/// be substituted for that duration.
pub struct Swap<Clear>(PhantomData<Clear>);
/// A [`SystemParam`] for drawing gizmos.
///
/// They are drawn in immediate mode, which means they will be rendered only for
/// the frames, or ticks when in [`FixedMain`](bevy_app::FixedMain), in which
/// they are spawned.
///
/// A system in [`Main`](bevy_app::Main) will be cleared each rendering
/// frame, while a system in [`FixedMain`](bevy_app::FixedMain) will be
/// cleared each time the [`RunFixedMainLoop`](bevy_app::RunFixedMainLoop)
/// schedule is run.
///
/// Gizmos should be spawned before the [`Last`](bevy_app::Last) schedule
/// to ensure they are drawn.
///
/// To set up your own clearing context (useful for custom scheduling similar
/// to [`FixedMain`](bevy_app::FixedMain)):
///
/// ```
/// use bevy_gizmos::{prelude::*, *, gizmos::GizmoStorage};
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::{schedule::ScheduleLabel, prelude::*};
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct StartOfMyContext;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct EndOfMyContext;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct StartOfRun;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct EndOfRun;
/// # struct MyContext;
/// struct ClearContextSetup;
/// impl Plugin for ClearContextSetup {
/// fn build(&self, app: &mut App) {
/// app.init_resource::<GizmoStorage<DefaultGizmoConfigGroup, MyContext>>()
/// // Make sure this context starts/ends cleanly if inside another context. E.g. it
/// // should start after the parent context starts and end after the parent context ends.
/// .add_systems(StartOfMyContext, start_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// // If not running multiple times, put this with [`start_gizmo_context`].
/// .add_systems(StartOfRun, clear_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// // If not running multiple times, put this with [`end_gizmo_context`].
/// .add_systems(EndOfRun, collect_requested_gizmos::<DefaultGizmoConfigGroup, MyContext>)
/// .add_systems(EndOfMyContext, end_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// .add_systems(
/// Last,
/// propagate_gizmos::<DefaultGizmoConfigGroup, MyContext>.before(UpdateGizmoMeshes),
/// );
/// }
/// }
/// ```
pub struct Gizmos<'w, 's, Config = DefaultGizmoConfigGroup, Clear = ()>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
buffer: Deferred<'s, GizmoBuffer<Config, Clear>>,
/// The currently used [`GizmoConfig`]
pub config: &'w GizmoConfig,
/// The currently used [`GizmoConfigGroup`]
pub config_ext: &'w Config,
}
impl<'w, 's, Config, Clear> Deref for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Target = GizmoBuffer<Config, Clear>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl<'w, 's, Config, Clear> DerefMut for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
type GizmosState<Config, Clear> = (
Deferred<'static, GizmoBuffer<Config, Clear>>,
Res<'static, GizmoConfigStore>,
);
#[doc(hidden)]
pub struct GizmosFetchState<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
state: <GizmosState<Config, Clear> as SystemParam>::State,
}
#[expect(
unsafe_code,
reason = "We cannot implement SystemParam without using unsafe code."
)]
// SAFETY: All methods are delegated to existing `SystemParam` implementations
unsafe impl<Config, Clear> SystemParam for Gizmos<'_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type State = GizmosFetchState<Config, Clear>;
type Item<'w, 's> = Gizmos<'w, 's, Config, Clear>;
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
GizmosFetchState {
state: GizmosState::<Config, Clear>::init_state(world, system_meta),
}
}
unsafe fn new_archetype(
state: &mut Self::State,
archetype: &bevy_ecs::archetype::Archetype,
system_meta: &mut SystemMeta,
) {
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
unsafe {
GizmosState::<Config, Clear>::new_archetype(&mut state.state, archetype, system_meta);
};
}
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
GizmosState::<Config, Clear>::apply(&mut state.state, system_meta, world);
}
#[inline]
unsafe fn validate_param(
state: &Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
// SAFETY: Delegated to existing `SystemParam` implementations.
unsafe { GizmosState::<Config, Clear>::validate_param(&state.state, system_meta, world) }
}
#[inline]
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Self::Item<'w, 's> {
// SAFETY: Delegated to existing `SystemParam` implementations.
let (mut f0, f1) = unsafe {
GizmosState::<Config, Clear>::get_param(
&mut state.state,
system_meta,
world,
change_tick,
)
};
// Accessing the GizmoConfigStore in every API call reduces performance significantly.
// Implementing SystemParam manually allows us to cache whether the config is currently enabled.
// Having this available allows for cheap early returns when gizmos are disabled.
let (config, config_ext) = f1.into_inner().config::<Config>();
f0.enabled = config.enabled;
Gizmos {
buffer: f0,
config,
config_ext,
}
}
}
#[expect(
unsafe_code,
reason = "We cannot implement ReadOnlySystemParam without using unsafe code."
)]
// Safety: Each field is `ReadOnlySystemParam`, and Gizmos SystemParam does not mutate world
unsafe impl<'w, 's, Config, Clear> ReadOnlySystemParam for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
Deferred<'s, GizmoBuffer<Config, Clear>>: ReadOnlySystemParam,
Res<'w, GizmoConfigStore>: ReadOnlySystemParam,
{
}
/// Buffer for gizmo vertex data.
#[derive(Debug, Clone, Reflect)]
#[reflect(Default)]
pub struct GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub(crate) enabled: bool,
pub(crate) list_positions: Vec<Vec3>,
pub(crate) list_colors: Vec<LinearRgba>,
pub(crate) strip_positions: Vec<Vec3>,
pub(crate) strip_colors: Vec<LinearRgba>,
#[reflect(ignore, clone)]
pub(crate) marker: PhantomData<(Config, Clear)>,
}
impl<Config, Clear> Default for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn default() -> Self {
GizmoBuffer {
enabled: true,
list_positions: Vec::new(),
list_colors: Vec::new(),
strip_positions: Vec::new(),
strip_colors: Vec::new(),
marker: PhantomData,
}
}
}
/// Read-only view into [`GizmoBuffer`] data.
pub struct GizmoBufferView<'a> {
/// Vertex positions for line-list topology.
pub list_positions: &'a Vec<Vec3>,
/// Vertex colors for line-list topology.
pub list_colors: &'a Vec<LinearRgba>,
/// Vertex positions for line-strip topology.
pub strip_positions: &'a Vec<Vec3>,
/// Vertex colors for line-strip topology.
pub strip_colors: &'a Vec<LinearRgba>,
}
impl<Config, Clear> SystemBuffer for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
let mut storage = world.resource_mut::<GizmoStorage<Config, Clear>>();
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<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Clear all data.
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
/// Read-only view into the buffers data.
pub fn buffer(&self) -> GizmoBufferView {
let GizmoBuffer {
list_positions,
list_colors,
strip_positions,
strip_colors,
..
} = self;
GizmoBufferView {
list_positions,
list_colors,
strip_positions,
strip_colors,
}
}
/// Draw a line in 3D from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line(Vec3::ZERO, Vec3::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line(&mut self, start: Vec3, end: Vec3, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.extend_list_positions([start, end]);
self.add_list_color(color, 2);
}
/// Draw a line in 3D with a color gradient from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient(Vec3::ZERO, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_gradient<C: Into<Color>>(
&mut self,
start: Vec3,
end: Vec3,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.extend_list_positions([start, end]);
self.extend_list_colors([start_color, end_color]);
}
/// Draw a line in 3D from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray(Vec3::Y, Vec3::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray(&mut self, start: Vec3, vector: Vec3, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line(start, start + vector, color);
}
/// Draw a line in 3D with a color gradient from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray_gradient(Vec3::Y, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_gradient<C: Into<Color>>(
&mut self,
start: Vec3,
vector: Vec3,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient(start, start + vector, start_color, end_color);
}
/// Draw a line in 3D made of straight segments between the points.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip([Vec3::ZERO, Vec3::X, Vec3::Y], GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip(
&mut self,
positions: impl IntoIterator<Item = Vec3>,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
self.extend_strip_positions(positions);
let len = self.strip_positions.len();
let linear_color = LinearRgba::from(color.into());
self.strip_colors.resize(len - 1, linear_color);
self.strip_colors.push(LinearRgba::NAN);
}
/// Draw a line in 3D made of straight segments between the points, with a color gradient.
///
/// This should be called for each frame the lines need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{BLUE, GREEN, RED};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_gradient([
/// (Vec3::ZERO, GREEN),
/// (Vec3::X, RED),
/// (Vec3::Y, BLUE)
/// ]);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_gradient<C: Into<Color>>(
&mut self,
points: impl IntoIterator<Item = (Vec3, C)>,
) {
if !self.enabled {
return;
}
let points = points.into_iter();
let GizmoBuffer {
strip_positions,
strip_colors,
..
} = self;
let (min, _) = points.size_hint();
strip_positions.reserve(min);
strip_colors.reserve(min);
for (position, color) in points {
strip_positions.push(position);
strip_colors.push(LinearRgba::from(color.into()));
}
strip_positions.push(Vec3::NAN);
strip_colors.push(LinearRgba::NAN);
}
/// Draw a wireframe rectangle in 3D with the given `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the sizes are aligned with the `Vec3::X` and `Vec3::Y` axes.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rect(Isometry3d::IDENTITY, Vec2::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect(&mut self, isometry: impl Into<Isometry3d>, size: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2.extend(0.));
self.linestrip([tl, tr, br, bl, tl], color);
}
/// Draw a wireframe cube in 3D.
///
/// This should be called for each frame the cube needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_transform::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cuboid(Transform::IDENTITY, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn cuboid(&mut self, transform: impl TransformPoint, color: impl Into<Color>) {
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
let rect = rect_inner(Vec2::ONE);
// Front
let [tlf, trf, brf, blf] = rect.map(|vec2| transform.transform_point(vec2.extend(0.5)));
// Back
let [tlb, trb, brb, blb] = rect.map(|vec2| transform.transform_point(vec2.extend(-0.5)));
let strip_positions = [
tlf, trf, brf, blf, tlf, // Front
tlb, trb, brb, blb, tlb, // Back
];
self.linestrip(strip_positions, polymorphic_color);
let list_positions = [
trf, trb, brf, brb, blf, blb, // Front to back
];
self.extend_list_positions(list_positions);
self.add_list_color(polymorphic_color, 6);
}
/// Draw a line in 2D from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_2d(Vec2::ZERO, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line(start.extend(0.), end.extend(0.), color);
}
/// Draw a line in 2D with a color gradient from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient_2d(Vec2::ZERO, Vec2::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_gradient_2d<C: Into<Color>>(
&mut self,
start: Vec2,
end: Vec2,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color);
}
/// Draw a line in 2D made of straight segments between the points.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_2d([Vec2::ZERO, Vec2::X, Vec2::Y], GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_2d(
&mut self,
positions: impl IntoIterator<Item = Vec2>,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color);
}
/// Draw a line in 2D made of straight segments between the points, with a color gradient.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN, BLUE};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_gradient_2d([
/// (Vec2::ZERO, GREEN),
/// (Vec2::X, RED),
/// (Vec2::Y, BLUE)
/// ]);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_gradient_2d<C: Into<Color>>(
&mut self,
positions: impl IntoIterator<Item = (Vec2, C)>,
) {
if !self.enabled {
return;
}
self.linestrip_gradient(
positions
.into_iter()
.map(|(vec2, color)| (vec2.extend(0.), color)),
);
}
/// Draw a line in 2D from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray_2d(Vec2::Y, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line_2d(start, start + vector, color);
}
/// Draw a line in 2D with a color gradient from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient(Vec3::Y, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_gradient_2d<C: Into<Color>>(
&mut self,
start: Vec2,
vector: Vec2,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient_2d(start, start + vector, start_color, end_color);
}
/// Draw a wireframe rectangle in 2D with the given `isometry` applied.
///
/// If `isometry == Isometry2d::IDENTITY` then
///
/// - the center is at `Vec2::ZERO`
/// - the sizes are aligned with the `Vec2::X` and `Vec2::Y` axes.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect_2d(
&mut self,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * 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);
}
#[inline]
fn extend_list_colors(&mut self, colors: impl IntoIterator<Item = impl Into<Color>>) {
self.list_colors.extend(
colors
.into_iter()
.map(|color| LinearRgba::from(color.into())),
);
}
#[inline]
fn add_list_color(&mut self, color: impl Into<Color>, count: usize) {
let polymorphic_color: Color = color.into();
let linear_color = LinearRgba::from(polymorphic_color);
self.list_colors.extend(iter::repeat_n(linear_color, count));
}
#[inline]
fn extend_strip_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
self.strip_positions.extend(positions);
self.strip_positions.push(Vec3::NAN);
}
}
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]
}

430
vendor/bevy_gizmos/src/grid.rs vendored Normal file
View File

@@ -0,0 +1,430 @@
//! Additional [`GizmoBuffer`] Functions -- Grids
//!
//! Includes the implementation of [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`].
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{ops, Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles};
/// A builder returned by [`GizmoBuffer::grid_3d`]
pub struct GridBuilder3d<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec3,
cell_count: UVec3,
skew: Vec3,
outer_edges: [bool; 3],
color: Color,
}
/// A builder returned by [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`]
pub struct GridBuilder2d<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec2,
cell_count: UVec2,
skew: Vec2,
outer_edges: [bool; 2],
color: Color,
}
impl<Config, Clear> GridBuilder3d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
pub fn skew_x(mut self, skew: f32) -> Self {
self.skew.x = skew;
self
}
/// Skews the grid by `tan(skew)` in the y direction.
/// `skew` is in radians
pub fn skew_y(mut self, skew: f32) -> Self {
self.skew.y = skew;
self
}
/// Skews the grid by `tan(skew)` in the z direction.
/// `skew` is in radians
pub fn skew_z(mut self, skew: f32) -> Self {
self.skew.z = skew;
self
}
/// Skews the grid by `tan(skew)` in the x, y and z directions.
/// `skew` is in radians
pub fn skew(mut self, skew: Vec3) -> Self {
self.skew = skew;
self
}
/// Declare that the outer edges of the grid along the x axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_x(mut self) -> Self {
self.outer_edges[0] = true;
self
}
/// Declare that the outer edges of the grid along the y axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_y(mut self) -> Self {
self.outer_edges[1] = true;
self
}
/// Declare that the outer edges of the grid along the z axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_z(mut self) -> Self {
self.outer_edges[2] = true;
self
}
/// Declare that all outer edges of the grid should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges(mut self) -> Self {
self.outer_edges.fill(true);
self
}
}
impl<Config, Clear> GridBuilder2d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
pub fn skew_x(mut self, skew: f32) -> Self {
self.skew.x = skew;
self
}
/// Skews the grid by `tan(skew)` in the y direction.
/// `skew` is in radians
pub fn skew_y(mut self, skew: f32) -> Self {
self.skew.y = skew;
self
}
/// Skews the grid by `tan(skew)` in the x and y directions.
/// `skew` is in radians
pub fn skew(mut self, skew: Vec2) -> Self {
self.skew = skew;
self
}
/// Declare that the outer edges of the grid along the x axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_x(mut self) -> Self {
self.outer_edges[0] = true;
self
}
/// Declare that the outer edges of the grid along the y axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_y(mut self) -> Self {
self.outer_edges[1] = true;
self
}
/// Declare that all outer edges of the grid should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges(mut self) -> Self {
self.outer_edges.fill(true);
self
}
}
impl<Config, Clear> Drop for GridBuilder3d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draws a grid, by drawing lines with the stored [`GizmoBuffer`]
fn drop(&mut self) {
draw_grid(
self.gizmos,
self.isometry,
self.spacing,
self.cell_count,
self.skew,
self.outer_edges,
self.color,
);
}
}
impl<Config, Clear> Drop for GridBuilder2d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
draw_grid(
self.gizmos,
self.isometry,
self.spacing.extend(0.),
self.cell_count.extend(0),
self.skew.extend(0.),
[self.outer_edges[0], self.outer_edges[1], true],
self.color,
);
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a 2D grid in 3D.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// The grid's default orientation aligns with the XY-plane.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the grid.
/// - the translation specifies the center of the grid
/// - defines the orientation of the grid, by default we assume the grid is contained in a
/// plane parallel to the XY plane
/// - `cell_count`: defines the amount of cells in the x and y axes
/// - `spacing`: defines the distance between cells along the x and y axes
/// - `color`: color of the grid
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid(
/// Isometry3d::IDENTITY,
/// UVec2::new(10, 10),
/// Vec2::splat(2.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid(
&mut self,
isometry: impl Into<Isometry3d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, Config, Clear> {
GridBuilder2d {
gizmos: self,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec2::ZERO,
outer_edges: [false, false],
color: color.into(),
}
}
/// Draw a 3D grid of voxel-like cells.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the grid.
/// - the translation specifies the center of the grid
/// - defines the orientation of the grid, by default we assume the grid is aligned with all axes
/// - `cell_count`: defines the amount of cells in the x, y and z axes
/// - `spacing`: defines the distance between cells along the x, y and z axes
/// - `color`: color of the grid
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)`, `.skew_y(...)` or `.skew_z(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)`, `.outer_edges_y(...)` or `.outer_edges_z(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid_3d(
/// Isometry3d::IDENTITY,
/// UVec3::new(10, 2, 10),
/// Vec3::splat(2.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid_3d(
&mut self,
isometry: impl Into<Isometry3d>,
cell_count: UVec3,
spacing: Vec3,
color: impl Into<Color>,
) -> GridBuilder3d<'_, Config, Clear> {
GridBuilder3d {
gizmos: self,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec3::ZERO,
outer_edges: [false, false, false],
color: color.into(),
}
}
/// Draw a grid in 2D.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the grid.
/// - the translation specifies the center of the grid
/// - defines the orientation of the grid, by default we assume the grid is aligned with all axes
/// - `cell_count`: defines the amount of cells in the x and y axes
/// - `spacing`: defines the distance between cells along the x and y axes
/// - `color`: color of the grid
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid_2d(
/// Isometry2d::IDENTITY,
/// UVec2::new(10, 10),
/// Vec2::splat(1.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid_2d(
&mut self,
isometry: impl Into<Isometry2d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, Config, Clear> {
let isometry = isometry.into();
GridBuilder2d {
gizmos: self,
isometry: Isometry3d::new(
isometry.translation.extend(0.0),
Quat::from_rotation_z(isometry.rotation.as_radians()),
),
spacing,
cell_count,
skew: Vec2::ZERO,
outer_edges: [false, false],
color: color.into(),
}
}
}
fn draw_grid<Config, Clear>(
gizmos: &mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec3,
cell_count: UVec3,
skew: Vec3,
outer_edges: [bool; 3],
color: Color,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
if !gizmos.enabled {
return;
}
#[inline]
fn or_zero(cond: bool, val: Vec3) -> Vec3 {
if cond {
val
} else {
Vec3::ZERO
}
}
// Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
let skew_tan = Vec3::from(skew.to_array().map(ops::tan));
let dx = or_zero(
cell_count.x != 0,
spacing.x * Vec3::new(1., skew_tan.y, skew_tan.z),
);
let dy = or_zero(
cell_count.y != 0,
spacing.y * Vec3::new(skew_tan.x, 1., skew_tan.z),
);
let dz = or_zero(
cell_count.z != 0,
spacing.z * Vec3::new(skew_tan.x, skew_tan.y, 1.),
);
// Bottom-left-front corner of the grid
let cell_count_half = cell_count.as_vec3() * 0.5;
let grid_start = -cell_count_half.x * dx - cell_count_half.y * dy - cell_count_half.z * dz;
let outer_edges_u32 = UVec3::from(outer_edges.map(|v| v as u32));
let line_count = outer_edges_u32 * cell_count.saturating_add(UVec3::ONE)
+ (UVec3::ONE - outer_edges_u32) * cell_count.saturating_sub(UVec3::ONE);
let x_start = grid_start + or_zero(!outer_edges[0], dy + dz);
let y_start = grid_start + or_zero(!outer_edges[1], dx + dz);
let z_start = grid_start + or_zero(!outer_edges[2], dx + dy);
fn iter_lines(
delta_a: Vec3,
delta_b: Vec3,
delta_c: Vec3,
line_count: UVec2,
cell_count: u32,
start: Vec3,
) -> impl Iterator<Item = [Vec3; 2]> {
let dline = delta_a * cell_count as f32;
(0..line_count.x).map(|v| v as f32).flat_map(move |b| {
(0..line_count.y).map(|v| v as f32).map(move |c| {
let line_start = start + b * delta_b + c * delta_c;
let line_end = line_start + dline;
[line_start, line_end]
})
})
}
// Lines along the x direction
let x_lines = iter_lines(dx, dy, dz, line_count.yz(), cell_count.x, x_start);
// Lines along the y direction
let y_lines = iter_lines(dy, dz, dx, line_count.zx(), cell_count.y, y_start);
// Lines along the z direction
let z_lines = iter_lines(dz, dx, dy, line_count.xy(), cell_count.z, z_start);
x_lines
.chain(y_lines)
.chain(z_lines)
.map(|vec3s| vec3s.map(|vec3| isometry * vec3))
.for_each(|[start, end]| {
gizmos.line(start, end, color);
});
}

888
vendor/bevy_gizmos/src/lib.rs vendored Executable file
View File

@@ -0,0 +1,888 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
//! This crate adds an immediate mode drawing api to Bevy for visual debugging.
//!
//! # Example
//! ```
//! # use bevy_gizmos::prelude::*;
//! # use bevy_math::prelude::*;
//! # use bevy_color::palettes::basic::GREEN;
//! fn system(mut gizmos: Gizmos) {
//! gizmos.line(Vec3::ZERO, Vec3::X, GREEN);
//! }
//! # bevy_ecs::system::assert_is_system(system);
//! ```
//!
//! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples.
// Required to make proc macros work in bevy itself.
extern crate self as bevy_gizmos;
/// System set label for the systems handling the rendering of gizmos.
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
pub enum GizmoRenderSystem {
/// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase
#[cfg(feature = "bevy_sprite")]
QueueLineGizmos2d,
/// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase
#[cfg(feature = "bevy_pbr")]
QueueLineGizmos3d,
}
#[cfg(feature = "bevy_render")]
pub mod aabb;
pub mod arcs;
pub mod arrows;
pub mod circles;
pub mod config;
pub mod cross;
pub mod curves;
pub mod gizmos;
pub mod grid;
pub mod primitives;
pub mod retained;
pub mod rounded_box;
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
pub mod light;
#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))]
mod pipeline_2d;
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
mod pipeline_3d;
/// The gizmos prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[cfg(feature = "bevy_render")]
pub use crate::aabb::{AabbGizmoConfigGroup, ShowAabbGizmo};
#[doc(hidden)]
pub use crate::{
config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
GizmoLineConfig, GizmoLineJoint, GizmoLineStyle,
},
gizmos::Gizmos,
primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
retained::Gizmo,
AppGizmoBuilder, GizmoAsset,
};
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
}
use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop};
use bevy_asset::{weak_handle, Asset, AssetApp, AssetId, Assets, Handle};
use bevy_ecs::{
resource::Resource,
schedule::{IntoScheduleConfigs, SystemSet},
system::{Res, ResMut},
};
use bevy_math::{Vec3, Vec4};
use bevy_reflect::TypePath;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
use crate::config::GizmoMeshConfig;
use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer};
#[cfg(feature = "bevy_render")]
use {
crate::retained::extract_linegizmos,
bevy_ecs::{
component::Component,
entity::Entity,
query::ROQueryItem,
system::{
lifetimeless::{Read, SRes},
Commands, SystemParamItem,
},
},
bevy_math::{Affine3, Affine3A},
bevy_render::{
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader,
ShaderStages, ShaderType, VertexFormat,
},
renderer::RenderDevice,
sync_world::{MainEntity, TemporaryRenderEntity},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
},
bytemuck::cast_slice,
};
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite"),
))]
use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode};
use bevy_time::Fixed;
use bevy_utils::TypeIdMap;
use config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint,
};
use core::{any::TypeId, marker::PhantomData, mem};
use gizmos::{GizmoStorage, Swap};
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
use light::LightGizmoPlugin;
#[cfg(feature = "bevy_render")]
const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("15dc5869-ad30-4664-b35a-4137cb8804a1");
#[cfg(feature = "bevy_render")]
const LINE_JOINT_SHADER_HANDLE: Handle<Shader> =
weak_handle!("7b5bdda5-df81-4711-a6cf-e587700de6f2");
/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
///
/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpritePlugin`](bevy_sprite::SpritePlugin).
#[derive(Default)]
pub struct GizmoPlugin;
impl Plugin for GizmoPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "bevy_render")]
{
use bevy_asset::load_internal_asset;
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
LINE_JOINT_SHADER_HANDLE,
"line_joints.wgsl",
Shader::from_wgsl
);
}
app.register_type::<GizmoConfig>()
.register_type::<GizmoConfigStore>()
.init_asset::<GizmoAsset>()
.init_resource::<GizmoHandles>()
// We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
.init_gizmo_group::<DefaultGizmoConfigGroup>();
#[cfg(feature = "bevy_render")]
app.add_plugins(aabb::AabbGizmoPlugin)
.add_plugins(UniformComponentPlugin::<LineGizmoUniform>::default())
.add_plugins(RenderAssetPlugin::<GpuLineGizmo>::default());
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
app.add_plugins(LightGizmoPlugin);
#[cfg(feature = "bevy_render")]
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_systems(
Render,
prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups),
);
render_app.add_systems(ExtractSchedule, (extract_gizmo_data, extract_linegizmos));
#[cfg(feature = "bevy_sprite")]
if app.is_plugin_added::<bevy_sprite::SpritePlugin>() {
app.add_plugins(pipeline_2d::LineGizmo2dPlugin);
} else {
tracing::warn!("bevy_sprite feature is enabled but bevy_sprite::SpritePlugin was not detected. Are you sure you loaded GizmoPlugin after SpritePlugin?");
}
#[cfg(feature = "bevy_pbr")]
if app.is_plugin_added::<bevy_pbr::PbrPlugin>() {
app.add_plugins(pipeline_3d::LineGizmo3dPlugin);
} else {
tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?");
}
} else {
tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?");
}
}
#[cfg(feature = "bevy_render")]
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
let render_device = render_app.world().resource::<RenderDevice>();
let line_layout = render_device.create_bind_group_layout(
"LineGizmoUniform layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX,
uniform_buffer::<LineGizmoUniform>(true),
),
);
render_app.insert_resource(LineGizmoUniformBindgroupLayout {
layout: line_layout,
});
}
}
/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`.
pub trait AppGizmoBuilder {
/// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos&lt;Config&gt;](crate::gizmos::Gizmos).
///
/// Configurations can be set using the [`GizmoConfigStore`] [`Resource`].
fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self;
/// Insert a [`GizmoConfig`] into a specific [`GizmoConfigGroup`].
///
/// This method should be preferred over [`AppGizmoBuilder::init_gizmo_group`] if and only if you need to configure fields upon initialization.
fn insert_gizmo_config<Config: GizmoConfigGroup>(
&mut self,
group: Config,
config: GizmoConfig,
) -> &mut Self;
}
impl AppGizmoBuilder for App {
fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self {
if self.world().contains_resource::<GizmoStorage<Config, ()>>() {
return self;
}
self.world_mut()
.get_resource_or_init::<GizmoConfigStore>()
.register::<Config>();
let mut handles = self.world_mut().get_resource_or_init::<GizmoHandles>();
handles.handles.insert(TypeId::of::<Config>(), None);
// These handles are safe to mutate in any order
self.allow_ambiguous_resource::<GizmoHandles>();
self.init_resource::<GizmoStorage<Config, ()>>()
.init_resource::<GizmoStorage<Config, Fixed>>()
.init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
.add_systems(
RunFixedMainLoop,
start_gizmo_context::<Config, Fixed>
.in_set(bevy_app::RunFixedMainLoopSystem::BeforeFixedMainLoop),
)
.add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
.add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
.add_systems(
RunFixedMainLoop,
end_gizmo_context::<Config, Fixed>
.in_set(bevy_app::RunFixedMainLoopSystem::AfterFixedMainLoop),
)
.add_systems(
Last,
(
propagate_gizmos::<Config, Fixed>.before(UpdateGizmoMeshes),
update_gizmo_meshes::<Config>.in_set(UpdateGizmoMeshes),
),
);
self
}
fn insert_gizmo_config<Config: GizmoConfigGroup>(
&mut self,
group: Config,
config: GizmoConfig,
) -> &mut Self {
self.init_gizmo_group::<Config>();
self.world_mut()
.get_resource_or_init::<GizmoConfigStore>()
.insert(config, group);
self
}
}
/// Holds handles to the line gizmos for each gizmo configuration group
// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses
// `Option<Handle>` to be able to reserve the slot when creating the gizmo configuration group.
// That way iteration order is stable across executions and depends on the order of configuration
// group creation.
#[derive(Resource, Default)]
struct GizmoHandles {
handles: TypeIdMap<Option<Handle<GizmoAsset>>>,
}
/// Start a new gizmo clearing context.
///
/// Internally this pushes the parent default context into a swap buffer.
/// Gizmo contexts should be handled like a stack, so if you push a new context,
/// you must pop the context before the parent context ends.
pub fn start_gizmo_context<Config, Clear>(
mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
mut default: ResMut<GizmoStorage<Config, ()>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
default.swap(&mut *swap);
}
/// End this gizmo clearing context.
///
/// Pop the default gizmos context out of the [`Swap<Clear>`] gizmo storage.
///
/// This must be called before [`UpdateGizmoMeshes`] in the [`Last`] schedule.
pub fn end_gizmo_context<Config, Clear>(
mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
mut default: ResMut<GizmoStorage<Config, ()>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
default.clear();
default.swap(&mut *swap);
}
/// Collect the requested gizmos into a specific clear context.
pub fn collect_requested_gizmos<Config, Clear>(
mut update: ResMut<GizmoStorage<Config, ()>>,
mut context: ResMut<GizmoStorage<Config, Clear>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
context.append_storage(&update);
update.clear();
}
/// Clear out the contextual gizmos.
pub fn clear_gizmo_context<Config, Clear>(mut context: ResMut<GizmoStorage<Config, Clear>>)
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
context.clear();
}
/// Propagate the contextual gizmo into the `Update` storage for rendering.
///
/// This should be before [`UpdateGizmoMeshes`].
pub fn propagate_gizmos<Config, Clear>(
mut update_storage: ResMut<GizmoStorage<Config, ()>>,
contextual_storage: Res<GizmoStorage<Config, Clear>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
update_storage.append_storage(&*contextual_storage);
}
/// System set for updating the rendering meshes for drawing gizmos.
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct UpdateGizmoMeshes;
/// Prepare gizmos for rendering.
///
/// This also clears the default `GizmoStorage`.
fn update_gizmo_meshes<Config: GizmoConfigGroup>(
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
mut handles: ResMut<GizmoHandles>,
mut storage: ResMut<GizmoStorage<Config, ()>>,
) {
if storage.list_positions.is_empty() && storage.strip_positions.is_empty() {
handles.handles.insert(TypeId::of::<Config>(), None);
} else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
if let Some(handle) = handle {
let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
gizmo.buffer.strip_positions = mem::take(&mut storage.strip_positions);
gizmo.buffer.strip_colors = mem::take(&mut storage.strip_colors);
} else {
let gizmo = GizmoAsset {
config_ty: TypeId::of::<Config>(),
buffer: GizmoBuffer {
enabled: true,
list_positions: mem::take(&mut storage.list_positions),
list_colors: mem::take(&mut storage.list_colors),
strip_positions: mem::take(&mut storage.strip_positions),
strip_colors: mem::take(&mut storage.strip_colors),
marker: PhantomData,
},
};
*handle = Some(gizmo_assets.add(gizmo));
}
}
}
#[cfg(feature = "bevy_render")]
fn extract_gizmo_data(
mut commands: Commands,
handles: Extract<Res<GizmoHandles>>,
config: Extract<Res<GizmoConfigStore>>,
) {
use bevy_utils::once;
use config::GizmoLineStyle;
use tracing::warn;
for (group_type_id, handle) in &handles.handles {
let Some((config, _)) = config.get_config_dyn(group_type_id) else {
continue;
};
if !config.enabled {
continue;
}
let Some(handle) = handle else {
continue;
};
let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line.joints {
resolution
} else {
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = config.line.style
{
if gap_scale <= 0.0 {
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero."));
}
if line_scale <= 0.0 {
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero."));
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
commands.spawn((
LineGizmoUniform {
world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
line_width: config.line.width,
depth_bias: config.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
GizmoMeshConfig {
line_perspective: config.line.perspective,
line_style: config.line.style,
line_joints: config.line.joints,
render_layers: config.render_layers.clone(),
handle: handle.clone(),
},
// The immediate mode API does not have a main world entity to refer to,
// but we do need MainEntity on this render entity for the systems to find it.
MainEntity::from(Entity::PLACEHOLDER),
TemporaryRenderEntity,
));
}
}
#[cfg(feature = "bevy_render")]
#[derive(Component, ShaderType, Clone, Copy)]
struct LineGizmoUniform {
world_from_local: [Vec4; 3],
line_width: f32,
depth_bias: f32,
// Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
joints_resolution: u32,
// Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
gap_scale: f32,
line_scale: f32,
/// WebGL2 structs must be 16 byte aligned.
#[cfg(feature = "webgl")]
_padding: Vec3,
}
/// A collection of gizmos.
///
/// Has the same gizmo drawing API as [`Gizmos`](crate::gizmos::Gizmos).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GizmoAsset {
/// vertex buffers.
buffer: GizmoBuffer<ErasedGizmoConfigGroup, ()>,
config_ty: TypeId,
}
impl GizmoAsset {
/// Create a new [`GizmoAsset`].
pub fn new() -> Self {
GizmoAsset {
buffer: GizmoBuffer::default(),
config_ty: TypeId::of::<ErasedGizmoConfigGroup>(),
}
}
/// The type of the gizmo's configuration group.
pub fn config_typeid(&self) -> TypeId {
self.config_ty
}
}
impl Default for GizmoAsset {
fn default() -> Self {
GizmoAsset::new()
}
}
#[cfg(feature = "bevy_render")]
#[derive(Debug, Clone)]
struct GpuLineGizmo {
list_position_buffer: Buffer,
list_color_buffer: Buffer,
list_vertex_count: u32,
strip_position_buffer: Buffer,
strip_color_buffer: Buffer,
strip_vertex_count: u32,
}
#[cfg(feature = "bevy_render")]
impl RenderAsset for GpuLineGizmo {
type SourceAsset = GizmoAsset;
type Param = SRes<RenderDevice>;
fn prepare_asset(
gizmo: Self::SourceAsset,
_: AssetId<Self::SourceAsset>,
render_device: &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
let list_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Position Buffer"),
contents: cast_slice(&gizmo.buffer.list_positions),
});
let list_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Color Buffer"),
contents: cast_slice(&gizmo.buffer.list_colors),
});
let strip_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Strip Position Buffer"),
contents: cast_slice(&gizmo.buffer.strip_positions),
});
let strip_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Strip Color Buffer"),
contents: cast_slice(&gizmo.buffer.strip_colors),
});
Ok(GpuLineGizmo {
list_position_buffer,
list_color_buffer,
list_vertex_count: gizmo.buffer.list_positions.len() as u32,
strip_position_buffer,
strip_color_buffer,
strip_vertex_count: gizmo.buffer.strip_positions.len() as u32,
})
}
}
#[cfg(feature = "bevy_render")]
#[derive(Resource)]
struct LineGizmoUniformBindgroupLayout {
layout: BindGroupLayout,
}
#[cfg(feature = "bevy_render")]
#[derive(Resource)]
struct LineGizmoUniformBindgroup {
bindgroup: BindGroup,
}
#[cfg(feature = "bevy_render")]
fn prepare_line_gizmo_bind_group(
mut commands: Commands,
line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
render_device: Res<RenderDevice>,
line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
) {
if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
commands.insert_resource(LineGizmoUniformBindgroup {
bindgroup: render_device.create_bind_group(
"LineGizmoUniform bindgroup",
&line_gizmo_uniform_layout.layout,
&BindGroupEntries::single(binding),
),
});
}
}
#[cfg(feature = "bevy_render")]
struct SetLineGizmoBindGroup<const I: usize>;
#[cfg(feature = "bevy_render")]
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
type Param = SRes<LineGizmoUniformBindgroup>;
type ViewQuery = ();
type ItemQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
uniform_index: Option<ROQueryItem<'w, Self::ItemQuery>>,
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(uniform_index) = uniform_index else {
return RenderCommandResult::Skip;
};
pass.set_bind_group(
I,
&bind_group.into_inner().bindgroup,
&[uniform_index.index()],
);
RenderCommandResult::Success
}
}
#[cfg(feature = "bevy_render")]
struct DrawLineGizmo<const STRIP: bool>;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
impl<P: PhaseItem, const STRIP: bool> RenderCommand<P> for DrawLineGizmo<STRIP> {
type Param = SRes<RenderAssets<GpuLineGizmo>>;
type ViewQuery = ();
type ItemQuery = Read<GizmoMeshConfig>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
config: Option<ROQueryItem<'w, Self::ItemQuery>>,
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(config) = config else {
return RenderCommandResult::Skip;
};
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
return RenderCommandResult::Skip;
};
let vertex_count = if STRIP {
line_gizmo.strip_vertex_count
} else {
line_gizmo.list_vertex_count
};
if vertex_count < 2 {
return RenderCommandResult::Success;
}
let instances = if STRIP {
let item_size = VertexFormat::Float32x3.size();
let buffer_size = line_gizmo.strip_position_buffer.size() - item_size;
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size));
pass.set_vertex_buffer(1, line_gizmo.strip_position_buffer.slice(item_size..));
let item_size = VertexFormat::Float32x4.size();
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
pass.set_vertex_buffer(2, line_gizmo.strip_color_buffer.slice(..buffer_size));
pass.set_vertex_buffer(3, line_gizmo.strip_color_buffer.slice(item_size..));
vertex_count - 1
} else {
pass.set_vertex_buffer(0, line_gizmo.list_position_buffer.slice(..));
pass.set_vertex_buffer(1, line_gizmo.list_color_buffer.slice(..));
vertex_count / 2
};
pass.draw(0..6, 0..instances);
RenderCommandResult::Success
}
}
#[cfg(feature = "bevy_render")]
struct DrawLineJointGizmo;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
type Param = SRes<RenderAssets<GpuLineGizmo>>;
type ViewQuery = ();
type ItemQuery = Read<GizmoMeshConfig>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
config: Option<ROQueryItem<'w, Self::ItemQuery>>,
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(config) = config else {
return RenderCommandResult::Skip;
};
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
return RenderCommandResult::Skip;
};
if line_gizmo.strip_vertex_count <= 2 {
return RenderCommandResult::Success;
};
if config.line_joints == GizmoLineJoint::None {
return RenderCommandResult::Success;
};
let instances = {
let item_size = VertexFormat::Float32x3.size();
// position_a
let buffer_size_a = line_gizmo.strip_position_buffer.size() - item_size * 2;
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size_a));
// position_b
let buffer_size_b = line_gizmo.strip_position_buffer.size() - item_size;
pass.set_vertex_buffer(
1,
line_gizmo
.strip_position_buffer
.slice(item_size..buffer_size_b),
);
// position_c
pass.set_vertex_buffer(2, line_gizmo.strip_position_buffer.slice(item_size * 2..));
// color
let item_size = VertexFormat::Float32x4.size();
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
// This corresponds to the color of position_b, hence starts from `item_size`
pass.set_vertex_buffer(
3,
line_gizmo.strip_color_buffer.slice(item_size..buffer_size),
);
line_gizmo.strip_vertex_count - 2
};
let vertices = match config.line_joints {
GizmoLineJoint::None => unreachable!(),
GizmoLineJoint::Miter => 6,
GizmoLineJoint::Round(resolution) => resolution * 3,
GizmoLineJoint::Bevel => 3,
};
pass.draw(0..vertices, 0..instances);
RenderCommandResult::Success
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
array_stride: Float32x3.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x3,
offset: 0,
shader_location: 0,
}],
};
let mut color_layout = VertexBufferLayout {
array_stride: Float32x4.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x4,
offset: 0,
shader_location: 2,
}],
};
if strip {
vec![
position_layout.clone(),
{
position_layout.attributes[0].shader_location = 1;
position_layout
},
color_layout.clone(),
{
color_layout.attributes[0].shader_location = 3;
color_layout
},
]
} else {
position_layout.array_stride *= 2;
position_layout.attributes.push(VertexAttribute {
format: Float32x3,
offset: Float32x3.size(),
shader_location: 1,
});
color_layout.array_stride *= 2;
color_layout.attributes.push(VertexAttribute {
format: Float32x4,
offset: Float32x4.size(),
shader_location: 3,
});
vec![position_layout, color_layout]
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
array_stride: Float32x3.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x3,
offset: 0,
shader_location: 0,
}],
};
let color_layout = VertexBufferLayout {
array_stride: Float32x4.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x4,
offset: 0,
shader_location: 3,
}],
};
vec![
position_layout.clone(),
{
position_layout.attributes[0].shader_location = 1;
position_layout.clone()
},
{
position_layout.attributes[0].shader_location = 2;
position_layout
},
color_layout.clone(),
]
}

311
vendor/bevy_gizmos/src/light.rs vendored Normal file
View File

@@ -0,0 +1,311 @@
//! A module adding debug visualization of [`PointLight`]s, [`SpotLight`]s and [`DirectionalLight`]s.
use core::f32::consts::PI;
use crate::primitives::dim3::GizmoPrimitive3d;
use bevy_app::{Plugin, PostUpdate};
use bevy_color::{
palettes::basic::{BLUE, GREEN, RED},
Color, Oklcha,
};
use bevy_ecs::{
component::Component,
entity::Entity,
query::Without,
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
system::{Query, Res},
};
use bevy_math::{
ops,
primitives::{Cone, Sphere},
Isometry3d, Quat, Vec3,
};
use bevy_pbr::{DirectionalLight, PointLight, SpotLight};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::{components::GlobalTransform, TransformSystem};
use crate::{
config::{GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
AppGizmoBuilder,
};
/// Draws a standard sphere for the radius and an axis sphere for the range.
fn point_light_gizmo(
transform: &GlobalTransform,
point_light: &PointLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let position = transform.translation();
gizmos
.primitive_3d(&Sphere::new(point_light.radius), position, color)
.resolution(16);
gizmos
.sphere(position, point_light.range, color)
.resolution(32);
}
/// Draws a sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the
/// farthest point of effect of the spot light along its direction.
fn spot_light_gizmo(
transform: &GlobalTransform,
spot_light: &SpotLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.primitive_3d(&Sphere::new(spot_light.radius), translation, color)
.resolution(16);
// Offset the tip of the cone to the light position.
for angle in [spot_light.inner_angle, spot_light.outer_angle] {
let height = spot_light.range * ops::cos(angle);
let position = translation + rotation * Vec3::NEG_Z * height / 2.0;
gizmos
.primitive_3d(
&Cone {
radius: spot_light.range * ops::sin(angle),
height,
},
Isometry3d::new(position, rotation * Quat::from_rotation_x(PI / 2.0)),
color,
)
.height_resolution(4)
.base_resolution(32);
}
for arc_rotation in [
Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle),
Quat::from_euler(
bevy_math::EulerRot::XZY,
0.0,
PI / 2.0,
PI / 2.0 - spot_light.outer_angle,
),
] {
gizmos
.arc_3d(
2.0 * spot_light.outer_angle,
spot_light.range,
Isometry3d::new(translation, rotation * arc_rotation),
color,
)
.resolution(16);
}
}
/// Draws an arrow alongside the directional light direction.
fn directional_light_gizmo(
transform: &GlobalTransform,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.arrow(translation, translation + rotation * Vec3::NEG_Z, color)
.with_tip_length(0.3);
}
/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s
/// and [`DirectionalLight`]s for debugging.
pub struct LightGizmoPlugin;
impl Plugin for LightGizmoPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.register_type::<LightGizmoConfigGroup>()
.init_gizmo_group::<LightGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_lights,
draw_all_lights.run_if(|config: Res<GizmoConfigStore>| {
config.config::<LightGizmoConfigGroup>().1.draw_all
}),
)
.after(TransformSystem::TransformPropagate),
);
}
}
/// Configures how a color is attributed to a light gizmo.
#[derive(Debug, Clone, Copy, Default, Reflect)]
#[reflect(Clone, Default)]
pub enum LightGizmoColor {
/// User-specified color.
Manual(Color),
/// Random color derived from the light's [`Entity`].
Varied,
/// Take the color of the represented light.
#[default]
MatchLightColor,
/// Take the color provided by [`LightGizmoConfigGroup`] depending on the light kind.
ByLightType,
}
/// The [`GizmoConfigGroup`] used to configure the visualization of lights.
#[derive(Clone, Reflect, GizmoConfigGroup)]
#[reflect(Clone, Default)]
pub struct LightGizmoConfigGroup {
/// Draw a gizmo for all lights if true.
///
/// Defaults to `false`.
pub draw_all: bool,
/// Default color strategy for all light gizmos.
///
/// Defaults to [`LightGizmoColor::MatchLightColor`].
pub color: LightGizmoColor,
/// [`Color`] to use for drawing a [`PointLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`RED`].
pub point_light_color: Color,
/// [`Color`] to use for drawing a [`SpotLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`GREEN`].
pub spot_light_color: Color,
/// [`Color`] to use for drawing a [`DirectionalLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`BLUE`].
pub directional_light_color: Color,
}
impl Default for LightGizmoConfigGroup {
fn default() -> Self {
Self {
draw_all: false,
color: LightGizmoColor::MatchLightColor,
point_light_color: RED.into(),
spot_light_color: GREEN.into(),
directional_light_color: BLUE.into(),
}
}
}
/// Add this [`Component`] to an entity to draw any of its lights components
/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]).
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component, Default, Debug)]
pub struct ShowLightGizmo {
/// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`].
///
/// Defaults to [`None`].
pub color: Option<LightGizmoColor>,
}
fn draw_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform, &ShowLightGizmo)>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform, &ShowLightGizmo)>,
directional_query: Query<(Entity, &DirectionalLight, &GlobalTransform, &ShowLightGizmo)>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
let color = |entity: Entity, gizmo_color: Option<LightGizmoColor>, light_color, type_color| {
match gizmo_color.unwrap_or(gizmos.config_ext.color) {
LightGizmoColor::Manual(color) => color,
LightGizmoColor::Varied => Oklcha::sequential_dispersed(entity.index()).into(),
LightGizmoColor::MatchLightColor => light_color,
LightGizmoColor::ByLightType => type_color,
}
};
for (entity, light, transform, light_gizmo) in &point_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.point_light_color,
);
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &spot_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.spot_light_color,
);
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &directional_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.directional_light_color,
);
directional_light_gizmo(transform, color, &mut gizmos);
}
}
fn draw_all_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform), Without<ShowLightGizmo>>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform), Without<ShowLightGizmo>>,
directional_query: Query<
(Entity, &DirectionalLight, &GlobalTransform),
Without<ShowLightGizmo>,
>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
match gizmos.config_ext.color {
LightGizmoColor::Manual(color) => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(transform, color, &mut gizmos);
}
}
LightGizmoColor::Varied => {
let color = |entity: Entity| Oklcha::sequential_dispersed(entity.index()).into();
for (entity, light, transform) in &point_query {
point_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, _, transform) in &directional_query {
directional_light_gizmo(transform, color(entity), &mut gizmos);
}
}
LightGizmoColor::MatchLightColor => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &directional_query {
directional_light_gizmo(transform, light.color, &mut gizmos);
}
}
LightGizmoColor::ByLightType => {
for (_, light, transform) in &point_query {
point_light_gizmo(
transform,
light,
gizmos.config_ext.point_light_color,
&mut gizmos,
);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(
transform,
light,
gizmos.config_ext.spot_light_color,
&mut gizmos,
);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(
transform,
gizmos.config_ext.directional_light_color,
&mut gizmos,
);
}
}
}
}

255
vendor/bevy_gizmos/src/line_joints.wgsl vendored Normal file
View File

@@ -0,0 +1,255 @@
#import bevy_render::{view::View, maths::affine3_to_square}
@group(0) @binding(0) var<uniform> view: View;
struct LineGizmoUniform {
world_from_local: mat3x4<f32>,
line_width: f32,
depth_bias: f32,
resolution: u32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// WebGL2 structs must be 16 byte aligned.
_padding: f32,
#endif
}
@group(1) @binding(0) var<uniform> joints_gizmo: LineGizmoUniform;
struct VertexInput {
@location(0) position_a: vec3<f32>,
@location(1) position_b: vec3<f32>,
@location(2) position_c: vec3<f32>,
@location(3) color: vec4<f32>,
@builtin(vertex_index) index: u32,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
};
const EPSILON: f32 = 4.88e-04;
@vertex
fn vertex_bevel(vertex: VertexInput) -> VertexOutput {
var positions = array<vec2<f32>, 3>(
vec2(0, 0),
vec2(0, 0.5),
vec2(0.5, 0),
);
var position = positions[vertex.index];
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
let tangent = normalize(ab - cb);
let normal = vec2(-tangent.y, tangent.x);
let sigma = sign(dot(ab + cb, normal));
var p0 = line_width * sigma * ab_norm;
var p1 = line_width * sigma * cb_norm;
let screen = screen_b + position.x * p0 + position.y * p1;
let depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
@vertex
fn vertex_miter(vertex: VertexInput) -> VertexOutput {
var positions = array<vec3<f32>, 6>(
vec3(0, 0, 0),
vec3(0.5, 0, 0),
vec3(0, 0.5, 0),
vec3(0, 0, 0),
vec3(0, 0.5, 0),
vec3(0, 0, 0.5),
);
var position = positions[vertex.index];
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
let tangent = normalize(ab - cb);
let normal = vec2(-tangent.y, tangent.x);
let sigma = sign(dot(ab + cb, normal));
var p0 = line_width * sigma * ab_norm;
var p1 = line_width * sigma * normal / dot(normal, ab_norm);
var p2 = line_width * sigma * cb_norm;
var screen = screen_b + position.x * p0 + position.y * p1 + position.z * p2;
var depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
@vertex
fn vertex_round(vertex: VertexInput) -> VertexOutput {
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
// We render `joints_gizmo.resolution`triangles. The vertices in each triangle are ordered as follows:
// - 0: The 'center' vertex at `screen_b`.
// - 1: The vertex closer to the ab line.
// - 2: The vertex closer to the cb line.
var in_triangle_index = f32(vertex.index) % 3.0;
var tri_index = floor(f32(vertex.index) / 3.0);
var radius = sign(in_triangle_index) * 0.5 * line_width;
var theta = acos(dot(ab_norm, cb_norm));
let sigma = sign(dot(ab_norm, cb));
var angle = theta * (tri_index + in_triangle_index - 1) / f32(joints_gizmo.resolution);
var position_x = sigma * radius * cos(angle);
var position_y = radius * sin(angle);
var screen = screen_b + position_x * ab_norm + position_y * ab;
var depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
// Move a if a is behind the near plane and b is in front.
if a.z > a.w && b.z <= b.w {
// Interpolate a towards b until it's at the near plane.
let distance_a = a.z - a.w;
let distance_b = b.z - b.w;
// Add an epsilon to the interpolator to ensure that the point is
// not just behind the clip plane due to floating-point imprecision.
let t = distance_a / (distance_a - distance_b) + EPSILON;
return mix(a, b, t);
}
return a;
}
fn depth(clip: vec4<f32>) -> f32 {
var depth: f32;
if joints_gizmo.depth_bias >= 0. {
depth = clip.z * (1. - joints_gizmo.depth_bias);
} else {
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
// and when equal to 0.0, it is exactly equal to depth.
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
// clip.w represents the near plane in homogeneous clip space in bevy, having a depth
// of this value means nothing can be in front of this
// The reason this uses an exponential function is that it makes it much easier for the
// user to chose a value that is convenient for them
depth = clip.z * exp2(-joints_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
}
return depth;
}
struct FragmentInput {
@location(0) color: vec4<f32>,
};
struct FragmentOutput {
@location(0) color: vec4<f32>,
};
@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
// return FragmentOutput(vec4(1, 1, 1, 1));
return FragmentOutput(in.color);
}

189
vendor/bevy_gizmos/src/lines.wgsl vendored Normal file
View File

@@ -0,0 +1,189 @@
// TODO use common view binding
#import bevy_render::{view::View, maths::affine3_to_square}
@group(0) @binding(0) var<uniform> view: View;
struct LineGizmoUniform {
world_from_local: mat3x4<f32>,
line_width: f32,
depth_bias: f32,
_joints_resolution: u32,
gap_scale: f32,
line_scale: f32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// WebGL2 structs must be 16 byte aligned.
_padding: vec3<f32>,
#endif
}
@group(1) @binding(0) var<uniform> line_gizmo: LineGizmoUniform;
struct VertexInput {
@location(0) position_a: vec3<f32>,
@location(1) position_b: vec3<f32>,
@location(2) color_a: vec4<f32>,
@location(3) color_b: vec4<f32>,
@builtin(vertex_index) index: u32,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
const EPSILON: f32 = 4.88e-04;
@vertex
fn vertex(vertex: VertexInput) -> VertexOutput {
var positions = array<vec2<f32>, 6>(
vec2(-0.5, 0.),
vec2(-0.5, 1.),
vec2(0.5, 1.),
vec2(-0.5, 0.),
vec2(0.5, 1.),
vec2(0.5, 0.)
);
let position = positions[vertex.index];
let world_from_local = affine3_to_square(line_gizmo.world_from_local);
// algorithm based on https://wwwtyro.net/2019/11/18/instanced-lines.html
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_b);
clip_b = clip_near_plane(clip_b, clip_a);
let clip = mix(clip_a, clip_b, position.y);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let y_basis = normalize(screen_b - screen_a);
let x_basis = vec2(-y_basis.y, y_basis.x);
var color = mix(vertex.color_a, vertex.color_b, position.y);
var line_width = line_gizmo.line_width;
var alpha = 1.;
var uv: f32;
#ifdef PERSPECTIVE
line_width /= clip.w;
// get height of near clipping plane in world space
let pos0 = view.view_from_clip * vec4(0, -1, 0, 1); // Bottom of the screen
let pos1 = view.view_from_clip * vec4(0, 1, 0, 1); // Top of the screen
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.world_from_clip * clip_a;
let position_b = view.world_from_clip * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
let clipped_offset = length(position_a.xyz - vertex.position_a);
uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
#else
// Get the distance of b to the camera along camera axes
let camera_b = view.view_from_clip * clip_b;
// This differentiates between orthographic and perspective cameras.
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
var depth_adaptment: f32;
if (clip_b.w == 1.0) {
depth_adaptment = 1.0;
}
else {
depth_adaptment = -camera_b.z;
}
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let x_offset = line_width * position.x * x_basis;
let screen = mix(screen_a, screen_b, position.y) + x_offset;
var depth: f32;
if line_gizmo.depth_bias >= 0. {
depth = clip.z * (1. - line_gizmo.depth_bias);
} else {
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
// and when equal to 0.0, it is exactly equal to depth.
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
// clip.w represents the near plane in homogeneous clip space in bevy, having a depth
// of this value means nothing can be in front of this
// The reason this uses an exponential function is that it makes it much easier for the
// user to chose a value that is convenient for them
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
}
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
let line_fraction = 2.0 * line_gizmo.line_scale / (line_gizmo.gap_scale + line_gizmo.line_scale);
uv /= (line_gizmo.gap_scale + line_gizmo.line_scale) / 2.0;
return VertexOutput(clip_position, color, uv, line_fraction);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
// Move a if a is behind the near plane and b is in front.
if a.z > a.w && b.z <= b.w {
// Interpolate a towards b until it's at the near plane.
let distance_a = a.z - a.w;
let distance_b = b.z - b.w;
// Add an epsilon to the interpolator to ensure that the point is
// not just behind the clip plane due to floating-point imprecision.
let t = distance_a / (distance_a - distance_b) + EPSILON;
return mix(a, b, t);
}
return a;
}
struct FragmentInput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
struct FragmentOutput {
@location(0) color: vec4<f32>,
};
@fragment
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
return FragmentOutput(in.color);
}
@fragment
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
var alpha: f32;
#ifdef PERSPECTIVE
alpha = 1 - floor(in.uv % 2.0);
#else
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
#endif
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}
@fragment
fn fragment_dashed(in: FragmentInput) -> FragmentOutput {
#ifdef PERSPECTIVE
let uv = in.uv;
#else
let uv = in.uv * in.position.w;
#endif
let alpha = 1.0 - floor(min((uv % 2.0) / in.line_fraction, 1.0));
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}

431
vendor/bevy_gizmos/src/pipeline_2d.rs vendored Normal file
View File

@@ -0,0 +1,431 @@
use crate::{
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT};
use bevy_ecs::{
prelude::Entity,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Query, Res, ResMut},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::FloatOrd;
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_asset::{prepare_assets, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::*,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup};
use tracing::error;
pub struct LineGizmo2dPlugin;
impl Plugin for LineGizmo2dPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_render_command::<Transparent2d, DrawLineGizmo2d>()
.add_render_command::<Transparent2d, DrawLineGizmo2dStrip>()
.add_render_command::<Transparent2d, DrawLineJointGizmo2d>()
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
.init_resource::<SpecializedRenderPipelines<LineJointGizmoPipeline>>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos2d
.in_set(RenderSet::Queue)
.ambiguous_with(bevy_sprite::queue_sprites)
.ambiguous_with(
bevy_sprite::queue_material2d_meshes::<bevy_sprite::ColorMaterial>,
),
)
.add_systems(
Render,
(queue_line_gizmos_2d, queue_line_joint_gizmos_2d)
.in_set(GizmoRenderSystem::QueueLineGizmos2d)
.after(prepare_assets::<GpuLineGizmo>),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<LineGizmoPipeline>();
render_app.init_resource::<LineJointGizmoPipeline>();
}
}
#[derive(Clone, Resource)]
struct LineGizmoPipeline {
mesh_pipeline: Mesh2dPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineGizmoPipeline {
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
strip: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
type Key = LineGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
let layout = vec![
self.mesh_pipeline.view_layout.clone(),
self.uniform_layout.clone(),
];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
},
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Always,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.mesh_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
#[derive(Clone, Resource)]
struct LineJointGizmoPipeline {
mesh_pipeline: Mesh2dPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineJointGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineJointGizmoPipeline {
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineJointGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
joints: GizmoLineJoint,
}
impl SpecializedRenderPipeline for LineJointGizmoPipeline {
type Key = LineJointGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
let layout = vec![
self.mesh_pipeline.view_layout.clone(),
self.uniform_layout.clone(),
];
if key.joints == GizmoLineJoint::None {
error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
};
let entry_point = match key.joints {
GizmoLineJoint::Miter => "vertex_miter",
GizmoLineJoint::Round(_) => "vertex_round",
GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_JOINT_SHADER_HANDLE,
entry_point: entry_point.into(),
shader_defs: shader_defs.clone(),
buffers: line_joint_gizmo_vertex_buffer_layouts(),
},
fragment: Some(FragmentState {
shader: LINE_JOINT_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Always,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.mesh_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineJointGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
type DrawLineGizmo2d = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<false>,
);
type DrawLineGizmo2dStrip = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<true>,
);
type DrawLineJointGizmo2d = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineJointGizmo,
);
fn queue_line_gizmos_2d(
draw_functions: Res<DrawFunctions<Transparent2d>>,
pipeline: Res<LineGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>,
) {
let draw_function = draw_functions.read().get_id::<DrawLineGizmo2d>().unwrap();
let draw_function_strip = draw_functions
.read()
.get_id::<DrawLineGizmo2dStrip>()
.unwrap();
for (view, msaa, render_layers) in &mut views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
let render_layers = render_layers.unwrap_or_default();
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.list_vertex_count > 0 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
mesh_key,
strip: false,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
if line_gizmo.strip_vertex_count >= 2 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
mesh_key,
strip: true,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function: draw_function_strip,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
}
}
}
fn queue_line_joint_gizmos_2d(
draw_functions: Res<DrawFunctions<Transparent2d>>,
pipeline: Res<LineJointGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineJointGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>,
) {
let draw_function = draw_functions
.read()
.get_id::<DrawLineJointGizmo2d>()
.unwrap();
for (view, msaa, render_layers) in &mut views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
let render_layers = render_layers.unwrap_or_default();
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.strip_vertex_count < 3 || config.line_joints == GizmoLineJoint::None {
continue;
}
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineJointGizmoPipelineKey {
mesh_key,
joints: config.line_joints,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
}
}

500
vendor/bevy_gizmos/src/pipeline_3d.rs vendored Normal file
View File

@@ -0,0 +1,500 @@
use crate::{
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_core_pipeline::{
core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT},
oit::OrderIndependentTransparencySettings,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
};
use bevy_ecs::{
prelude::Entity,
query::Has,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Query, Res, ResMut},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup};
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_asset::{prepare_assets, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::*,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
use tracing::error;
pub struct LineGizmo3dPlugin;
impl Plugin for LineGizmo3dPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_render_command::<Transparent3d, DrawLineGizmo3d>()
.add_render_command::<Transparent3d, DrawLineGizmo3dStrip>()
.add_render_command::<Transparent3d, DrawLineJointGizmo3d>()
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
.init_resource::<SpecializedRenderPipelines<LineJointGizmoPipeline>>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos3d
.in_set(RenderSet::Queue)
.ambiguous_with(bevy_pbr::queue_material_meshes::<bevy_pbr::StandardMaterial>),
)
.add_systems(
Render,
(queue_line_gizmos_3d, queue_line_joint_gizmos_3d)
.in_set(GizmoRenderSystem::QueueLineGizmos3d)
.after(prepare_assets::<GpuLineGizmo>),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<LineGizmoPipeline>();
render_app.init_resource::<LineJointGizmoPipeline>();
}
}
#[derive(Clone, Resource)]
struct LineGizmoPipeline {
mesh_pipeline: MeshPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineGizmoPipeline {
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineGizmoPipelineKey {
view_key: MeshPipelineKey,
strip: bool,
perspective: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
type Key = LineGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
if key.perspective {
shader_defs.push("PERSPECTIVE".into());
}
let format = if key.view_key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let view_layout = self
.mesh_pipeline
.get_view_layout(key.view_key.into())
.clone();
let layout = vec![view_layout, self.uniform_layout.clone()];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
},
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: key.view_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineGizmo 3d Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
#[derive(Clone, Resource)]
struct LineJointGizmoPipeline {
mesh_pipeline: MeshPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineJointGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineJointGizmoPipeline {
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineJointGizmoPipelineKey {
view_key: MeshPipelineKey,
perspective: bool,
joints: GizmoLineJoint,
}
impl SpecializedRenderPipeline for LineJointGizmoPipeline {
type Key = LineJointGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
if key.perspective {
shader_defs.push("PERSPECTIVE".into());
}
let format = if key.view_key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let view_layout = self
.mesh_pipeline
.get_view_layout(key.view_key.into())
.clone();
let layout = vec![view_layout, self.uniform_layout.clone()];
if key.joints == GizmoLineJoint::None {
error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
};
let entry_point = match key.joints {
GizmoLineJoint::Miter => "vertex_miter",
GizmoLineJoint::Round(_) => "vertex_round",
GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_JOINT_SHADER_HANDLE,
entry_point: entry_point.into(),
shader_defs: shader_defs.clone(),
buffers: line_joint_gizmo_vertex_buffer_layouts(),
},
fragment: Some(FragmentState {
shader: LINE_JOINT_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: key.view_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineJointGizmo 3d Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
type DrawLineGizmo3d = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<false>,
);
type DrawLineGizmo3dStrip = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<true>,
);
type DrawLineJointGizmo3d = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineJointGizmo,
);
fn queue_line_gizmos_3d(
draw_functions: Res<DrawFunctions<Transparent3d>>,
pipeline: Res<LineGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(
&ExtractedView,
&Msaa,
Option<&RenderLayers>,
(
Has<NormalPrepass>,
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
Has<DeferredPrepass>,
Has<OrderIndependentTransparencySettings>,
),
)>,
) {
let draw_function = draw_functions.read().get_id::<DrawLineGizmo3d>().unwrap();
let draw_function_strip = draw_functions
.read()
.get_id::<DrawLineGizmo3dStrip>()
.unwrap();
for (
view,
msaa,
render_layers,
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass, oit),
) in &views
{
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let render_layers = render_layers.unwrap_or_default();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if depth_prepass {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
}
if motion_vector_prepass {
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
if deferred_prepass {
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
if oit {
view_key |= MeshPipelineKey::OIT_ENABLED;
}
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.list_vertex_count > 0 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
view_key,
strip: false,
perspective: config.line_perspective,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
if line_gizmo.strip_vertex_count >= 2 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
view_key,
strip: true,
perspective: config.line_perspective,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function: draw_function_strip,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
}
}
}
fn queue_line_joint_gizmos_3d(
draw_functions: Res<DrawFunctions<Transparent3d>>,
pipeline: Res<LineJointGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineJointGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(
&ExtractedView,
&Msaa,
Option<&RenderLayers>,
(
Has<NormalPrepass>,
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
Has<DeferredPrepass>,
),
)>,
) {
let draw_function = draw_functions
.read()
.get_id::<DrawLineJointGizmo3d>()
.unwrap();
for (
view,
msaa,
render_layers,
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
) in &views
{
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let render_layers = render_layers.unwrap_or_default();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if depth_prepass {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
}
if motion_vector_prepass {
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
if deferred_prepass {
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.strip_vertex_count < 3 || config.line_joints == GizmoLineJoint::None {
continue;
}
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineJointGizmoPipelineKey {
view_key,
perspective: config.line_perspective,
joints: config.line_joints,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
}
}

View File

@@ -0,0 +1,902 @@
//! A module for rendering each of the 2D [`bevy_math::primitives`] with [`GizmoBuffer`].
use core::f32::consts::{FRAC_PI_2, PI};
use super::helpers::*;
use bevy_color::Color;
use bevy_math::{
primitives::{
Annulus, Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector,
CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle,
RegularPolygon, Rhombus, Segment2d, Triangle2d,
},
Dir2, Isometry2d, Rot2, Vec2,
};
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
// some magic number since using directions as offsets will result in lines of length 1 pixel
const MIN_LINE_LEN: f32 = 50.0;
const HALF_MIN_LINE_LEN: f32 = 25.0;
// length used to simulate infinite lines
const INFINITE_LEN: f32 = 100_000.0;
/// A trait for rendering 2D geometric primitives (`P`) with [`GizmoBuffer`].
pub trait GizmoPrimitive2d<P: Primitive2d> {
/// The output of `primitive_2d`. This is a builder to set non-default values.
type Output<'a>
where
Self: 'a;
/// Renders a 2D primitive with its associated details.
fn primitive_2d(
&mut self,
primitive: &P,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
// direction 2d
impl<Config, Clear> GizmoPrimitive2d<Dir2> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Dir2,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start = Vec2::ZERO;
let end = *primitive * MIN_LINE_LEN;
self.arrow_2d(isometry * start, isometry * end, color);
}
}
// arc 2d
impl<Config, Clear> GizmoPrimitive2d<Arc2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Arc2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));
self.arc_2d(
start_iso,
primitive.half_angle * 2.0,
primitive.radius,
color,
);
}
}
// circle 2d
impl<Config, Clear> GizmoPrimitive2d<Circle> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= crate::circles::Ellipse2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Circle,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.circle_2d(isometry, primitive.radius, color)
}
}
// circular sector 2d
impl<Config, Clear> GizmoPrimitive2d<CircularSector> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &CircularSector,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let start_iso =
isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
// we need to draw the arc part of the sector, and the two lines connecting the arc and the center
self.arc_2d(
start_iso,
primitive.arc.half_angle * 2.0,
primitive.arc.radius,
color,
);
let end_position = primitive.arc.radius * Vec2::Y;
self.line_2d(isometry * Vec2::ZERO, start_iso * end_position, color);
self.line_2d(isometry * Vec2::ZERO, end_iso * end_position, color);
}
}
// circular segment 2d
impl<Config, Clear> GizmoPrimitive2d<CircularSegment> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &CircularSegment,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let start_iso =
isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle));
let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle));
// we need to draw the arc part of the segment, and the line connecting the two ends
self.arc_2d(
start_iso,
primitive.arc.half_angle * 2.0,
primitive.arc.radius,
color,
);
let position = primitive.arc.radius * Vec2::Y;
self.line_2d(start_iso * position, end_iso * position, color);
}
}
// ellipse 2d
impl<Config, Clear> GizmoPrimitive2d<Ellipse> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= crate::circles::Ellipse2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d<'a>(
&mut self,
primitive: &Ellipse,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.ellipse_2d(isometry, primitive.half_size, color)
}
}
// annulus 2d
/// Builder for configuring the drawing options of [`Annulus`].
pub struct Annulus2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
inner_radius: f32,
outer_radius: f32,
color: Color,
inner_resolution: u32,
outer_resolution: u32,
}
impl<Config, Clear> Annulus2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments for each circle of the annulus.
pub fn resolution(mut self, resolution: u32) -> Self {
self.outer_resolution = resolution;
self.inner_resolution = resolution;
self
}
/// Set the number of line-segments for the outer circle of the annulus.
pub fn outer_resolution(mut self, resolution: u32) -> Self {
self.outer_resolution = resolution;
self
}
/// Set the number of line-segments for the inner circle of the annulus.
pub fn inner_resolution(mut self, resolution: u32) -> Self {
self.inner_resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Annulus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Annulus2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Annulus,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Annulus2dBuilder {
gizmos: self,
isometry: isometry.into(),
inner_radius: primitive.inner_circle.radius,
outer_radius: primitive.outer_circle.radius,
color: color.into(),
inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Annulus2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let Annulus2dBuilder {
gizmos,
isometry,
inner_radius,
outer_radius,
inner_resolution,
outer_resolution,
color,
..
} = self;
gizmos
.circle_2d(*isometry, *outer_radius, *color)
.resolution(*outer_resolution);
gizmos
.circle_2d(*isometry, *inner_radius, *color)
.resolution(*inner_resolution);
}
}
// rhombus 2d
impl<Config, Clear> GizmoPrimitive2d<Rhombus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Rhombus,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
};
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
primitive.half_diagonals.x * sign_x,
primitive.half_diagonals.y * sign_y,
)
});
let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// capsule 2d
impl<Config, Clear> GizmoPrimitive2d<Capsule2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Capsule2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
// transform points from the reference unit square to capsule "rectangle"
let [top_left, top_right, bottom_left, bottom_right, top_center, bottom_center] = [
[-1.0, 1.0],
[1.0, 1.0],
[-1.0, -1.0],
[1.0, -1.0],
// just reuse the pipeline for these points as well
[0.0, 1.0],
[0.0, -1.0],
]
.map(|[sign_x, sign_y]| Vec2::X * sign_x + Vec2::Y * sign_y)
.map(|reference_point| {
let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length;
reference_point * scaling
})
.map(|vec2| isometry * vec2);
// draw left and right side of capsule "rectangle"
self.line_2d(bottom_left, top_left, polymorphic_color);
self.line_2d(bottom_right, top_right, polymorphic_color);
let start_angle_top = isometry.rotation.as_radians() - FRAC_PI_2;
let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2;
// draw arcs
self.arc_2d(
Isometry2d::new(top_center, Rot2::radians(start_angle_top)),
PI,
primitive.radius,
polymorphic_color,
);
self.arc_2d(
Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),
PI,
primitive.radius,
polymorphic_color,
);
}
}
// line 2d
//
/// Builder for configuring the drawing options of [`Line2d`].
pub struct Line2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
direction: Dir2, // Direction of the line
isometry: Isometry2d,
color: Color, // color of the line
draw_arrow: bool, // decides whether to indicate the direction of the line with an arrow
}
impl<Config, Clear> Line2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the drawing mode of the line (arrow vs. plain line)
pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
self.draw_arrow = is_enabled;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Line2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Line2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Line2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Line2dBuilder {
gizmos: self,
direction: primitive.direction,
isometry: isometry.into(),
color: color.into(),
draw_arrow: false,
}
}
}
impl<Config, Clear> Drop for Line2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let [start, end] = [1.0, -1.0]
.map(|sign| sign * INFINITE_LEN)
// offset the line from the origin infinitely into the given direction
.map(|length| self.direction * length)
// transform the line with the given isometry
.map(|offset| self.isometry * offset);
self.gizmos.line_2d(start, end, self.color);
// optionally draw an arrow head at the center of the line
if self.draw_arrow {
self.gizmos.arrow_2d(
self.isometry * (-self.direction * MIN_LINE_LEN),
self.isometry * Vec2::ZERO,
self.color,
);
}
}
}
// plane 2d
impl<Config, Clear> GizmoPrimitive2d<Plane2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Plane2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
// draw normal of the plane (orthogonal to the plane itself)
let normal = primitive.normal;
let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.);
self.primitive_2d(
&normal_segment,
// offset the normal so it starts on the plane line
Isometry2d::new(isometry * (HALF_MIN_LINE_LEN * normal), isometry.rotation),
polymorphic_color,
)
.draw_arrow(true);
// draw the plane line
let direction = Dir2::new_unchecked(-normal.perp());
self.primitive_2d(&Line2d { direction }, isometry, polymorphic_color)
.draw_arrow(false);
// draw an arrow such that the normal is always left side of the plane with respect to the
// planes direction. This is to follow the "counter-clockwise" convention
self.arrow_2d(
isometry * Vec2::ZERO,
isometry * (MIN_LINE_LEN * direction),
polymorphic_color,
);
}
}
// segment 2d
/// Builder for configuring the drawing options of [`Segment2d`].
pub struct Segment2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
point1: Vec2, // First point of the segment
point2: Vec2, // Second point of the segment
isometry: Isometry2d, // isometric transformation of the line segment
color: Color, // color of the line segment
draw_arrow: bool, // decides whether to draw just a line or an arrow
}
impl<Config, Clear> Segment2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the drawing mode of the line (arrow vs. plain line)
pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
self.draw_arrow = is_enabled;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Segment2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Segment2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Segment2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Segment2dBuilder {
gizmos: self,
point1: primitive.point1(),
point2: primitive.point2(),
isometry: isometry.into(),
color: color.into(),
draw_arrow: Default::default(),
}
}
}
impl<Config, Clear> Drop for Segment2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);
if self.draw_arrow {
self.gizmos
.arrow_2d(segment.point1(), segment.point2(), self.color);
} else {
self.gizmos
.line_2d(segment.point1(), segment.point2(), self.color);
}
}
}
// polyline 2d
impl<const N: usize, Config, Clear> GizmoPrimitive2d<Polyline2d<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Polyline2d<N>,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.map(|vec2| isometry * vec2),
color,
);
}
}
// boxed polyline 2d
impl<Config, Clear> GizmoPrimitive2d<BoxedPolyline2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &BoxedPolyline2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.map(|vec2| isometry * vec2),
color,
);
}
}
// triangle 2d
impl<Config, Clear> GizmoPrimitive2d<Triangle2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Triangle2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
let positions = [a, b, c, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// rectangle 2d
impl<Config, Clear> GizmoPrimitive2d<Rectangle> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Rectangle,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
primitive.half_size.x * sign_x,
primitive.half_size.y * sign_y,
)
});
let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// polygon 2d
impl<const N: usize, Config, Clear> GizmoPrimitive2d<Polygon<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Polygon<N>,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// Check if the polygon needs a closing point
let closing_point = {
let first = primitive.vertices.first();
(primitive.vertices.last() != first)
.then_some(first)
.flatten()
.cloned()
};
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.chain(closing_point)
.map(|vec2| isometry * vec2),
color,
);
}
}
// boxed polygon 2d
impl<Config, Clear> GizmoPrimitive2d<BoxedPolygon> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &BoxedPolygon,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let closing_point = {
let first = primitive.vertices.first();
(primitive.vertices.last() != first)
.then_some(first)
.flatten()
.cloned()
};
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.chain(closing_point)
.map(|vec2| isometry * vec2),
color,
);
}
}
// regular polygon 2d
impl<Config, Clear> GizmoPrimitive2d<RegularPolygon> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &RegularPolygon,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let points = (0..=primitive.sides)
.map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))
.map(|vec2| isometry * vec2);
self.linestrip_2d(points, color);
}
}

View File

@@ -0,0 +1,965 @@
//! A module for rendering each of the 3D [`bevy_math::primitives`] with [`GizmoBuffer`].
use super::helpers::*;
use bevy_color::Color;
use bevy_math::{
primitives::{
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d,
Polyline3d, Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d,
},
Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3,
};
use crate::{circles::SphereBuilder, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
const DEFAULT_RESOLUTION: u32 = 5;
// length used to simulate infinite lines
const INFINITE_LEN: f32 = 10_000.0;
/// A trait for rendering 3D geometric primitives (`P`) with [`GizmoBuffer`].
pub trait GizmoPrimitive3d<P: Primitive3d> {
/// The output of `primitive_3d`. This is a builder to set non-default values.
type Output<'a>
where
Self: 'a;
/// Renders a 3D primitive with its associated details.
fn primitive_3d(
&mut self,
primitive: &P,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
// direction 3d
impl<Config, Clear> GizmoPrimitive3d<Dir3> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Dir3,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let start = Vec3::ZERO;
let end = primitive.as_vec3();
self.arrow(isometry * start, isometry * end, color);
}
}
// sphere
impl<Config, Clear> GizmoPrimitive3d<Sphere> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= SphereBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Sphere,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.sphere(isometry, primitive.radius, color)
}
}
// plane 3d
/// Builder for configuring the drawing options of [`Plane3d`].
pub struct Plane3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Direction of the normal orthogonal to the plane
normal: Dir3,
isometry: Isometry3d,
// Color of the plane
color: Color,
// Defines the amount of cells in the x and y axes
cell_count: UVec2,
// Defines the distance between cells along the x and y axes
spacing: Vec2,
}
impl<Config, Clear> Plane3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of cells in the x and y axes direction.
pub fn cell_count(mut self, cell_count: UVec2) -> Self {
self.cell_count = cell_count;
self
}
/// Set the distance between cells along the x and y axes.
pub fn spacing(mut self, spacing: Vec2) -> Self {
self.spacing = spacing;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Plane3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Plane3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Plane3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Plane3dBuilder {
gizmos: self,
normal: primitive.normal,
isometry: isometry.into(),
color: color.into(),
cell_count: UVec2::splat(3),
spacing: Vec2::splat(1.0),
}
}
}
impl<Config, Clear> Drop for Plane3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
self.gizmos
.primitive_3d(&self.normal, self.isometry, self.color);
// the default orientation of the grid is Z-up
let rot = Quat::from_rotation_arc(Vec3::Z, self.normal.as_vec3());
self.gizmos.grid(
Isometry3d::new(self.isometry.translation, self.isometry.rotation * rot),
self.cell_count,
self.spacing,
self.color,
);
}
}
// line 3d
impl<Config, Clear> GizmoPrimitive3d<Line3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Line3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
let direction = primitive.direction.as_vec3();
self.arrow(isometry * Vec3::ZERO, isometry * direction, color);
let [start, end] = [1.0, -1.0]
.map(|sign| sign * INFINITE_LEN)
.map(|length| primitive.direction * length)
.map(|offset| isometry * offset);
self.line(start, end, color);
}
}
// segment 3d
impl<Config, Clear> GizmoPrimitive3d<Segment3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Segment3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let transformed = primitive.transformed(isometry);
self.line(transformed.point1(), transformed.point2(), color);
}
}
// polyline 3d
impl<const N: usize, Config, Clear> GizmoPrimitive3d<Polyline3d<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Polyline3d<N>,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(primitive.vertices.map(|vec3| isometry * vec3), color);
}
}
// boxed polyline 3d
impl<Config, Clear> GizmoPrimitive3d<BoxedPolyline3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &BoxedPolyline3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(
primitive
.vertices
.iter()
.copied()
.map(|vec3| isometry * vec3),
color,
);
}
}
// triangle 3d
impl<Config, Clear> GizmoPrimitive3d<Triangle3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Triangle3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
self.linestrip([a, b, c, a].map(|vec3| isometry * vec3), color);
}
}
// cuboid
impl<Config, Clear> GizmoPrimitive3d<Cuboid> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cuboid,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// transform the points from the reference unit cube to the cuboid coords
let vertices @ [a, b, c, d, e, f, g, h] = [
[1.0, 1.0, 1.0],
[-1.0, 1.0, 1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, -1.0],
[1.0, -1.0, -1.0],
]
.map(Vec3::from)
.map(|vec3| vec3 * primitive.half_size)
.map(|vec3| isometry * vec3);
// lines for the upper rectangle of the cuboid
let upper = [a, b, c, d]
.into_iter()
.zip([a, b, c, d].into_iter().cycle().skip(1));
// lines for the lower rectangle of the cuboid
let lower = [e, f, g, h]
.into_iter()
.zip([e, f, g, h].into_iter().cycle().skip(1));
// lines connecting upper and lower rectangles of the cuboid
let connections = vertices.into_iter().zip(vertices.into_iter().skip(4));
let color = color.into();
upper
.chain(lower)
.chain(connections)
.for_each(|(start, end)| {
self.line(start, end, color);
});
}
}
// cylinder 3d
/// Builder for configuring the drawing options of [`Cylinder`].
pub struct Cylinder3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the cylinder
radius: f32,
// Half height of the cylinder
half_height: f32,
isometry: Isometry3d,
// Color of the cylinder
color: Color,
// Number of lines used to approximate the cylinder geometry
resolution: u32,
}
impl<Config, Clear> Cylinder3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the top and bottom of the cylinder geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Cylinder> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cylinder3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cylinder,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cylinder3dBuilder {
gizmos: self,
radius: primitive.radius,
half_height: primitive.half_height,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Cylinder3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
self.gizmos
.primitive_3d(
&ConicalFrustum {
radius_top: self.radius,
radius_bottom: self.radius,
height: self.half_height * 2.0,
},
self.isometry,
self.color,
)
.resolution(self.resolution);
}
}
// capsule 3d
/// Builder for configuring the drawing options of [`Capsule3d`].
pub struct Capsule3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the capsule
radius: f32,
// Half length of the capsule
half_length: f32,
isometry: Isometry3d,
// Color of the capsule
color: Color,
// Number of lines used to approximate the capsule geometry
resolution: u32,
}
impl<Config, Clear> Capsule3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the capsule geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Capsule3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Capsule3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Capsule3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Capsule3dBuilder {
gizmos: self,
radius: primitive.radius,
half_length: primitive.half_length,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Capsule3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let [upper_apex, lower_apex] = [-1.0, 1.0]
.map(|sign| Vec3::Y * sign * (self.half_length + self.radius))
.map(|vec3| self.isometry * vec3);
let [upper_center, lower_center] = [-1.0, 1.0]
.map(|sign| Vec3::Y * sign * self.half_length)
.map(|vec3| self.isometry * vec3);
let [upper_points, lower_points] = [-1.0, 1.0]
.map(|sign| Vec3::Y * sign * self.half_length)
.map(|vec3| {
circle_coordinates_closed(self.radius, self.resolution)
.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + vec3)
.map(|vec3| self.isometry * vec3)
.collect::<Vec<_>>()
});
upper_points.iter().skip(1).copied().for_each(|start| {
self.gizmos
.short_arc_3d_between(upper_center, start, upper_apex, self.color);
});
lower_points.iter().skip(1).copied().for_each(|start| {
self.gizmos
.short_arc_3d_between(lower_center, start, lower_apex, self.color);
});
let circle_rotation = self
.isometry
.rotation
.mul_quat(Quat::from_rotation_x(core::f32::consts::FRAC_PI_2));
self.gizmos.circle(
Isometry3d::new(upper_center, circle_rotation),
self.radius,
self.color,
);
self.gizmos.circle(
Isometry3d::new(lower_center, circle_rotation),
self.radius,
self.color,
);
let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);
connection_lines.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
}
}
// cone 3d
/// Builder for configuring the drawing options of [`Cone`].
pub struct Cone3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the cone
radius: f32,
// Height of the cone
height: f32,
isometry: Isometry3d,
// Color of the cone
color: Color,
// Number of lines used to approximate the cone base geometry
base_resolution: u32,
// Number of lines used to approximate the cone height geometry
height_resolution: u32,
}
impl<Config, Clear> Cone3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the cone geometry for its base and height.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self.height_resolution = resolution;
self
}
/// Set the number of lines used to approximate the height of the cone geometry.
///
/// `resolution` should be a multiple of the value passed to [`Self::height_resolution`]
/// for the height to connect properly with the base.
pub fn base_resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self
}
/// Set the number of lines used to approximate the height of the cone geometry.
///
/// `resolution` should be a divisor of the value passed to [`Self::base_resolution`]
/// for the height to connect properly with the base.
pub fn height_resolution(mut self, resolution: u32) -> Self {
self.height_resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Cone> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cone3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cone,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cone3dBuilder {
gizmos: self,
radius: primitive.radius,
height: primitive.height,
isometry: isometry.into(),
color: color.into(),
base_resolution: DEFAULT_RESOLUTION,
height_resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Cone3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let half_height = self.height * 0.5;
let apex = self.isometry * (Vec3::Y * half_height);
let circle_center = half_height * Vec3::NEG_Y;
let circle_coords = circle_coordinates_closed(self.radius, self.height_resolution)
.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + circle_center)
.map(|vec3| self.isometry * vec3)
.collect::<Vec<_>>();
// connections to apex
circle_coords
.iter()
.skip(1)
.map(|vec3| (*vec3, apex))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
// base circle
circle_coords
.windows(2)
.map(|win| (win[0], win[1]))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
}
}
// conical frustum 3d
/// Builder for configuring the drawing options of [`ConicalFrustum`].
pub struct ConicalFrustum3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the top circle
radius_top: f32,
// Radius of the bottom circle
radius_bottom: f32,
// Height of the conical frustum
height: f32,
isometry: Isometry3d,
// Color of the conical frustum
color: Color,
// Number of lines used to approximate the curved surfaces
resolution: u32,
}
impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the curved surfaces.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<ConicalFrustum> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ConicalFrustum3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &ConicalFrustum,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
ConicalFrustum3dBuilder {
gizmos: self,
radius_top: primitive.radius_top,
radius_bottom: primitive.radius_bottom,
height: primitive.height,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for ConicalFrustum3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let half_height = self.height * 0.5;
let [upper_points, lower_points] = [(-1.0, self.radius_bottom), (1.0, self.radius_top)]
.map(|(sign, radius)| {
let translation = Vec3::Y * sign * half_height;
circle_coordinates_closed(radius, self.resolution)
.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
.map(|vec3| self.isometry * vec3)
.collect::<Vec<_>>()
});
let upper_lines = upper_points.windows(2).map(|win| (win[0], win[1]));
let lower_lines = lower_points.windows(2).map(|win| (win[0], win[1]));
upper_lines.chain(lower_lines).for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);
connection_lines.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
}
}
// torus 3d
/// Builder for configuring the drawing options of [`Torus`].
pub struct Torus3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the minor circle (tube)
minor_radius: f32,
// Radius of the major circle (ring)
major_radius: f32,
isometry: Isometry3d,
// Color of the torus
color: Color,
// Number of lines in the minor (tube) direction
minor_resolution: u32,
// Number of lines in the major (ring) direction
major_resolution: u32,
}
impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines in the minor (tube) direction.
pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
self.minor_resolution = minor_resolution;
self
}
/// Set the number of lines in the major (ring) direction.
pub fn major_resolution(mut self, major_resolution: u32) -> Self {
self.major_resolution = major_resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Torus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Torus3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Torus,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Torus3dBuilder {
gizmos: self,
minor_radius: primitive.minor_radius,
major_radius: primitive.major_radius,
isometry: isometry.into(),
color: color.into(),
minor_resolution: DEFAULT_RESOLUTION,
major_resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Torus3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// draw 4 circles with major_radius
let [inner, outer, top, bottom] = [
(self.major_radius - self.minor_radius, 0.0),
(self.major_radius + self.minor_radius, 0.0),
(self.major_radius, self.minor_radius),
(self.major_radius, -self.minor_radius),
]
.map(|(radius, height)| {
let translation = height * Vec3::Y;
circle_coordinates_closed(radius, self.major_resolution)
.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)
.map(|vec3| self.isometry * vec3)
.collect::<Vec<_>>()
});
[&inner, &outer, &top, &bottom]
.iter()
.flat_map(|points| points.windows(2).map(|win| (win[0], win[1])))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
inner
.into_iter()
.zip(top)
.zip(outer)
.zip(bottom)
.flat_map(|(((inner, top), outer), bottom)| {
let center = (inner + top + outer + bottom) * 0.25;
[(inner, top), (top, outer), (outer, bottom), (bottom, inner)]
.map(|(start, end)| (start, end, center))
})
.for_each(|(from, to, center)| {
self.gizmos
.short_arc_3d_between(center, from, to, self.color)
.resolution(self.minor_resolution);
});
}
}
// tetrahedron
impl<Config, Clear> GizmoPrimitive3d<Tetrahedron> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Tetrahedron,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3);
let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)];
let color = color.into();
lines.into_iter().for_each(|(start, end)| {
self.line(start, end, color);
});
}
}

View File

@@ -0,0 +1,43 @@
use core::f32::consts::TAU;
use bevy_math::{ops, Vec2};
/// Calculates the `nth` coordinate of a circle.
///
/// Given a circle's radius and its resolution, this function computes the position
/// of the `nth` point along the circumference of the circle. The rotation starts at `(0.0, radius)`
/// and proceeds counter-clockwise.
pub(crate) fn single_circle_coordinate(radius: f32, resolution: u32, nth_point: u32) -> Vec2 {
let angle = nth_point as f32 * TAU / resolution as f32;
let (x, y) = ops::sin_cos(angle);
Vec2::new(x, y) * radius
}
/// Generates an iterator over the coordinates of a circle.
///
/// The coordinates form an open circle, meaning the first and last points aren't the same.
///
/// This function creates an iterator that yields the positions of points approximating a
/// circle with the given radius, divided into linear segments. The iterator produces `resolution`
/// number of points.
pub(crate) fn circle_coordinates(radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..)
.map(move |p| single_circle_coordinate(radius, resolution, p))
.take(resolution as usize)
}
/// Generates an iterator over the coordinates of a circle.
///
/// The coordinates form a closed circle, meaning the first and last points are the same.
///
/// This function creates an iterator that yields the positions of points approximating a
/// circle with the given radius, divided into linear segments. The iterator produces `resolution`
/// number of points.
pub(crate) fn circle_coordinates_closed(
radius: f32,
resolution: u32,
) -> impl Iterator<Item = Vec2> {
circle_coordinates(radius, resolution).chain(core::iter::once(single_circle_coordinate(
radius, resolution, resolution,
)))
}

View File

@@ -0,0 +1,5 @@
//! A module for rendering each of the 2D and 3D [`bevy_math::primitives`] with [`crate::prelude::Gizmos`].
pub mod dim2;
pub mod dim3;
pub(crate) mod helpers;

160
vendor/bevy_gizmos/src/retained.rs vendored Normal file
View File

@@ -0,0 +1,160 @@
//! This module is for 'retained' alternatives to the 'immediate mode' [`Gizmos`](crate::gizmos::Gizmos) system parameter.
use core::ops::{Deref, DerefMut};
use bevy_asset::Handle;
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::components::Transform;
#[cfg(feature = "bevy_render")]
use {
crate::{config::GizmoLineJoint, LineGizmoUniform},
bevy_ecs::{
entity::Entity,
system::{Commands, Local, Query},
},
bevy_render::{view::RenderLayers, Extract},
bevy_transform::components::GlobalTransform,
};
use crate::{
config::{ErasedGizmoConfigGroup, GizmoLineConfig},
gizmos::GizmoBuffer,
GizmoAsset,
};
impl Deref for GizmoAsset {
type Target = GizmoBuffer<ErasedGizmoConfigGroup, ()>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl DerefMut for GizmoAsset {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
/// A component that draws the gizmos of a [`GizmoAsset`].
///
/// When drawing a greater number of static lines a [`Gizmo`] component can
/// have far better performance than the [`Gizmos`] system parameter,
/// but the system parameter will perform better for smaller lines that update often.
///
/// ## Example
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_gizmos::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_color::palettes::css::*;
/// # use bevy_utils::default;
/// # use bevy_math::prelude::*;
/// fn system(
/// mut commands: Commands,
/// mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
/// ) {
/// let mut gizmo = GizmoAsset::default();
///
/// gizmo.sphere(Vec3::ZERO, 1., RED);
///
/// commands.spawn(Gizmo {
/// handle: gizmo_assets.add(gizmo),
/// line_config: GizmoLineConfig {
/// width: 4.,
/// ..default()
/// },
/// ..default()
/// });
/// }
/// ```
///
/// [`Gizmos`]: crate::gizmos::Gizmos
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect(Component, Clone, Default)]
#[require(Transform)]
pub struct Gizmo {
/// The handle to the gizmo to draw.
pub handle: Handle<GizmoAsset>,
/// The line specific configuration for this gizmo.
pub line_config: GizmoLineConfig,
/// How closer to the camera than real geometry the gizmo should be.
///
/// In 2D this setting has no effect and is effectively always -1.
///
/// Value between -1 and 1 (inclusive).
/// * 0 means that there is no change to the gizmo position when rendering
/// * 1 means it is furthest away from camera as possible
/// * -1 means that it will always render in front of other things.
///
/// This is typically useful if you are drawing wireframes on top of polygons
/// and your wireframe is z-fighting (flickering on/off) with your main model.
/// You would set this value to a negative number close to 0.
pub depth_bias: f32,
}
#[cfg(feature = "bevy_render")]
pub(crate) fn extract_linegizmos(
mut commands: Commands,
mut previous_len: Local<usize>,
query: Extract<Query<(Entity, &Gizmo, &GlobalTransform, Option<&RenderLayers>)>>,
) {
use bevy_math::Affine3;
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
use bevy_utils::once;
use tracing::warn;
use crate::config::GizmoLineStyle;
let mut values = Vec::with_capacity(*previous_len);
for (entity, gizmo, transform, render_layers) in &query {
let joints_resolution = if let GizmoLineJoint::Round(resolution) = gizmo.line_config.joints
{
resolution
} else {
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = gizmo.line_config.style
{
if gap_scale <= 0.0 {
once!(warn!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero"));
}
if line_scale <= 0.0 {
once!(warn!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero"));
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
values.push((
LineGizmoUniform {
world_from_local: Affine3::from(&transform.affine()).to_transpose(),
line_width: gizmo.line_config.width,
depth_bias: gizmo.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
crate::config::GizmoMeshConfig {
line_perspective: gizmo.line_config.perspective,
line_style: gizmo.line_config.style,
line_joints: gizmo.line_config.joints,
render_layers: render_layers.cloned().unwrap_or_default(),
handle: gizmo.handle.clone_weak(),
},
MainEntity::from(entity),
TemporaryRenderEntity,
));
}
*previous_len = values.len();
commands.spawn_batch(values);
}

400
vendor/bevy_gizmos/src/rounded_box.rs vendored Normal file
View File

@@ -0,0 +1,400 @@
//! Additional [`GizmoBuffer`] Functions -- Rounded cuboids and rectangles
//!
//! Includes the implementation of [`GizmoBuffer::rounded_rect`], [`GizmoBuffer::rounded_rect_2d`] and [`GizmoBuffer::rounded_cuboid`].
//! and assorted support items.
use core::f32::consts::FRAC_PI_2;
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Quat, Vec2, Vec3};
use bevy_transform::components::Transform;
/// A builder returned by [`GizmoBuffer::rounded_rect`] and [`GizmoBuffer::rounded_rect_2d`]
pub struct RoundedRectBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
size: Vec2,
gizmos: &'a mut GizmoBuffer<Config, Clear>,
config: RoundedBoxConfig,
}
/// A builder returned by [`GizmoBuffer::rounded_cuboid`]
pub struct RoundedCuboidBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
size: Vec3,
gizmos: &'a mut GizmoBuffer<Config, Clear>,
config: RoundedBoxConfig,
}
struct RoundedBoxConfig {
isometry: Isometry3d,
color: Color,
corner_radius: f32,
arc_resolution: u32,
}
impl<Config, Clear> RoundedRectBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the radius of the corners to be `corner_radius`.
/// The default corner radius is [min axis of size] / 10.0
pub fn corner_radius(mut self, corner_radius: f32) -> Self {
self.config.corner_radius = corner_radius;
self
}
/// Change the resolution of the arcs at the corners of the rectangle.
/// The default value is 8
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
self.config.arc_resolution = arc_resolution;
self
}
}
impl<Config, Clear> RoundedCuboidBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the radius of the edges to be `edge_radius`.
/// The default edge radius is [min axis of size] / 10.0
pub fn edge_radius(mut self, edge_radius: f32) -> Self {
self.config.corner_radius = edge_radius;
self
}
/// Change the resolution of the arcs at the edges of the cuboid.
/// The default value is 8
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
self.config.arc_resolution = arc_resolution;
self
}
}
impl<Config, Clear> Drop for RoundedRectBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let config = &self.config;
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
let mut outer_half_size = self.size.abs() / 2.0;
let inner_half_size =
(outer_half_size - Vec2::splat(config.corner_radius.abs())).max(Vec2::ZERO);
let corner_radius = (outer_half_size - inner_half_size).min_element();
let mut inner_half_size = outer_half_size - Vec2::splat(corner_radius);
if config.corner_radius < 0. {
core::mem::swap(&mut outer_half_size, &mut inner_half_size);
}
// Handle cases where the rectangle collapses into simpler shapes
if outer_half_size.x * outer_half_size.y == 0. {
self.gizmos.line(
config.isometry * -outer_half_size.extend(0.),
config.isometry * outer_half_size.extend(0.),
config.color,
);
return;
}
if corner_radius == 0. {
self.gizmos.rect(config.isometry, self.size, config.color);
return;
}
let vertices = [
// top right
Vec3::new(inner_half_size.x, outer_half_size.y, 0.),
Vec3::new(inner_half_size.x, inner_half_size.y, 0.),
Vec3::new(outer_half_size.x, inner_half_size.y, 0.),
// bottom right
Vec3::new(outer_half_size.x, -inner_half_size.y, 0.),
Vec3::new(inner_half_size.x, -inner_half_size.y, 0.),
Vec3::new(inner_half_size.x, -outer_half_size.y, 0.),
// bottom left
Vec3::new(-inner_half_size.x, -outer_half_size.y, 0.),
Vec3::new(-inner_half_size.x, -inner_half_size.y, 0.),
Vec3::new(-outer_half_size.x, -inner_half_size.y, 0.),
// top left
Vec3::new(-outer_half_size.x, inner_half_size.y, 0.),
Vec3::new(-inner_half_size.x, inner_half_size.y, 0.),
Vec3::new(-inner_half_size.x, outer_half_size.y, 0.),
]
.map(|vec3| config.isometry * vec3);
for chunk in vertices.chunks_exact(3) {
self.gizmos
.short_arc_3d_between(chunk[1], chunk[0], chunk[2], config.color)
.resolution(config.arc_resolution);
}
let edges = if config.corner_radius > 0. {
[
(vertices[2], vertices[3]),
(vertices[5], vertices[6]),
(vertices[8], vertices[9]),
(vertices[11], vertices[0]),
]
} else {
[
(vertices[0], vertices[5]),
(vertices[3], vertices[8]),
(vertices[6], vertices[11]),
(vertices[9], vertices[2]),
]
};
for (start, end) in edges {
self.gizmos.line(start, end, config.color);
}
}
}
impl<Config, Clear> Drop for RoundedCuboidBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let config = &self.config;
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
let outer_half_size = self.size.abs() / 2.0;
let inner_half_size =
(outer_half_size - Vec3::splat(config.corner_radius.abs())).max(Vec3::ZERO);
let mut edge_radius = (outer_half_size - inner_half_size).min_element();
let inner_half_size = outer_half_size - Vec3::splat(edge_radius);
edge_radius *= config.corner_radius.signum();
// Handle cases where the rounded cuboid collapses into simpler shapes
if edge_radius == 0.0 {
let transform = Transform::from_translation(config.isometry.translation.into())
.with_rotation(config.isometry.rotation)
.with_scale(self.size);
self.gizmos.cuboid(transform, config.color);
return;
}
let rects = [
(
Vec3::X,
Vec2::new(self.size.z, self.size.y),
Quat::from_rotation_y(FRAC_PI_2),
),
(
Vec3::Y,
Vec2::new(self.size.x, self.size.z),
Quat::from_rotation_x(FRAC_PI_2),
),
(Vec3::Z, Vec2::new(self.size.x, self.size.y), Quat::IDENTITY),
];
for (position, size, rotation) in rects {
let local_position = position * inner_half_size;
self.gizmos
.rounded_rect(
config.isometry * Isometry3d::new(local_position, rotation),
size,
config.color,
)
.arc_resolution(config.arc_resolution)
.corner_radius(edge_radius);
self.gizmos
.rounded_rect(
config.isometry * Isometry3d::new(-local_position, rotation),
size,
config.color,
)
.arc_resolution(config.arc_resolution)
.corner_radius(edge_radius);
}
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a wireframe rectangle with rounded corners in 3D.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the rectangle.
/// - the translation specifies the center of the rectangle
/// - defines orientation of the rectangle, by default we assume the rectangle is contained in
/// a plane parallel to the XY plane.
/// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box.
/// - `color`: color of the rectangle
///
/// # Builder methods
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_rect(
/// Isometry3d::IDENTITY,
/// Vec2::ONE,
/// GREEN
/// )
/// .corner_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_rect(
&mut self,
isometry: impl Into<Isometry3d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, Config, Clear> {
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedRectBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
},
size,
}
}
/// Draw a wireframe rectangle with rounded corners in 2D.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the rectangle.
/// - the translation specifies the center of the rectangle
/// - defines orientation of the rectangle, by default we assume the rectangle aligned with all axes.
/// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box.
/// - `color`: color of the rectangle
///
/// # Builder methods
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_rect_2d(
/// Isometry2d::IDENTITY,
/// Vec2::ONE,
/// GREEN
/// )
/// .corner_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_rect_2d(
&mut self,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, Config, Clear> {
let isometry = isometry.into();
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedRectBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry: Isometry3d::new(
isometry.translation.extend(0.0),
Quat::from_rotation_z(isometry.rotation.as_radians()),
),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
},
size,
}
}
/// Draw a wireframe cuboid with rounded corners in 3D.
///
/// This should be called for each frame the cuboid needs to be rendered.
///
/// # Arguments
///
/// - `isometry` defines the translation and rotation of the cuboid.
/// - the translation specifies the center of the cuboid
/// - defines orientation of the cuboid, by default we assume the cuboid aligned with all axes.
/// - `size`: defines the size of the cuboid. This refers to the 'outer size', similar to a bounding box.
/// - `color`: color of the cuboid
///
/// # Builder methods
///
/// - The edge radius can be adjusted with the `.edge_radius(...)` method.
/// - The resolution of the arcs at each edge (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_cuboid(
/// Isometry3d::IDENTITY,
/// Vec3::ONE,
/// GREEN
/// )
/// .edge_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_cuboid(
&mut self,
isometry: impl Into<Isometry3d>,
size: Vec3,
color: impl Into<Color>,
) -> RoundedCuboidBuilder<'_, Config, Clear> {
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedCuboidBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
},
size,
}
}
}
const DEFAULT_ARC_RESOLUTION: u32 = 8;
const DEFAULT_CORNER_RADIUS: f32 = 0.1;