Merge branch 'main' into proper-json-schema
This commit is contained in:
commit
22eae74a38
44
crates/bevy_camera/Cargo.toml
Normal file
44
crates/bevy_camera/Cargo.toml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
[package]
|
||||||
|
name = "bevy_camera"
|
||||||
|
version = "0.17.0-dev"
|
||||||
|
edition = "2024"
|
||||||
|
description = "Provides a camera abstraction for Bevy Engine"
|
||||||
|
homepage = "https://bevy.org"
|
||||||
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
keywords = ["bevy"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# bevy
|
||||||
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||||
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
|
||||||
|
"serialize",
|
||||||
|
] }
|
||||||
|
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||||
|
|
||||||
|
# other
|
||||||
|
wgpu-types = { version = "25", default-features = false }
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
thiserror = { version = "2", default-features = false }
|
||||||
|
downcast-rs = { version = "2", default-features = false, features = ["std"] }
|
||||||
|
derive_more = { version = "2", default-features = false, features = ["from"] }
|
||||||
|
smallvec = { version = "1.11", features = ["const_new"] }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
||||||
|
all-features = true
|
||||||
176
crates/bevy_camera/LICENSE-APACHE
Normal file
176
crates/bevy_camera/LICENSE-APACHE
Normal 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
crates/bevy_camera/LICENSE-MIT
Normal file
19
crates/bevy_camera/LICENSE-MIT
Normal 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.
|
||||||
@ -1,53 +1,21 @@
|
|||||||
#![expect(
|
use crate::primitives::Frustum;
|
||||||
clippy::module_inception,
|
|
||||||
reason = "The parent module contains all things viewport-related, while this module handles cameras as a component. However, a rename/refactor which should clear up this lint is being discussed; see #17196."
|
use super::{
|
||||||
)]
|
visibility::{Visibility, VisibleEntities},
|
||||||
use super::{ClearColorConfig, Projection};
|
ClearColorConfig,
|
||||||
use crate::{
|
|
||||||
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
|
|
||||||
camera::{ManualTextureViewHandle, ManualTextureViews},
|
|
||||||
primitives::Frustum,
|
|
||||||
render_asset::RenderAssets,
|
|
||||||
render_graph::{InternedRenderSubGraph, RenderSubGraph},
|
|
||||||
render_resource::TextureView,
|
|
||||||
sync_world::{RenderEntity, SyncToRenderWorld},
|
|
||||||
texture::GpuImage,
|
|
||||||
view::{
|
|
||||||
ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing, RenderLayers,
|
|
||||||
RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset, Visibility, VisibleEntities,
|
|
||||||
},
|
|
||||||
Extract,
|
|
||||||
};
|
|
||||||
use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
|
||||||
use bevy_derive::{Deref, DerefMut};
|
|
||||||
use bevy_ecs::{
|
|
||||||
change_detection::DetectChanges,
|
|
||||||
component::Component,
|
|
||||||
entity::{ContainsEntity, Entity},
|
|
||||||
event::EventReader,
|
|
||||||
lifecycle::HookContext,
|
|
||||||
prelude::With,
|
|
||||||
query::Has,
|
|
||||||
reflect::ReflectComponent,
|
|
||||||
resource::Resource,
|
|
||||||
system::{Commands, Query, Res, ResMut},
|
|
||||||
world::DeferredWorld,
|
|
||||||
};
|
};
|
||||||
|
use bevy_asset::Handle;
|
||||||
|
use bevy_derive::Deref;
|
||||||
|
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
||||||
use bevy_image::Image;
|
use bevy_image::Image;
|
||||||
use bevy_math::{ops, vec2, Dir3, FloatOrd, Mat4, Ray3d, Rect, URect, UVec2, UVec4, Vec2, Vec3};
|
use bevy_math::{ops, Dir3, FloatOrd, Mat4, Ray3d, Rect, URect, UVec2, Vec2, Vec3};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render_macros::ExtractComponent;
|
|
||||||
use bevy_transform::components::{GlobalTransform, Transform};
|
use bevy_transform::components::{GlobalTransform, Transform};
|
||||||
use bevy_window::{
|
use bevy_window::WindowRef;
|
||||||
NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowRef, WindowResized,
|
|
||||||
WindowScaleFactorChanged,
|
|
||||||
};
|
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use derive_more::derive::From;
|
use derive_more::derive::From;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::warn;
|
use wgpu_types::{BlendState, TextureUsages};
|
||||||
use wgpu::{BlendState, TextureFormat, TextureUsages};
|
|
||||||
|
|
||||||
/// Render viewport configuration for the [`Camera`] component.
|
/// Render viewport configuration for the [`Camera`] component.
|
||||||
///
|
///
|
||||||
@ -124,6 +92,19 @@ impl Viewport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Override the resolution a 3d camera's main pass is rendered at.
|
||||||
|
///
|
||||||
|
/// Does not affect post processing.
|
||||||
|
///
|
||||||
|
/// ## Usage
|
||||||
|
///
|
||||||
|
/// * Insert this component on a 3d camera entity in the render world.
|
||||||
|
/// * The resolution override must be smaller than the camera's viewport size.
|
||||||
|
/// * The resolution override is specified in physical pixels.
|
||||||
|
#[derive(Component, Reflect, Deref)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct MainPassResolutionOverride(pub UVec2);
|
||||||
|
|
||||||
/// Settings to define a camera sub view.
|
/// Settings to define a camera sub view.
|
||||||
///
|
///
|
||||||
/// When [`Camera::sub_camera_view`] is `Some`, only the sub-section of the
|
/// When [`Camera::sub_camera_view`] is `Some`, only the sub-section of the
|
||||||
@ -188,11 +169,11 @@ pub struct RenderTargetInfo {
|
|||||||
/// Holds internally computed [`Camera`] values.
|
/// Holds internally computed [`Camera`] values.
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct ComputedCameraValues {
|
pub struct ComputedCameraValues {
|
||||||
clip_from_view: Mat4,
|
pub clip_from_view: Mat4,
|
||||||
target_info: Option<RenderTargetInfo>,
|
pub target_info: Option<RenderTargetInfo>,
|
||||||
// size of the `Viewport`
|
// size of the `Viewport`
|
||||||
old_viewport_size: Option<UVec2>,
|
pub old_viewport_size: Option<UVec2>,
|
||||||
old_sub_camera_view: Option<SubCameraView>,
|
pub old_sub_camera_view: Option<SubCameraView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How much energy a `Camera3d` absorbs from incoming light.
|
/// How much energy a `Camera3d` absorbs from incoming light.
|
||||||
@ -303,7 +284,7 @@ impl Default for PhysicalCameraParameters {
|
|||||||
pub enum ViewportConversionError {
|
pub enum ViewportConversionError {
|
||||||
/// The pre-computed size of the viewport was not available.
|
/// The pre-computed size of the viewport was not available.
|
||||||
///
|
///
|
||||||
/// This may be because the `Camera` was just created and [`camera_system`] has not been executed
|
/// This may be because the `Camera` was just created and `camera_system` has not been executed
|
||||||
/// yet, or because the [`RenderTarget`] is misconfigured in one of the following ways:
|
/// yet, or because the [`RenderTarget`] is misconfigured in one of the following ways:
|
||||||
/// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
|
/// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
|
||||||
/// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
|
/// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
|
||||||
@ -322,8 +303,8 @@ pub enum ViewportConversionError {
|
|||||||
#[error("computed coordinate beyond `Camera`'s far plane")]
|
#[error("computed coordinate beyond `Camera`'s far plane")]
|
||||||
PastFarPlane,
|
PastFarPlane,
|
||||||
/// The Normalized Device Coordinates could not be computed because the `camera_transform`, the
|
/// The Normalized Device Coordinates could not be computed because the `camera_transform`, the
|
||||||
/// `world_position`, or the projection matrix defined by [`Projection`] contained `NAN` (see
|
/// `world_position`, or the projection matrix defined by [`Projection`](super::projection::Projection)
|
||||||
/// [`world_to_ndc`][Camera::world_to_ndc] and [`ndc_to_world`][Camera::ndc_to_world]).
|
/// contained `NAN` (see [`world_to_ndc`][Camera::world_to_ndc] and [`ndc_to_world`][Camera::ndc_to_world]).
|
||||||
#[error("found NaN while computing NDC")]
|
#[error("found NaN while computing NDC")]
|
||||||
InvalidData,
|
InvalidData,
|
||||||
}
|
}
|
||||||
@ -336,7 +317,7 @@ pub enum ViewportConversionError {
|
|||||||
/// to transform the 3D objects into a 2D image, as well as the render target into which that image
|
/// to transform the 3D objects into a 2D image, as well as the render target into which that image
|
||||||
/// is produced.
|
/// is produced.
|
||||||
///
|
///
|
||||||
/// Note that a [`Camera`] needs a [`CameraRenderGraph`] to render anything.
|
/// Note that a [`Camera`] needs a `CameraRenderGraph` to render anything.
|
||||||
/// This is typically provided by adding a [`Camera2d`] or [`Camera3d`] component,
|
/// This is typically provided by adding a [`Camera2d`] or [`Camera3d`] component,
|
||||||
/// but custom render graphs can also be defined. Inserting a [`Camera`] with no render
|
/// but custom render graphs can also be defined. Inserting a [`Camera`] with no render
|
||||||
/// graph will emit an error at runtime.
|
/// graph will emit an error at runtime.
|
||||||
@ -345,15 +326,12 @@ pub enum ViewportConversionError {
|
|||||||
/// [`Camera3d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/core_3d/struct.Camera3d.html
|
/// [`Camera3d`]: https://docs.rs/bevy/latest/bevy/core_pipeline/core_3d/struct.Camera3d.html
|
||||||
#[derive(Component, Debug, Reflect, Clone)]
|
#[derive(Component, Debug, Reflect, Clone)]
|
||||||
#[reflect(Component, Default, Debug, Clone)]
|
#[reflect(Component, Default, Debug, Clone)]
|
||||||
#[component(on_add = warn_on_no_render_graph)]
|
|
||||||
#[require(
|
#[require(
|
||||||
Frustum,
|
Frustum,
|
||||||
CameraMainTextureUsages,
|
CameraMainTextureUsages,
|
||||||
VisibleEntities,
|
VisibleEntities,
|
||||||
Transform,
|
Transform,
|
||||||
Visibility,
|
Visibility
|
||||||
Msaa,
|
|
||||||
SyncToRenderWorld
|
|
||||||
)]
|
)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
/// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`].
|
/// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`].
|
||||||
@ -383,12 +361,6 @@ pub struct Camera {
|
|||||||
pub sub_camera_view: Option<SubCameraView>,
|
pub sub_camera_view: Option<SubCameraView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
|
|
||||||
if !world.entity(entity).contains::<CameraRenderGraph>() {
|
|
||||||
warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Camera {
|
impl Default for Camera {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -501,7 +473,7 @@ impl Camera {
|
|||||||
.map(|t: &RenderTargetInfo| t.scale_factor)
|
.map(|t: &RenderTargetInfo| t.scale_factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The projection matrix computed using this camera's [`Projection`].
|
/// The projection matrix computed using this camera's [`Projection`](super::projection::Projection).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn clip_from_view(&self) -> Mat4 {
|
pub fn clip_from_view(&self) -> Mat4 {
|
||||||
self.computed.clip_from_view
|
self.computed.clip_from_view
|
||||||
@ -666,7 +638,8 @@ impl Camera {
|
|||||||
/// To get the coordinates in the render target's viewport dimensions, you should use
|
/// To get the coordinates in the render target's viewport dimensions, you should use
|
||||||
/// [`world_to_viewport`](Self::world_to_viewport).
|
/// [`world_to_viewport`](Self::world_to_viewport).
|
||||||
///
|
///
|
||||||
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`Projection`] contain `NAN`.
|
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by
|
||||||
|
/// [`Projection`](super::projection::Projection) contain `NAN`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
@ -692,7 +665,8 @@ impl Camera {
|
|||||||
/// To get the world space coordinates with the viewport position, you should use
|
/// To get the world space coordinates with the viewport position, you should use
|
||||||
/// [`world_to_viewport`](Self::world_to_viewport).
|
/// [`world_to_viewport`](Self::world_to_viewport).
|
||||||
///
|
///
|
||||||
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by [`Projection`] contain `NAN`.
|
/// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by
|
||||||
|
/// [`Projection`](super::projection::Projection) contain `NAN`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
@ -754,27 +728,7 @@ impl Default for CameraOutputMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configures the [`RenderGraph`](crate::render_graph::RenderGraph) name assigned to be run for a given [`Camera`] entity.
|
/// The "target" that a [`Camera`] will render to. For example, this could be a `Window`
|
||||||
#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)]
|
|
||||||
#[reflect(opaque)]
|
|
||||||
#[reflect(Component, Debug, Clone)]
|
|
||||||
pub struct CameraRenderGraph(InternedRenderSubGraph);
|
|
||||||
|
|
||||||
impl CameraRenderGraph {
|
|
||||||
/// Creates a new [`CameraRenderGraph`] from any string-like type.
|
|
||||||
#[inline]
|
|
||||||
pub fn new<T: RenderSubGraph>(name: T) -> Self {
|
|
||||||
Self(name.intern())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the graph name.
|
|
||||||
#[inline]
|
|
||||||
pub fn set<T: RenderSubGraph>(&mut self, name: T) {
|
|
||||||
self.0 = name.intern();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The "target" that a [`Camera`] will render to. For example, this could be a [`Window`]
|
|
||||||
/// swapchain or an [`Image`].
|
/// swapchain or an [`Image`].
|
||||||
#[derive(Debug, Clone, Reflect, From)]
|
#[derive(Debug, Clone, Reflect, From)]
|
||||||
#[reflect(Clone)]
|
#[reflect(Clone)]
|
||||||
@ -788,6 +742,23 @@ pub enum RenderTarget {
|
|||||||
TextureView(ManualTextureViewHandle),
|
TextureView(ManualTextureViewHandle),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RenderTarget {
|
||||||
|
/// Get a handle to the render target's image,
|
||||||
|
/// or `None` if the render target is another variant.
|
||||||
|
pub fn as_image(&self) -> Option<&Handle<Image>> {
|
||||||
|
if let Self::Image(image_target) = self {
|
||||||
|
Some(&image_target.handle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A unique id that corresponds to a specific `ManualTextureView` in the `ManualTextureViews` collection.
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Component, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug, PartialEq, Hash, Clone)]
|
||||||
|
pub struct ManualTextureViewHandle(pub u32);
|
||||||
|
|
||||||
/// A render target that renders to an [`Image`].
|
/// A render target that renders to an [`Image`].
|
||||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
#[reflect(Clone, PartialEq, Hash)]
|
#[reflect(Clone, PartialEq, Hash)]
|
||||||
@ -820,254 +791,8 @@ impl Default for RenderTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalized version of the render target.
|
|
||||||
///
|
|
||||||
/// Once we have this we shouldn't need to resolve it down anymore.
|
|
||||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord, From)]
|
|
||||||
#[reflect(Clone, PartialEq, Hash)]
|
|
||||||
pub enum NormalizedRenderTarget {
|
|
||||||
/// Window to which the camera's view is rendered.
|
|
||||||
Window(NormalizedWindowRef),
|
|
||||||
/// Image to which the camera's view is rendered.
|
|
||||||
Image(ImageRenderTarget),
|
|
||||||
/// Texture View to which the camera's view is rendered.
|
|
||||||
/// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
|
|
||||||
TextureView(ManualTextureViewHandle),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderTarget {
|
|
||||||
/// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
|
|
||||||
pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
|
|
||||||
match self {
|
|
||||||
RenderTarget::Window(window_ref) => window_ref
|
|
||||||
.normalize(primary_window)
|
|
||||||
.map(NormalizedRenderTarget::Window),
|
|
||||||
RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
|
|
||||||
RenderTarget::TextureView(id) => Some(NormalizedRenderTarget::TextureView(*id)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a handle to the render target's image,
|
|
||||||
/// or `None` if the render target is another variant.
|
|
||||||
pub fn as_image(&self) -> Option<&Handle<Image>> {
|
|
||||||
if let Self::Image(image_target) = self {
|
|
||||||
Some(&image_target.handle)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NormalizedRenderTarget {
|
|
||||||
pub fn get_texture_view<'a>(
|
|
||||||
&self,
|
|
||||||
windows: &'a ExtractedWindows,
|
|
||||||
images: &'a RenderAssets<GpuImage>,
|
|
||||||
manual_texture_views: &'a ManualTextureViews,
|
|
||||||
) -> Option<&'a TextureView> {
|
|
||||||
match self {
|
|
||||||
NormalizedRenderTarget::Window(window_ref) => windows
|
|
||||||
.get(&window_ref.entity())
|
|
||||||
.and_then(|window| window.swap_chain_texture_view.as_ref()),
|
|
||||||
NormalizedRenderTarget::Image(image_target) => images
|
|
||||||
.get(&image_target.handle)
|
|
||||||
.map(|image| &image.texture_view),
|
|
||||||
NormalizedRenderTarget::TextureView(id) => {
|
|
||||||
manual_texture_views.get(id).map(|tex| &tex.texture_view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the [`TextureFormat`] of this render target, if it exists.
|
|
||||||
pub fn get_texture_format<'a>(
|
|
||||||
&self,
|
|
||||||
windows: &'a ExtractedWindows,
|
|
||||||
images: &'a RenderAssets<GpuImage>,
|
|
||||||
manual_texture_views: &'a ManualTextureViews,
|
|
||||||
) -> Option<TextureFormat> {
|
|
||||||
match self {
|
|
||||||
NormalizedRenderTarget::Window(window_ref) => windows
|
|
||||||
.get(&window_ref.entity())
|
|
||||||
.and_then(|window| window.swap_chain_texture_format),
|
|
||||||
NormalizedRenderTarget::Image(image_target) => images
|
|
||||||
.get(&image_target.handle)
|
|
||||||
.map(|image| image.texture_format),
|
|
||||||
NormalizedRenderTarget::TextureView(id) => {
|
|
||||||
manual_texture_views.get(id).map(|tex| tex.format)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_render_target_info<'a>(
|
|
||||||
&self,
|
|
||||||
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
|
|
||||||
images: &Assets<Image>,
|
|
||||||
manual_texture_views: &ManualTextureViews,
|
|
||||||
) -> Option<RenderTargetInfo> {
|
|
||||||
match self {
|
|
||||||
NormalizedRenderTarget::Window(window_ref) => resolutions
|
|
||||||
.into_iter()
|
|
||||||
.find(|(entity, _)| *entity == window_ref.entity())
|
|
||||||
.map(|(_, window)| RenderTargetInfo {
|
|
||||||
physical_size: window.physical_size(),
|
|
||||||
scale_factor: window.resolution.scale_factor(),
|
|
||||||
}),
|
|
||||||
NormalizedRenderTarget::Image(image_target) => {
|
|
||||||
let image = images.get(&image_target.handle)?;
|
|
||||||
Some(RenderTargetInfo {
|
|
||||||
physical_size: image.size(),
|
|
||||||
scale_factor: image_target.scale_factor.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
NormalizedRenderTarget::TextureView(id) => {
|
|
||||||
manual_texture_views.get(id).map(|tex| RenderTargetInfo {
|
|
||||||
physical_size: tex.size,
|
|
||||||
scale_factor: 1.0,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this render target is contained in the given changed windows or images.
|
|
||||||
fn is_changed(
|
|
||||||
&self,
|
|
||||||
changed_window_ids: &HashSet<Entity>,
|
|
||||||
changed_image_handles: &HashSet<&AssetId<Image>>,
|
|
||||||
) -> bool {
|
|
||||||
match self {
|
|
||||||
NormalizedRenderTarget::Window(window_ref) => {
|
|
||||||
changed_window_ids.contains(&window_ref.entity())
|
|
||||||
}
|
|
||||||
NormalizedRenderTarget::Image(image_target) => {
|
|
||||||
changed_image_handles.contains(&image_target.handle.id())
|
|
||||||
}
|
|
||||||
NormalizedRenderTarget::TextureView(_) => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// System in charge of updating a [`Camera`] when its window or projection changes.
|
|
||||||
///
|
|
||||||
/// The system detects window creation, resize, and scale factor change events to update the camera
|
|
||||||
/// [`Projection`] if needed.
|
|
||||||
///
|
|
||||||
/// ## World Resources
|
|
||||||
///
|
|
||||||
/// [`Res<Assets<Image>>`](Assets<Image>) -- For cameras that render to an image, this resource is used to
|
|
||||||
/// inspect information about the render target. This system will not access any other image assets.
|
|
||||||
///
|
|
||||||
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
|
||||||
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
|
||||||
pub fn camera_system(
|
|
||||||
mut window_resized_events: EventReader<WindowResized>,
|
|
||||||
mut window_created_events: EventReader<WindowCreated>,
|
|
||||||
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
|
||||||
mut image_asset_events: EventReader<AssetEvent<Image>>,
|
|
||||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
|
||||||
windows: Query<(Entity, &Window)>,
|
|
||||||
images: Res<Assets<Image>>,
|
|
||||||
manual_texture_views: Res<ManualTextureViews>,
|
|
||||||
mut cameras: Query<(&mut Camera, &mut Projection)>,
|
|
||||||
) {
|
|
||||||
let primary_window = primary_window.iter().next();
|
|
||||||
|
|
||||||
let mut changed_window_ids = <HashSet<_>>::default();
|
|
||||||
changed_window_ids.extend(window_created_events.read().map(|event| event.window));
|
|
||||||
changed_window_ids.extend(window_resized_events.read().map(|event| event.window));
|
|
||||||
let scale_factor_changed_window_ids: HashSet<_> = window_scale_factor_changed_events
|
|
||||||
.read()
|
|
||||||
.map(|event| event.window)
|
|
||||||
.collect();
|
|
||||||
changed_window_ids.extend(scale_factor_changed_window_ids.clone());
|
|
||||||
|
|
||||||
let changed_image_handles: HashSet<&AssetId<Image>> = image_asset_events
|
|
||||||
.read()
|
|
||||||
.filter_map(|event| match event {
|
|
||||||
AssetEvent::Modified { id } | AssetEvent::Added { id } => Some(id),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (mut camera, mut camera_projection) in &mut cameras {
|
|
||||||
let mut viewport_size = camera
|
|
||||||
.viewport
|
|
||||||
.as_ref()
|
|
||||||
.map(|viewport| viewport.physical_size);
|
|
||||||
|
|
||||||
if let Some(normalized_target) = camera.target.normalize(primary_window) {
|
|
||||||
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
|
|
||||||
|| camera.is_added()
|
|
||||||
|| camera_projection.is_changed()
|
|
||||||
|| camera.computed.old_viewport_size != viewport_size
|
|
||||||
|| camera.computed.old_sub_camera_view != camera.sub_camera_view
|
|
||||||
{
|
|
||||||
let new_computed_target_info = normalized_target.get_render_target_info(
|
|
||||||
windows,
|
|
||||||
&images,
|
|
||||||
&manual_texture_views,
|
|
||||||
);
|
|
||||||
// Check for the scale factor changing, and resize the viewport if needed.
|
|
||||||
// This can happen when the window is moved between monitors with different DPIs.
|
|
||||||
// Without this, the viewport will take a smaller portion of the window moved to
|
|
||||||
// a higher DPI monitor.
|
|
||||||
if normalized_target
|
|
||||||
.is_changed(&scale_factor_changed_window_ids, &HashSet::default())
|
|
||||||
{
|
|
||||||
if let (Some(new_scale_factor), Some(old_scale_factor)) = (
|
|
||||||
new_computed_target_info
|
|
||||||
.as_ref()
|
|
||||||
.map(|info| info.scale_factor),
|
|
||||||
camera
|
|
||||||
.computed
|
|
||||||
.target_info
|
|
||||||
.as_ref()
|
|
||||||
.map(|info| info.scale_factor),
|
|
||||||
) {
|
|
||||||
let resize_factor = new_scale_factor / old_scale_factor;
|
|
||||||
if let Some(ref mut viewport) = camera.viewport {
|
|
||||||
let resize = |vec: UVec2| (vec.as_vec2() * resize_factor).as_uvec2();
|
|
||||||
viewport.physical_position = resize(viewport.physical_position);
|
|
||||||
viewport.physical_size = resize(viewport.physical_size);
|
|
||||||
viewport_size = Some(viewport.physical_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This check is needed because when changing WindowMode to Fullscreen, the viewport may have invalid
|
|
||||||
// arguments due to a sudden change on the window size to a lower value.
|
|
||||||
// If the size of the window is lower, the viewport will match that lower value.
|
|
||||||
if let Some(viewport) = &mut camera.viewport {
|
|
||||||
let target_info = &new_computed_target_info;
|
|
||||||
if let Some(target) = target_info {
|
|
||||||
viewport.clamp_to_size(target.physical_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
camera.computed.target_info = new_computed_target_info;
|
|
||||||
if let Some(size) = camera.logical_viewport_size() {
|
|
||||||
if size.x != 0.0 && size.y != 0.0 {
|
|
||||||
camera_projection.update(size.x, size.y);
|
|
||||||
camera.computed.clip_from_view = match &camera.sub_camera_view {
|
|
||||||
Some(sub_view) => {
|
|
||||||
camera_projection.get_clip_from_view_for_sub(sub_view)
|
|
||||||
}
|
|
||||||
None => camera_projection.get_clip_from_view(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if camera.computed.old_viewport_size != viewport_size {
|
|
||||||
camera.computed.old_viewport_size = viewport_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if camera.computed.old_sub_camera_view != camera.sub_camera_view {
|
|
||||||
camera.computed.old_sub_camera_view = camera.sub_camera_view;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This component lets you control the [`TextureUsages`] field of the main texture generated for the camera
|
/// This component lets you control the [`TextureUsages`] field of the main texture generated for the camera
|
||||||
#[derive(Component, ExtractComponent, Clone, Copy, Reflect)]
|
#[derive(Component, Clone, Copy, Reflect)]
|
||||||
#[reflect(opaque)]
|
#[reflect(opaque)]
|
||||||
#[reflect(Component, Default, Clone)]
|
#[reflect(Component, Default, Clone)]
|
||||||
pub struct CameraMainTextureUsages(pub TextureUsages);
|
pub struct CameraMainTextureUsages(pub TextureUsages);
|
||||||
@ -1088,310 +813,3 @@ impl CameraMainTextureUsages {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct ExtractedCamera {
|
|
||||||
pub target: Option<NormalizedRenderTarget>,
|
|
||||||
pub physical_viewport_size: Option<UVec2>,
|
|
||||||
pub physical_target_size: Option<UVec2>,
|
|
||||||
pub viewport: Option<Viewport>,
|
|
||||||
pub render_graph: InternedRenderSubGraph,
|
|
||||||
pub order: isize,
|
|
||||||
pub output_mode: CameraOutputMode,
|
|
||||||
pub msaa_writeback: bool,
|
|
||||||
pub clear_color: ClearColorConfig,
|
|
||||||
pub sorted_camera_index_for_target: usize,
|
|
||||||
pub exposure: f32,
|
|
||||||
pub hdr: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_cameras(
|
|
||||||
mut commands: Commands,
|
|
||||||
query: Extract<
|
|
||||||
Query<(
|
|
||||||
Entity,
|
|
||||||
RenderEntity,
|
|
||||||
&Camera,
|
|
||||||
&CameraRenderGraph,
|
|
||||||
&GlobalTransform,
|
|
||||||
&VisibleEntities,
|
|
||||||
&Frustum,
|
|
||||||
Has<Hdr>,
|
|
||||||
Option<&ColorGrading>,
|
|
||||||
Option<&Exposure>,
|
|
||||||
Option<&TemporalJitter>,
|
|
||||||
Option<&MipBias>,
|
|
||||||
Option<&RenderLayers>,
|
|
||||||
Option<&Projection>,
|
|
||||||
Has<NoIndirectDrawing>,
|
|
||||||
)>,
|
|
||||||
>,
|
|
||||||
primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
|
|
||||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
|
||||||
mapper: Extract<Query<&RenderEntity>>,
|
|
||||||
) {
|
|
||||||
let primary_window = primary_window.iter().next();
|
|
||||||
for (
|
|
||||||
main_entity,
|
|
||||||
render_entity,
|
|
||||||
camera,
|
|
||||||
camera_render_graph,
|
|
||||||
transform,
|
|
||||||
visible_entities,
|
|
||||||
frustum,
|
|
||||||
hdr,
|
|
||||||
color_grading,
|
|
||||||
exposure,
|
|
||||||
temporal_jitter,
|
|
||||||
mip_bias,
|
|
||||||
render_layers,
|
|
||||||
projection,
|
|
||||||
no_indirect_drawing,
|
|
||||||
) in query.iter()
|
|
||||||
{
|
|
||||||
if !camera.is_active {
|
|
||||||
commands.entity(render_entity).remove::<(
|
|
||||||
ExtractedCamera,
|
|
||||||
ExtractedView,
|
|
||||||
RenderVisibleEntities,
|
|
||||||
TemporalJitter,
|
|
||||||
MipBias,
|
|
||||||
RenderLayers,
|
|
||||||
Projection,
|
|
||||||
NoIndirectDrawing,
|
|
||||||
ViewUniformOffset,
|
|
||||||
)>();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
|
|
||||||
|
|
||||||
if let (
|
|
||||||
Some(URect {
|
|
||||||
min: viewport_origin,
|
|
||||||
..
|
|
||||||
}),
|
|
||||||
Some(viewport_size),
|
|
||||||
Some(target_size),
|
|
||||||
) = (
|
|
||||||
camera.physical_viewport_rect(),
|
|
||||||
camera.physical_viewport_size(),
|
|
||||||
camera.physical_target_size(),
|
|
||||||
) {
|
|
||||||
if target_size.x == 0 || target_size.y == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_visible_entities = RenderVisibleEntities {
|
|
||||||
entities: visible_entities
|
|
||||||
.entities
|
|
||||||
.iter()
|
|
||||||
.map(|(type_id, entities)| {
|
|
||||||
let entities = entities
|
|
||||||
.iter()
|
|
||||||
.map(|entity| {
|
|
||||||
let render_entity = mapper
|
|
||||||
.get(*entity)
|
|
||||||
.cloned()
|
|
||||||
.map(|entity| entity.id())
|
|
||||||
.unwrap_or(Entity::PLACEHOLDER);
|
|
||||||
(render_entity, (*entity).into())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
(*type_id, entities)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut commands = commands.entity(render_entity);
|
|
||||||
commands.insert((
|
|
||||||
ExtractedCamera {
|
|
||||||
target: camera.target.normalize(primary_window),
|
|
||||||
viewport: camera.viewport.clone(),
|
|
||||||
physical_viewport_size: Some(viewport_size),
|
|
||||||
physical_target_size: Some(target_size),
|
|
||||||
render_graph: camera_render_graph.0,
|
|
||||||
order: camera.order,
|
|
||||||
output_mode: camera.output_mode,
|
|
||||||
msaa_writeback: camera.msaa_writeback,
|
|
||||||
clear_color: camera.clear_color,
|
|
||||||
// this will be set in sort_cameras
|
|
||||||
sorted_camera_index_for_target: 0,
|
|
||||||
exposure: exposure
|
|
||||||
.map(Exposure::exposure)
|
|
||||||
.unwrap_or_else(|| Exposure::default().exposure()),
|
|
||||||
hdr,
|
|
||||||
},
|
|
||||||
ExtractedView {
|
|
||||||
retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0),
|
|
||||||
clip_from_view: camera.clip_from_view(),
|
|
||||||
world_from_view: *transform,
|
|
||||||
clip_from_world: None,
|
|
||||||
hdr,
|
|
||||||
viewport: UVec4::new(
|
|
||||||
viewport_origin.x,
|
|
||||||
viewport_origin.y,
|
|
||||||
viewport_size.x,
|
|
||||||
viewport_size.y,
|
|
||||||
),
|
|
||||||
color_grading,
|
|
||||||
},
|
|
||||||
render_visible_entities,
|
|
||||||
*frustum,
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(temporal_jitter) = temporal_jitter {
|
|
||||||
commands.insert(temporal_jitter.clone());
|
|
||||||
} else {
|
|
||||||
commands.remove::<TemporalJitter>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mip_bias) = mip_bias {
|
|
||||||
commands.insert(mip_bias.clone());
|
|
||||||
} else {
|
|
||||||
commands.remove::<MipBias>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(render_layers) = render_layers {
|
|
||||||
commands.insert(render_layers.clone());
|
|
||||||
} else {
|
|
||||||
commands.remove::<RenderLayers>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(perspective) = projection {
|
|
||||||
commands.insert(perspective.clone());
|
|
||||||
} else {
|
|
||||||
commands.remove::<Projection>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if no_indirect_drawing
|
|
||||||
|| !matches!(
|
|
||||||
gpu_preprocessing_support.max_supported_mode,
|
|
||||||
GpuPreprocessingMode::Culling
|
|
||||||
)
|
|
||||||
{
|
|
||||||
commands.insert(NoIndirectDrawing);
|
|
||||||
} else {
|
|
||||||
commands.remove::<NoIndirectDrawing>();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cameras sorted by their order field. This is updated in the [`sort_cameras`] system.
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
pub struct SortedCameras(pub Vec<SortedCamera>);
|
|
||||||
|
|
||||||
pub struct SortedCamera {
|
|
||||||
pub entity: Entity,
|
|
||||||
pub order: isize,
|
|
||||||
pub target: Option<NormalizedRenderTarget>,
|
|
||||||
pub hdr: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sort_cameras(
|
|
||||||
mut sorted_cameras: ResMut<SortedCameras>,
|
|
||||||
mut cameras: Query<(Entity, &mut ExtractedCamera)>,
|
|
||||||
) {
|
|
||||||
sorted_cameras.0.clear();
|
|
||||||
for (entity, camera) in cameras.iter() {
|
|
||||||
sorted_cameras.0.push(SortedCamera {
|
|
||||||
entity,
|
|
||||||
order: camera.order,
|
|
||||||
target: camera.target.clone(),
|
|
||||||
hdr: camera.hdr,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// sort by order and ensure within an order, RenderTargets of the same type are packed together
|
|
||||||
sorted_cameras
|
|
||||||
.0
|
|
||||||
.sort_by(|c1, c2| (c1.order, &c1.target).cmp(&(c2.order, &c2.target)));
|
|
||||||
let mut previous_order_target = None;
|
|
||||||
let mut ambiguities = <HashSet<_>>::default();
|
|
||||||
let mut target_counts = <HashMap<_, _>>::default();
|
|
||||||
for sorted_camera in &mut sorted_cameras.0 {
|
|
||||||
let new_order_target = (sorted_camera.order, sorted_camera.target.clone());
|
|
||||||
if let Some(previous_order_target) = previous_order_target {
|
|
||||||
if previous_order_target == new_order_target {
|
|
||||||
ambiguities.insert(new_order_target.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(target) = &sorted_camera.target {
|
|
||||||
let count = target_counts
|
|
||||||
.entry((target.clone(), sorted_camera.hdr))
|
|
||||||
.or_insert(0usize);
|
|
||||||
let (_, mut camera) = cameras.get_mut(sorted_camera.entity).unwrap();
|
|
||||||
camera.sorted_camera_index_for_target = *count;
|
|
||||||
*count += 1;
|
|
||||||
}
|
|
||||||
previous_order_target = Some(new_order_target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ambiguities.is_empty() {
|
|
||||||
warn!(
|
|
||||||
"Camera order ambiguities detected for active cameras with the following priorities: {:?}. \
|
|
||||||
To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. \
|
|
||||||
Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will \
|
|
||||||
result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, \
|
|
||||||
ambiguities could result in unpredictable render results.",
|
|
||||||
ambiguities
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A subpixel offset to jitter a perspective camera's frustum by.
|
|
||||||
///
|
|
||||||
/// Useful for temporal rendering techniques.
|
|
||||||
///
|
|
||||||
/// Do not use with [`OrthographicProjection`].
|
|
||||||
///
|
|
||||||
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
|
||||||
#[derive(Component, Clone, Default, Reflect)]
|
|
||||||
#[reflect(Default, Component, Clone)]
|
|
||||||
pub struct TemporalJitter {
|
|
||||||
/// Offset is in range [-0.5, 0.5].
|
|
||||||
pub offset: Vec2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TemporalJitter {
|
|
||||||
pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) {
|
|
||||||
if clip_from_view.w_axis.w == 1.0 {
|
|
||||||
warn!(
|
|
||||||
"TemporalJitter not supported with OrthographicProjection. Use PerspectiveProjection instead."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/docs/techniques/media/super-resolution-temporal/jitter-space.svg
|
|
||||||
let jitter = (self.offset * vec2(2.0, -2.0)) / view_size;
|
|
||||||
|
|
||||||
clip_from_view.z_axis.x += jitter.x;
|
|
||||||
clip_from_view.z_axis.y += jitter.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Camera component specifying a mip bias to apply when sampling from material textures.
|
|
||||||
///
|
|
||||||
/// Often used in conjunction with antialiasing post-process effects to reduce textures blurriness.
|
|
||||||
#[derive(Component, Reflect, Clone)]
|
|
||||||
#[reflect(Default, Component)]
|
|
||||||
pub struct MipBias(pub f32);
|
|
||||||
|
|
||||||
/// Override the resolution a 3d camera's main pass is rendered at.
|
|
||||||
///
|
|
||||||
/// Does not affect post processing.
|
|
||||||
///
|
|
||||||
/// ## Usage
|
|
||||||
///
|
|
||||||
/// * Insert this component on a 3d camera entity in the render world.
|
|
||||||
/// * The resolution override must be smaller than the camera's viewport size.
|
|
||||||
/// * The resolution override is specified in physical pixels.
|
|
||||||
#[derive(Component, Reflect, Deref)]
|
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct MainPassResolutionOverride(pub UVec2);
|
|
||||||
|
|
||||||
impl Default for MipBias {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(-1.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
use crate::extract_resource::ExtractResource;
|
|
||||||
use bevy_color::Color;
|
use bevy_color::Color;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
@ -32,7 +31,7 @@ pub enum ClearColorConfig {
|
|||||||
/// clear color or opt out of clearing their viewport.
|
/// clear color or opt out of clearing their viewport.
|
||||||
///
|
///
|
||||||
/// [`Camera.clear_color`]: crate::camera::Camera::clear_color
|
/// [`Camera.clear_color`]: crate::camera::Camera::clear_color
|
||||||
#[derive(Resource, Clone, Debug, Deref, DerefMut, ExtractResource, Reflect)]
|
#[derive(Resource, Clone, Debug, Deref, DerefMut, Reflect)]
|
||||||
#[reflect(Resource, Default, Debug, Clone)]
|
#[reflect(Resource, Default, Debug, Clone)]
|
||||||
pub struct ClearColor(pub Color);
|
pub struct ClearColor(pub Color);
|
||||||
|
|
||||||
35
crates/bevy_camera/src/lib.rs
Normal file
35
crates/bevy_camera/src/lib.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
||||||
|
mod camera;
|
||||||
|
mod clear_color;
|
||||||
|
pub mod primitives;
|
||||||
|
mod projection;
|
||||||
|
pub mod visibility;
|
||||||
|
|
||||||
|
pub use camera::*;
|
||||||
|
pub use clear_color::*;
|
||||||
|
pub use projection::*;
|
||||||
|
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<Camera>()
|
||||||
|
.register_type::<ClearColor>()
|
||||||
|
.register_type::<CameraMainTextureUsages>()
|
||||||
|
.register_type::<Exposure>()
|
||||||
|
.register_type::<MainPassResolutionOverride>()
|
||||||
|
.register_type::<primitives::Aabb>()
|
||||||
|
.register_type::<primitives::CascadesFrusta>()
|
||||||
|
.register_type::<primitives::CubemapFrusta>()
|
||||||
|
.register_type::<primitives::Frustum>()
|
||||||
|
.init_resource::<ClearColor>()
|
||||||
|
.add_plugins((
|
||||||
|
CameraProjectionPlugin,
|
||||||
|
visibility::VisibilityPlugin,
|
||||||
|
visibility::VisibilityRangePlugin,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,29 @@ use core::borrow::Borrow;
|
|||||||
|
|
||||||
use bevy_ecs::{component::Component, entity::EntityHashMap, reflect::ReflectComponent};
|
use bevy_ecs::{component::Component, entity::EntityHashMap, reflect::ReflectComponent};
|
||||||
use bevy_math::{Affine3A, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
|
use bevy_math::{Affine3A, Mat3A, Mat4, Vec3, Vec3A, Vec4, Vec4Swizzles};
|
||||||
|
use bevy_mesh::{Mesh, VertexAttributeValues};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
|
|
||||||
|
pub trait MeshAabb {
|
||||||
|
/// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space
|
||||||
|
///
|
||||||
|
/// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of
|
||||||
|
/// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices.
|
||||||
|
fn compute_aabb(&self) -> Option<Aabb>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MeshAabb for Mesh {
|
||||||
|
fn compute_aabb(&self) -> Option<Aabb> {
|
||||||
|
let Some(VertexAttributeValues::Float32x3(values)) =
|
||||||
|
self.attribute(Mesh::ATTRIBUTE_POSITION)
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An axis-aligned bounding box, defined by:
|
/// An axis-aligned bounding box, defined by:
|
||||||
/// - a center,
|
/// - a center,
|
||||||
/// - the distances from the center to each faces along the axis,
|
/// - the distances from the center to each faces along the axis,
|
||||||
@ -24,10 +45,10 @@ use bevy_reflect::prelude::*;
|
|||||||
/// It won't be updated automatically if the space occupied by the entity changes,
|
/// It won't be updated automatically if the space occupied by the entity changes,
|
||||||
/// for example if the vertex positions of a [`Mesh3d`] are updated.
|
/// for example if the vertex positions of a [`Mesh3d`] are updated.
|
||||||
///
|
///
|
||||||
/// [`Camera`]: crate::camera::Camera
|
/// [`Camera`]: crate::Camera
|
||||||
/// [`NoFrustumCulling`]: crate::view::visibility::NoFrustumCulling
|
/// [`NoFrustumCulling`]: crate::visibility::NoFrustumCulling
|
||||||
/// [`CalculateBounds`]: crate::view::visibility::VisibilitySystems::CalculateBounds
|
/// [`CalculateBounds`]: crate::visibility::VisibilitySystems::CalculateBounds
|
||||||
/// [`Mesh3d`]: crate::mesh::Mesh
|
/// [`Mesh3d`]: bevy_mesh::Mesh
|
||||||
#[derive(Component, Clone, Copy, Debug, Default, Reflect, PartialEq)]
|
#[derive(Component, Clone, Copy, Debug, Default, Reflect, PartialEq)]
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||||
pub struct Aabb {
|
pub struct Aabb {
|
||||||
@ -56,7 +77,7 @@ impl Aabb {
|
|||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_math::{Vec3, Vec3A};
|
/// # use bevy_math::{Vec3, Vec3A};
|
||||||
/// # use bevy_render::primitives::Aabb;
|
/// # use bevy_camera::primitives::Aabb;
|
||||||
/// let bb = Aabb::enclosing([Vec3::X, Vec3::Z * 2.0, Vec3::Y * -0.5]).unwrap();
|
/// let bb = Aabb::enclosing([Vec3::X, Vec3::Z * 2.0, Vec3::Y * -0.5]).unwrap();
|
||||||
/// assert_eq!(bb.min(), Vec3A::new(0.0, -0.5, 0.0));
|
/// assert_eq!(bb.min(), Vec3A::new(0.0, -0.5, 0.0));
|
||||||
/// assert_eq!(bb.max(), Vec3A::new(1.0, 0.0, 2.0));
|
/// assert_eq!(bb.max(), Vec3A::new(1.0, 0.0, 2.0));
|
||||||
@ -218,10 +239,10 @@ impl HalfSpace {
|
|||||||
/// It is usually updated automatically by [`update_frusta`] from the
|
/// It is usually updated automatically by [`update_frusta`] from the
|
||||||
/// [`CameraProjection`] component and [`GlobalTransform`] of the camera entity.
|
/// [`CameraProjection`] component and [`GlobalTransform`] of the camera entity.
|
||||||
///
|
///
|
||||||
/// [`Camera`]: crate::camera::Camera
|
/// [`Camera`]: crate::Camera
|
||||||
/// [`NoFrustumCulling`]: crate::view::visibility::NoFrustumCulling
|
/// [`NoFrustumCulling`]: crate::visibility::NoFrustumCulling
|
||||||
/// [`update_frusta`]: crate::view::visibility::update_frusta
|
/// [`update_frusta`]: crate::visibility::update_frusta
|
||||||
/// [`CameraProjection`]: crate::camera::CameraProjection
|
/// [`CameraProjection`]: crate::CameraProjection
|
||||||
/// [`GlobalTransform`]: bevy_transform::components::GlobalTransform
|
/// [`GlobalTransform`]: bevy_transform::components::GlobalTransform
|
||||||
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
|
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
|
||||||
#[reflect(Component, Default, Debug, Clone)]
|
#[reflect(Component, Default, Debug, Clone)]
|
||||||
@ -356,7 +377,7 @@ mod tests {
|
|||||||
use bevy_math::{ops, Quat};
|
use bevy_math::{ops, Quat};
|
||||||
use bevy_transform::components::GlobalTransform;
|
use bevy_transform::components::GlobalTransform;
|
||||||
|
|
||||||
use crate::camera::{CameraProjection, PerspectiveProjection};
|
use crate::{CameraProjection, PerspectiveProjection};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -1,9 +1,8 @@
|
|||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::{primitives::Frustum, view::VisibilitySystems};
|
use crate::{primitives::Frustum, visibility::VisibilitySystems};
|
||||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
use bevy_app::{App, Plugin, PostUpdate};
|
||||||
use bevy_asset::AssetEventSystems;
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize, ReflectSerialize};
|
||||||
@ -23,28 +22,16 @@ impl Plugin for CameraProjectionPlugin {
|
|||||||
.register_type::<PerspectiveProjection>()
|
.register_type::<PerspectiveProjection>()
|
||||||
.register_type::<OrthographicProjection>()
|
.register_type::<OrthographicProjection>()
|
||||||
.register_type::<CustomProjection>()
|
.register_type::<CustomProjection>()
|
||||||
.add_systems(
|
|
||||||
PostStartup,
|
|
||||||
crate::camera::camera_system.in_set(CameraUpdateSystems),
|
|
||||||
)
|
|
||||||
.add_systems(
|
.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
crate::visibility::update_frusta
|
||||||
crate::camera::camera_system
|
.in_set(VisibilitySystems::UpdateFrusta)
|
||||||
.in_set(CameraUpdateSystems)
|
.after(TransformSystems::Propagate),
|
||||||
.before(AssetEventSystems),
|
|
||||||
crate::view::update_frusta
|
|
||||||
.in_set(VisibilitySystems::UpdateFrusta)
|
|
||||||
.after(crate::camera::camera_system)
|
|
||||||
.after(TransformSystems::Propagate),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Label for [`camera_system<T>`], shared across all `T`.
|
/// Label for `camera_system<T>`, shared across all `T`.
|
||||||
///
|
|
||||||
/// [`camera_system<T>`]: crate::camera::camera_system
|
|
||||||
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub struct CameraUpdateSystems;
|
pub struct CameraUpdateSystems;
|
||||||
|
|
||||||
@ -90,7 +77,7 @@ pub trait CameraProjection {
|
|||||||
|
|
||||||
/// Compute camera frustum for camera with given projection and transform.
|
/// Compute camera frustum for camera with given projection and transform.
|
||||||
///
|
///
|
||||||
/// This code is called by [`update_frusta`](crate::view::visibility::update_frusta) system
|
/// This code is called by [`update_frusta`](crate::visibility::update_frusta) system
|
||||||
/// for each camera to update its frustum.
|
/// for each camera to update its frustum.
|
||||||
fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum {
|
fn compute_frustum(&self, camera_transform: &GlobalTransform) -> Frustum {
|
||||||
let clip_from_world = self.get_clip_from_view() * camera_transform.to_matrix().inverse();
|
let clip_from_world = self.get_clip_from_view() * camera_transform.to_matrix().inverse();
|
||||||
@ -160,7 +147,7 @@ impl CustomProjection {
|
|||||||
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_render::prelude::{Projection, PerspectiveProjection};
|
/// # use bevy_camera::{Projection, PerspectiveProjection};
|
||||||
/// // For simplicity's sake, use perspective as a custom projection:
|
/// // For simplicity's sake, use perspective as a custom projection:
|
||||||
/// let projection = Projection::custom(PerspectiveProjection::default());
|
/// let projection = Projection::custom(PerspectiveProjection::default());
|
||||||
/// let Projection::Custom(custom) = projection else { return };
|
/// let Projection::Custom(custom) = projection else { return };
|
||||||
@ -183,7 +170,7 @@ impl CustomProjection {
|
|||||||
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
/// Returns `None` if this dynamic object is not a projection of type `P`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_render::prelude::{Projection, PerspectiveProjection};
|
/// # use bevy_camera::{Projection, PerspectiveProjection};
|
||||||
/// // For simplicity's sake, use perspective as a custom projection:
|
/// // For simplicity's sake, use perspective as a custom projection:
|
||||||
/// let mut projection = Projection::custom(PerspectiveProjection::default());
|
/// let mut projection = Projection::custom(PerspectiveProjection::default());
|
||||||
/// let Projection::Custom(mut custom) = projection else { return };
|
/// let Projection::Custom(mut custom) = projection else { return };
|
||||||
@ -303,8 +290,8 @@ pub struct PerspectiveProjection {
|
|||||||
|
|
||||||
/// The aspect ratio (width divided by height) of the viewing frustum.
|
/// The aspect ratio (width divided by height) of the viewing frustum.
|
||||||
///
|
///
|
||||||
/// Bevy's [`camera_system`](crate::camera::camera_system) automatically
|
/// Bevy's `camera_system` automatically updates this value when the aspect ratio
|
||||||
/// updates this value when the aspect ratio of the associated window changes.
|
/// of the associated window changes.
|
||||||
///
|
///
|
||||||
/// Defaults to a value of `1.0`.
|
/// Defaults to a value of `1.0`.
|
||||||
pub aspect_ratio: f32,
|
pub aspect_ratio: f32,
|
||||||
@ -422,7 +409,7 @@ impl Default for PerspectiveProjection {
|
|||||||
/// Configure the orthographic projection to two world units per window height:
|
/// Configure the orthographic projection to two world units per window height:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
|
/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};
|
||||||
/// let projection = Projection::Orthographic(OrthographicProjection {
|
/// let projection = Projection::Orthographic(OrthographicProjection {
|
||||||
/// scaling_mode: ScalingMode::FixedVertical { viewport_height: 2.0 },
|
/// scaling_mode: ScalingMode::FixedVertical { viewport_height: 2.0 },
|
||||||
/// ..OrthographicProjection::default_2d()
|
/// ..OrthographicProjection::default_2d()
|
||||||
@ -478,7 +465,7 @@ pub enum ScalingMode {
|
|||||||
/// Configure the orthographic projection to one world unit per 100 window pixels:
|
/// Configure the orthographic projection to one world unit per 100 window pixels:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
|
/// # use bevy_camera::{OrthographicProjection, Projection, ScalingMode};
|
||||||
/// let projection = Projection::Orthographic(OrthographicProjection {
|
/// let projection = Projection::Orthographic(OrthographicProjection {
|
||||||
/// scaling_mode: ScalingMode::WindowSize,
|
/// scaling_mode: ScalingMode::WindowSize,
|
||||||
/// scale: 0.01,
|
/// scale: 0.01,
|
||||||
@ -535,7 +522,7 @@ pub struct OrthographicProjection {
|
|||||||
pub scale: f32,
|
pub scale: f32,
|
||||||
/// The area that the projection covers relative to `viewport_origin`.
|
/// The area that the projection covers relative to `viewport_origin`.
|
||||||
///
|
///
|
||||||
/// Bevy's [`camera_system`](crate::camera::camera_system) automatically
|
/// Bevy's `camera_system` automatically
|
||||||
/// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.
|
/// updates this value when the viewport is resized depending on `OrthographicProjection`'s other fields.
|
||||||
/// In this case, `area` should not be manually modified.
|
/// In this case, `area` should not be manually modified.
|
||||||
///
|
///
|
||||||
948
crates/bevy_camera/src/visibility/mod.rs
Normal file
948
crates/bevy_camera/src/visibility/mod.rs
Normal file
@ -0,0 +1,948 @@
|
|||||||
|
mod range;
|
||||||
|
mod render_layers;
|
||||||
|
|
||||||
|
use core::any::TypeId;
|
||||||
|
|
||||||
|
use bevy_ecs::entity::EntityHashSet;
|
||||||
|
use bevy_ecs::lifecycle::HookContext;
|
||||||
|
use bevy_ecs::world::DeferredWorld;
|
||||||
|
use derive_more::derive::{Deref, DerefMut};
|
||||||
|
pub use range::*;
|
||||||
|
pub use render_layers::*;
|
||||||
|
|
||||||
|
use bevy_app::{Plugin, PostUpdate};
|
||||||
|
use bevy_asset::Assets;
|
||||||
|
use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*};
|
||||||
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
|
use bevy_transform::{components::GlobalTransform, TransformSystems};
|
||||||
|
use bevy_utils::{Parallel, TypeIdMap};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
camera::Camera,
|
||||||
|
primitives::{Aabb, Frustum, MeshAabb, Sphere},
|
||||||
|
Projection,
|
||||||
|
};
|
||||||
|
use bevy_mesh::{Mesh, Mesh2d, Mesh3d};
|
||||||
|
|
||||||
|
#[derive(Component, Default)]
|
||||||
|
pub struct NoCpuCulling;
|
||||||
|
|
||||||
|
/// User indication of whether an entity is visible. Propagates down the entity hierarchy.
|
||||||
|
///
|
||||||
|
/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who
|
||||||
|
/// are set to [`Inherited`](Self::Inherited) will also be hidden.
|
||||||
|
///
|
||||||
|
/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and
|
||||||
|
/// `Visibility` to set the values of each entity's [`InheritedVisibility`] component.
|
||||||
|
#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
|
||||||
|
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||||
|
#[require(InheritedVisibility, ViewVisibility)]
|
||||||
|
pub enum Visibility {
|
||||||
|
/// An entity with `Visibility::Inherited` will inherit the Visibility of its [`ChildOf`] target.
|
||||||
|
///
|
||||||
|
/// A root-level entity that is set to `Inherited` will be visible.
|
||||||
|
#[default]
|
||||||
|
Inherited,
|
||||||
|
/// An entity with `Visibility::Hidden` will be unconditionally hidden.
|
||||||
|
Hidden,
|
||||||
|
/// An entity with `Visibility::Visible` will be unconditionally visible.
|
||||||
|
///
|
||||||
|
/// Note that an entity with `Visibility::Visible` will be visible regardless of whether the
|
||||||
|
/// [`ChildOf`] target entity is hidden.
|
||||||
|
Visible,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visibility {
|
||||||
|
/// Toggles between `Visibility::Inherited` and `Visibility::Visible`.
|
||||||
|
/// If the value is `Visibility::Hidden`, it remains unaffected.
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle_inherited_visible(&mut self) {
|
||||||
|
*self = match *self {
|
||||||
|
Visibility::Inherited => Visibility::Visible,
|
||||||
|
Visibility::Visible => Visibility::Inherited,
|
||||||
|
_ => *self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Toggles between `Visibility::Inherited` and `Visibility::Hidden`.
|
||||||
|
/// If the value is `Visibility::Visible`, it remains unaffected.
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle_inherited_hidden(&mut self) {
|
||||||
|
*self = match *self {
|
||||||
|
Visibility::Inherited => Visibility::Hidden,
|
||||||
|
Visibility::Hidden => Visibility::Inherited,
|
||||||
|
_ => *self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// Toggles between `Visibility::Visible` and `Visibility::Hidden`.
|
||||||
|
/// If the value is `Visibility::Inherited`, it remains unaffected.
|
||||||
|
#[inline]
|
||||||
|
pub fn toggle_visible_hidden(&mut self) {
|
||||||
|
*self = match *self {
|
||||||
|
Visibility::Visible => Visibility::Hidden,
|
||||||
|
Visibility::Hidden => Visibility::Visible,
|
||||||
|
_ => *self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows `&Visibility == Visibility`
|
||||||
|
impl PartialEq<Visibility> for &Visibility {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &Visibility) -> bool {
|
||||||
|
// Use the base Visibility == Visibility implementation.
|
||||||
|
<Visibility as PartialEq<Visibility>>::eq(*self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows `Visibility == &Visibility`
|
||||||
|
impl PartialEq<&Visibility> for Visibility {
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self, other: &&Visibility) -> bool {
|
||||||
|
// Use the base Visibility == Visibility implementation.
|
||||||
|
<Visibility as PartialEq<Visibility>>::eq(self, *other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether or not an entity is visible in the hierarchy.
|
||||||
|
/// This will not be accurate until [`VisibilityPropagate`] runs in the [`PostUpdate`] schedule.
|
||||||
|
///
|
||||||
|
/// If this is false, then [`ViewVisibility`] should also be false.
|
||||||
|
///
|
||||||
|
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
||||||
|
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
|
||||||
|
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||||
|
#[component(on_insert = validate_parent_has_component::<Self>)]
|
||||||
|
pub struct InheritedVisibility(bool);
|
||||||
|
|
||||||
|
impl InheritedVisibility {
|
||||||
|
/// An entity that is invisible in the hierarchy.
|
||||||
|
pub const HIDDEN: Self = Self(false);
|
||||||
|
/// An entity that is visible in the hierarchy.
|
||||||
|
pub const VISIBLE: Self = Self(true);
|
||||||
|
|
||||||
|
/// Returns `true` if the entity is visible in the hierarchy.
|
||||||
|
/// Otherwise, returns `false`.
|
||||||
|
#[inline]
|
||||||
|
pub fn get(self) -> bool {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bucket into which we group entities for the purposes of visibility.
|
||||||
|
///
|
||||||
|
/// Bevy's various rendering subsystems (3D, 2D, etc.) want to be able to
|
||||||
|
/// quickly winnow the set of entities to only those that the subsystem is
|
||||||
|
/// tasked with rendering, to avoid spending time examining irrelevant entities.
|
||||||
|
/// At the same time, Bevy wants the [`check_visibility`] system to determine
|
||||||
|
/// all entities' visibilities at the same time, regardless of what rendering
|
||||||
|
/// subsystem is responsible for drawing them. Additionally, your application
|
||||||
|
/// may want to add more types of renderable objects that Bevy determines
|
||||||
|
/// visibility for just as it does for Bevy's built-in objects.
|
||||||
|
///
|
||||||
|
/// The solution to this problem is *visibility classes*. A visibility class is
|
||||||
|
/// a type, typically the type of a component, that represents the subsystem
|
||||||
|
/// that renders it: for example, `Mesh3d`, `Mesh2d`, and `Sprite`. The
|
||||||
|
/// [`VisibilityClass`] component stores the visibility class or classes that
|
||||||
|
/// the entity belongs to. (Generally, an object will belong to only one
|
||||||
|
/// visibility class, but in rare cases it may belong to multiple.)
|
||||||
|
///
|
||||||
|
/// When adding a new renderable component, you'll typically want to write an
|
||||||
|
/// add-component hook that adds the type ID of that component to the
|
||||||
|
/// [`VisibilityClass`] array. See `custom_phase_item` for an example.
|
||||||
|
//
|
||||||
|
// Note: This can't be a `ComponentId` because the visibility classes are copied
|
||||||
|
// into the render world, and component IDs are per-world.
|
||||||
|
#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]
|
||||||
|
#[reflect(Component, Default, Clone)]
|
||||||
|
pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);
|
||||||
|
|
||||||
|
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
|
||||||
|
///
|
||||||
|
/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].
|
||||||
|
/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].
|
||||||
|
/// Because of this, values of this type will be marked as changed every frame, even when they do not change.
|
||||||
|
///
|
||||||
|
/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.
|
||||||
|
///
|
||||||
|
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
||||||
|
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
|
||||||
|
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
|
||||||
|
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||||
|
pub struct ViewVisibility(bool);
|
||||||
|
|
||||||
|
impl ViewVisibility {
|
||||||
|
/// An entity that cannot be seen from any views.
|
||||||
|
pub const HIDDEN: Self = Self(false);
|
||||||
|
|
||||||
|
/// Returns `true` if the entity is visible in any view.
|
||||||
|
/// Otherwise, returns `false`.
|
||||||
|
#[inline]
|
||||||
|
pub fn get(self) -> bool {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the visibility to `true`. This should not be considered reversible for a given frame,
|
||||||
|
/// as this component tracks whether or not the entity visible in _any_ view.
|
||||||
|
///
|
||||||
|
/// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set
|
||||||
|
/// to the proper value in [`CheckVisibility`].
|
||||||
|
///
|
||||||
|
/// You should only manually set this if you are defining a custom visibility system,
|
||||||
|
/// in which case the system should be placed in the [`CheckVisibility`] set.
|
||||||
|
/// For normal user-defined entity visibility, see [`Visibility`].
|
||||||
|
///
|
||||||
|
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
||||||
|
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
|
||||||
|
#[inline]
|
||||||
|
pub fn set(&mut self) {
|
||||||
|
self.0 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this component to opt-out of built-in frustum culling for entities, see
|
||||||
|
/// [`Frustum`].
|
||||||
|
///
|
||||||
|
/// It can be used for example:
|
||||||
|
/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations,
|
||||||
|
/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`]
|
||||||
|
/// to appear in the reflection of a [`Mesh`] within.
|
||||||
|
#[derive(Debug, Component, Default, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug)]
|
||||||
|
pub struct NoFrustumCulling;
|
||||||
|
|
||||||
|
/// Collection of entities visible from the current view.
|
||||||
|
///
|
||||||
|
/// This component contains all entities which are visible from the currently
|
||||||
|
/// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`]
|
||||||
|
/// system set. Renderers can use the equivalent `RenderVisibleEntities` to optimize rendering of
|
||||||
|
/// a particular view, to prevent drawing items not visible from that view.
|
||||||
|
///
|
||||||
|
/// This component is intended to be attached to the same entity as the [`Camera`] and
|
||||||
|
/// the [`Frustum`] defining the view.
|
||||||
|
#[derive(Clone, Component, Default, Debug, Reflect)]
|
||||||
|
#[reflect(Component, Default, Debug, Clone)]
|
||||||
|
pub struct VisibleEntities {
|
||||||
|
#[reflect(ignore, clone)]
|
||||||
|
pub entities: TypeIdMap<Vec<Entity>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisibleEntities {
|
||||||
|
pub fn get(&self, type_id: TypeId) -> &[Entity] {
|
||||||
|
match self.entities.get(&type_id) {
|
||||||
|
Some(entities) => &entities[..],
|
||||||
|
None => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
|
||||||
|
self.entities.entry(type_id).or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
|
||||||
|
self.get(type_id).iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self, type_id: TypeId) -> usize {
|
||||||
|
self.get(type_id).len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self, type_id: TypeId) -> bool {
|
||||||
|
self.get(type_id).is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self, type_id: TypeId) {
|
||||||
|
self.get_mut(type_id).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_all(&mut self) {
|
||||||
|
// Don't just nuke the hash table; we want to reuse allocations.
|
||||||
|
for entities in self.entities.values_mut() {
|
||||||
|
entities.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, entity: Entity, type_id: TypeId) {
|
||||||
|
self.get_mut(type_id).push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
|
pub enum VisibilitySystems {
|
||||||
|
/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,
|
||||||
|
/// calculating and inserting an [`Aabb`] to relevant entities.
|
||||||
|
CalculateBounds,
|
||||||
|
/// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).
|
||||||
|
UpdateFrusta,
|
||||||
|
/// Label for the system propagating the [`InheritedVisibility`] in a
|
||||||
|
/// [`ChildOf`] / [`Children`] hierarchy.
|
||||||
|
VisibilityPropagate,
|
||||||
|
/// Label for the [`check_visibility`] system updating [`ViewVisibility`]
|
||||||
|
/// of each entity and the [`VisibleEntities`] of each view.\
|
||||||
|
///
|
||||||
|
/// System order ambiguities between systems in this set are ignored:
|
||||||
|
/// the order of systems within this set is irrelevant, as [`check_visibility`]
|
||||||
|
/// assumes that its operations are irreversible during the frame.
|
||||||
|
CheckVisibility,
|
||||||
|
/// Label for the `mark_newly_hidden_entities_invisible` system, which sets
|
||||||
|
/// [`ViewVisibility`] to [`ViewVisibility::HIDDEN`] for entities that no
|
||||||
|
/// view has marked as visible.
|
||||||
|
MarkNewlyHiddenEntitiesInvisible,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VisibilityPlugin;
|
||||||
|
|
||||||
|
impl Plugin for VisibilityPlugin {
|
||||||
|
fn build(&self, app: &mut bevy_app::App) {
|
||||||
|
use VisibilitySystems::*;
|
||||||
|
|
||||||
|
app.register_type::<VisibilityClass>()
|
||||||
|
.register_type::<InheritedVisibility>()
|
||||||
|
.register_type::<ViewVisibility>()
|
||||||
|
.register_type::<NoFrustumCulling>()
|
||||||
|
.register_type::<RenderLayers>()
|
||||||
|
.register_type::<Visibility>()
|
||||||
|
.register_type::<VisibleEntities>()
|
||||||
|
.register_required_components::<Mesh3d, Visibility>()
|
||||||
|
.register_required_components::<Mesh3d, VisibilityClass>()
|
||||||
|
.register_required_components::<Mesh2d, Visibility>()
|
||||||
|
.register_required_components::<Mesh2d, VisibilityClass>()
|
||||||
|
.configure_sets(
|
||||||
|
PostUpdate,
|
||||||
|
(CalculateBounds, UpdateFrusta, VisibilityPropagate)
|
||||||
|
.before(CheckVisibility)
|
||||||
|
.after(TransformSystems::Propagate),
|
||||||
|
)
|
||||||
|
.configure_sets(
|
||||||
|
PostUpdate,
|
||||||
|
MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),
|
||||||
|
)
|
||||||
|
.init_resource::<PreviousVisibleEntities>()
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
(
|
||||||
|
calculate_bounds.in_set(CalculateBounds),
|
||||||
|
(visibility_propagate_system, reset_view_visibility)
|
||||||
|
.in_set(VisibilityPropagate),
|
||||||
|
check_visibility.in_set(CheckVisibility),
|
||||||
|
mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
app.world_mut()
|
||||||
|
.register_component_hooks::<Mesh3d>()
|
||||||
|
.on_add(add_visibility_class::<Mesh3d>);
|
||||||
|
app.world_mut()
|
||||||
|
.register_component_hooks::<Mesh2d>()
|
||||||
|
.on_add(add_visibility_class::<Mesh2d>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes and adds an [`Aabb`] component to entities with a
|
||||||
|
/// [`Mesh3d`] component and without a [`NoFrustumCulling`] component.
|
||||||
|
///
|
||||||
|
/// This system is used in system set [`VisibilitySystems::CalculateBounds`].
|
||||||
|
pub fn calculate_bounds(
|
||||||
|
mut commands: Commands,
|
||||||
|
meshes: Res<Assets<Mesh>>,
|
||||||
|
without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
||||||
|
) {
|
||||||
|
for (entity, mesh_handle) in &without_aabb {
|
||||||
|
if let Some(mesh) = meshes.get(mesh_handle) {
|
||||||
|
if let Some(aabb) = mesh.compute_aabb() {
|
||||||
|
commands.entity(entity).try_insert(aabb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates [`Frustum`].
|
||||||
|
///
|
||||||
|
/// This system is used in [`CameraProjectionPlugin`](crate::CameraProjectionPlugin).
|
||||||
|
pub fn update_frusta(
|
||||||
|
mut views: Query<
|
||||||
|
(&GlobalTransform, &Projection, &mut Frustum),
|
||||||
|
Or<(Changed<GlobalTransform>, Changed<Projection>)>,
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
for (transform, projection, mut frustum) in &mut views {
|
||||||
|
*frustum = projection.compute_frustum(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visibility_propagate_system(
|
||||||
|
changed: Query<
|
||||||
|
(Entity, &Visibility, Option<&ChildOf>, Option<&Children>),
|
||||||
|
(
|
||||||
|
With<InheritedVisibility>,
|
||||||
|
Or<(Changed<Visibility>, Changed<ChildOf>)>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
|
||||||
|
children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
|
||||||
|
) {
|
||||||
|
for (entity, visibility, child_of, children) in &changed {
|
||||||
|
let is_visible = match visibility {
|
||||||
|
Visibility::Visible => true,
|
||||||
|
Visibility::Hidden => false,
|
||||||
|
// fall back to true if no parent is found or parent lacks components
|
||||||
|
Visibility::Inherited => child_of
|
||||||
|
.and_then(|c| visibility_query.get(c.parent()).ok())
|
||||||
|
.is_none_or(|(_, x)| x.get()),
|
||||||
|
};
|
||||||
|
let (_, mut inherited_visibility) = visibility_query
|
||||||
|
.get_mut(entity)
|
||||||
|
.expect("With<InheritedVisibility> ensures this query will return a value");
|
||||||
|
|
||||||
|
// Only update the visibility if it has changed.
|
||||||
|
// This will also prevent the visibility from propagating multiple times in the same frame
|
||||||
|
// if this entity's visibility has been updated recursively by its parent.
|
||||||
|
if inherited_visibility.get() != is_visible {
|
||||||
|
inherited_visibility.0 = is_visible;
|
||||||
|
|
||||||
|
// Recursively update the visibility of each child.
|
||||||
|
for &child in children.into_iter().flatten() {
|
||||||
|
let _ =
|
||||||
|
propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propagate_recursive(
|
||||||
|
parent_is_visible: bool,
|
||||||
|
entity: Entity,
|
||||||
|
visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
|
||||||
|
children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
|
||||||
|
// BLOCKED: https://github.com/rust-lang/rust/issues/31436
|
||||||
|
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
// Get the visibility components for the current entity.
|
||||||
|
// If the entity does not have the required components, just return early.
|
||||||
|
let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
|
||||||
|
|
||||||
|
let is_visible = match visibility {
|
||||||
|
Visibility::Visible => true,
|
||||||
|
Visibility::Hidden => false,
|
||||||
|
Visibility::Inherited => parent_is_visible,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only update the visibility if it has changed.
|
||||||
|
if inherited_visibility.get() != is_visible {
|
||||||
|
inherited_visibility.0 = is_visible;
|
||||||
|
|
||||||
|
// Recursively update the visibility of each child.
|
||||||
|
for &child in children_query.get(entity).ok().into_iter().flatten() {
|
||||||
|
let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores all entities that were visible in the previous frame.
|
||||||
|
///
|
||||||
|
/// As systems that check visibility judge entities visible, they remove them
|
||||||
|
/// from this set. Afterward, the `mark_newly_hidden_entities_invisible` system
|
||||||
|
/// runs and marks every mesh still remaining in this set as hidden.
|
||||||
|
#[derive(Resource, Default, Deref, DerefMut)]
|
||||||
|
pub struct PreviousVisibleEntities(EntityHashSet);
|
||||||
|
|
||||||
|
/// Resets the view visibility of every entity.
|
||||||
|
/// Entities that are visible will be marked as such later this frame
|
||||||
|
/// by a [`VisibilitySystems::CheckVisibility`] system.
|
||||||
|
fn reset_view_visibility(
|
||||||
|
mut query: Query<(Entity, &ViewVisibility)>,
|
||||||
|
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
||||||
|
) {
|
||||||
|
previous_visible_entities.clear();
|
||||||
|
|
||||||
|
query.iter_mut().for_each(|(entity, view_visibility)| {
|
||||||
|
// Record the entities that were previously visible.
|
||||||
|
if view_visibility.get() {
|
||||||
|
previous_visible_entities.insert(entity);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System updating the visibility of entities each frame.
|
||||||
|
///
|
||||||
|
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each
|
||||||
|
/// frame, it updates the [`ViewVisibility`] of all entities, and for each view
|
||||||
|
/// also compute the [`VisibleEntities`] for that view.
|
||||||
|
///
|
||||||
|
/// To ensure that an entity is checked for visibility, make sure that it has a
|
||||||
|
/// [`VisibilityClass`] component and that that component is nonempty.
|
||||||
|
pub fn check_visibility(
|
||||||
|
mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,
|
||||||
|
mut view_query: Query<(
|
||||||
|
Entity,
|
||||||
|
&mut VisibleEntities,
|
||||||
|
&Frustum,
|
||||||
|
Option<&RenderLayers>,
|
||||||
|
&Camera,
|
||||||
|
Has<NoCpuCulling>,
|
||||||
|
)>,
|
||||||
|
mut visible_aabb_query: Query<(
|
||||||
|
Entity,
|
||||||
|
&InheritedVisibility,
|
||||||
|
&mut ViewVisibility,
|
||||||
|
&VisibilityClass,
|
||||||
|
Option<&RenderLayers>,
|
||||||
|
Option<&Aabb>,
|
||||||
|
&GlobalTransform,
|
||||||
|
Has<NoFrustumCulling>,
|
||||||
|
Has<VisibilityRange>,
|
||||||
|
)>,
|
||||||
|
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
|
||||||
|
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
||||||
|
) {
|
||||||
|
let visible_entity_ranges = visible_entity_ranges.as_deref();
|
||||||
|
|
||||||
|
for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in
|
||||||
|
&mut view_query
|
||||||
|
{
|
||||||
|
if !camera.is_active {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let view_mask = maybe_view_mask.unwrap_or_default();
|
||||||
|
|
||||||
|
visible_aabb_query.par_iter_mut().for_each_init(
|
||||||
|
|| thread_queues.borrow_local_mut(),
|
||||||
|
|queue, query_item| {
|
||||||
|
let (
|
||||||
|
entity,
|
||||||
|
inherited_visibility,
|
||||||
|
mut view_visibility,
|
||||||
|
visibility_class,
|
||||||
|
maybe_entity_mask,
|
||||||
|
maybe_model_aabb,
|
||||||
|
transform,
|
||||||
|
no_frustum_culling,
|
||||||
|
has_visibility_range,
|
||||||
|
) = query_item;
|
||||||
|
|
||||||
|
// Skip computing visibility for entities that are configured to be hidden.
|
||||||
|
// ViewVisibility has already been reset in `reset_view_visibility`.
|
||||||
|
if !inherited_visibility.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let entity_mask = maybe_entity_mask.unwrap_or_default();
|
||||||
|
if !view_mask.intersects(entity_mask) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If outside of the visibility range, cull.
|
||||||
|
if has_visibility_range
|
||||||
|
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
|
||||||
|
!visible_entity_ranges.entity_is_in_range_of_view(entity, view)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have an aabb, do frustum culling
|
||||||
|
if !no_frustum_culling && !no_cpu_culling {
|
||||||
|
if let Some(model_aabb) = maybe_model_aabb {
|
||||||
|
let world_from_local = transform.affine();
|
||||||
|
let model_sphere = Sphere {
|
||||||
|
center: world_from_local.transform_point3a(model_aabb.center),
|
||||||
|
radius: transform.radius_vec3a(model_aabb.half_extents),
|
||||||
|
};
|
||||||
|
// Do quick sphere-based frustum culling
|
||||||
|
if !frustum.intersects_sphere(&model_sphere, false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Do aabb-based frustum culling
|
||||||
|
if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we don't trigger changed notifications
|
||||||
|
// unnecessarily by checking whether the flag is set before
|
||||||
|
// setting it.
|
||||||
|
if !**view_visibility {
|
||||||
|
view_visibility.set();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the entity to the queue for all visibility classes the
|
||||||
|
// entity is in.
|
||||||
|
for visibility_class_id in visibility_class.iter() {
|
||||||
|
queue.entry(*visibility_class_id).or_default().push(entity);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
visible_entities.clear_all();
|
||||||
|
|
||||||
|
// Drain all the thread queues into the `visible_entities` list.
|
||||||
|
for class_queues in thread_queues.iter_mut() {
|
||||||
|
for (class, entities) in class_queues {
|
||||||
|
let visible_entities_for_class = visible_entities.get_mut(*class);
|
||||||
|
for entity in entities.drain(..) {
|
||||||
|
// As we mark entities as visible, we remove them from the
|
||||||
|
// `previous_visible_entities` list. At the end, all of the
|
||||||
|
// entities remaining in `previous_visible_entities` will be
|
||||||
|
// entities that were visible last frame but are no longer
|
||||||
|
// visible this frame.
|
||||||
|
previous_visible_entities.remove(&entity);
|
||||||
|
|
||||||
|
visible_entities_for_class.push(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks any entities that weren't judged visible this frame as invisible.
|
||||||
|
///
|
||||||
|
/// As visibility-determining systems run, they remove entities that they judge
|
||||||
|
/// visible from [`PreviousVisibleEntities`]. At the end of visibility
|
||||||
|
/// determination, all entities that remain in [`PreviousVisibleEntities`] must
|
||||||
|
/// be invisible. This system goes through those entities and marks them newly
|
||||||
|
/// invisible (which sets the change flag for them).
|
||||||
|
fn mark_newly_hidden_entities_invisible(
|
||||||
|
mut view_visibilities: Query<&mut ViewVisibility>,
|
||||||
|
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
||||||
|
) {
|
||||||
|
// Whatever previous visible entities are left are entities that were
|
||||||
|
// visible last frame but just became invisible.
|
||||||
|
for entity in previous_visible_entities.drain() {
|
||||||
|
if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) {
|
||||||
|
*view_visibility = ViewVisibility::HIDDEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic component add hook that automatically adds the appropriate
|
||||||
|
/// [`VisibilityClass`] to an entity.
|
||||||
|
///
|
||||||
|
/// This can be handy when creating custom renderable components. To use this
|
||||||
|
/// hook, add it to your renderable component like this:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// #[component(on_add = add_visibility_class::<MyComponent>)]
|
||||||
|
/// struct MyComponent {
|
||||||
|
/// ...
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn add_visibility_class<C>(
|
||||||
|
mut world: DeferredWorld<'_>,
|
||||||
|
HookContext { entity, .. }: HookContext,
|
||||||
|
) where
|
||||||
|
C: 'static,
|
||||||
|
{
|
||||||
|
if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
|
||||||
|
visibility_class.push(TypeId::of::<C>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use bevy_app::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visibility_propagation() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_systems(Update, visibility_propagate_system);
|
||||||
|
|
||||||
|
let root1 = app.world_mut().spawn(Visibility::Hidden).id();
|
||||||
|
let root1_child1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();
|
||||||
|
let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1)
|
||||||
|
.add_children(&[root1_child1, root1_child2]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1_child1)
|
||||||
|
.add_children(&[root1_child1_grandchild1]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1_child2)
|
||||||
|
.add_children(&[root1_child2_grandchild1]);
|
||||||
|
|
||||||
|
let root2 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
let root2_child1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();
|
||||||
|
let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
||||||
|
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root2)
|
||||||
|
.add_children(&[root2_child1, root2_child2]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root2_child1)
|
||||||
|
.add_children(&[root2_child1_grandchild1]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root2_child2)
|
||||||
|
.add_children(&[root2_child2_grandchild1]);
|
||||||
|
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let is_visible = |e: Entity| {
|
||||||
|
app.world()
|
||||||
|
.entity(e)
|
||||||
|
.get::<InheritedVisibility>()
|
||||||
|
.unwrap()
|
||||||
|
.get()
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1),
|
||||||
|
"invisibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1_child1),
|
||||||
|
"invisibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1_child2),
|
||||||
|
"invisibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1_child1_grandchild1),
|
||||||
|
"invisibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1_child2_grandchild1),
|
||||||
|
"invisibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
is_visible(root2),
|
||||||
|
"visibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_visible(root2_child1),
|
||||||
|
"visibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root2_child2),
|
||||||
|
"visibility propagates down tree from root, but local invisibility is preserved"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_visible(root2_child1_grandchild1),
|
||||||
|
"visibility propagates down tree from root"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root2_child2_grandchild1),
|
||||||
|
"child's invisibility propagates down to grandchild"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_visibility_propagation_on_parent_change() {
|
||||||
|
// Setup the world and schedule
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.add_systems(Update, visibility_propagate_system);
|
||||||
|
|
||||||
|
// Create entities with visibility and hierarchy
|
||||||
|
let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();
|
||||||
|
let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();
|
||||||
|
let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();
|
||||||
|
let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();
|
||||||
|
|
||||||
|
// Build hierarchy
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(parent1)
|
||||||
|
.add_children(&[child1, child2]);
|
||||||
|
|
||||||
|
// Run the system initially to set up visibility
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
// Change parent visibility to Hidden
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(parent2)
|
||||||
|
.insert(Visibility::Visible);
|
||||||
|
// Simulate a change in the parent component
|
||||||
|
app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent
|
||||||
|
|
||||||
|
// Run the system again to propagate changes
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let is_visible = |e: Entity| {
|
||||||
|
app.world()
|
||||||
|
.entity(e)
|
||||||
|
.get::<InheritedVisibility>()
|
||||||
|
.unwrap()
|
||||||
|
.get()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Retrieve and assert visibility
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
!is_visible(child1),
|
||||||
|
"Child1 should inherit visibility from parent"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
is_visible(child2),
|
||||||
|
"Child2 should inherit visibility from parent"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visibility_propagation_unconditional_visible() {
|
||||||
|
use Visibility::{Hidden, Inherited, Visible};
|
||||||
|
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_systems(Update, visibility_propagate_system);
|
||||||
|
|
||||||
|
let root1 = app.world_mut().spawn(Visible).id();
|
||||||
|
let root1_child1 = app.world_mut().spawn(Inherited).id();
|
||||||
|
let root1_child2 = app.world_mut().spawn(Hidden).id();
|
||||||
|
let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();
|
||||||
|
let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();
|
||||||
|
|
||||||
|
let root2 = app.world_mut().spawn(Inherited).id();
|
||||||
|
let root3 = app.world_mut().spawn(Hidden).id();
|
||||||
|
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1)
|
||||||
|
.add_children(&[root1_child1, root1_child2]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1_child1)
|
||||||
|
.add_children(&[root1_child1_grandchild1]);
|
||||||
|
app.world_mut()
|
||||||
|
.entity_mut(root1_child2)
|
||||||
|
.add_children(&[root1_child2_grandchild1]);
|
||||||
|
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let is_visible = |e: Entity| {
|
||||||
|
app.world()
|
||||||
|
.entity(e)
|
||||||
|
.get::<InheritedVisibility>()
|
||||||
|
.unwrap()
|
||||||
|
.get()
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
is_visible(root1),
|
||||||
|
"an unconditionally visible root is visible"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_visible(root1_child1),
|
||||||
|
"an inheriting child of an unconditionally visible parent is visible"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!is_visible(root1_child2),
|
||||||
|
"a hidden child on an unconditionally visible parent is hidden"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_visible(root1_child1_grandchild1),
|
||||||
|
"an unconditionally visible child of an inheriting parent is visible"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
is_visible(root1_child2_grandchild1),
|
||||||
|
"an unconditionally visible child of a hidden parent is visible"
|
||||||
|
);
|
||||||
|
assert!(is_visible(root2), "an inheriting root is visible");
|
||||||
|
assert!(!is_visible(root3), "a hidden root is hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visibility_propagation_change_detection() {
|
||||||
|
let mut world = World::new();
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.add_systems(visibility_propagate_system);
|
||||||
|
|
||||||
|
// Set up an entity hierarchy.
|
||||||
|
|
||||||
|
let id1 = world.spawn(Visibility::default()).id();
|
||||||
|
|
||||||
|
let id2 = world.spawn(Visibility::default()).id();
|
||||||
|
world.entity_mut(id1).add_children(&[id2]);
|
||||||
|
|
||||||
|
let id3 = world.spawn(Visibility::Hidden).id();
|
||||||
|
world.entity_mut(id2).add_children(&[id3]);
|
||||||
|
|
||||||
|
let id4 = world.spawn(Visibility::default()).id();
|
||||||
|
world.entity_mut(id3).add_children(&[id4]);
|
||||||
|
|
||||||
|
// Test the hierarchy.
|
||||||
|
|
||||||
|
// Make sure the hierarchy is up-to-date.
|
||||||
|
schedule.run(&mut world);
|
||||||
|
world.clear_trackers();
|
||||||
|
|
||||||
|
let mut q = world.query::<Ref<InheritedVisibility>>();
|
||||||
|
|
||||||
|
assert!(!q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id4).unwrap().is_changed());
|
||||||
|
|
||||||
|
world.clear_trackers();
|
||||||
|
world.entity_mut(id1).insert(Visibility::Hidden);
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
assert!(q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id4).unwrap().is_changed());
|
||||||
|
|
||||||
|
world.clear_trackers();
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
assert!(!q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id4).unwrap().is_changed());
|
||||||
|
|
||||||
|
world.clear_trackers();
|
||||||
|
world.entity_mut(id3).insert(Visibility::Inherited);
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
assert!(!q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id4).unwrap().is_changed());
|
||||||
|
|
||||||
|
world.clear_trackers();
|
||||||
|
world.entity_mut(id2).insert(Visibility::Visible);
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
assert!(!q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(q.get(&world, id4).unwrap().is_changed());
|
||||||
|
|
||||||
|
world.clear_trackers();
|
||||||
|
schedule.run(&mut world);
|
||||||
|
|
||||||
|
assert!(!q.get(&world, id1).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id2).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id3).unwrap().is_changed());
|
||||||
|
assert!(!q.get(&world, id4).unwrap().is_changed());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn visibility_propagation_with_invalid_parent() {
|
||||||
|
let mut world = World::new();
|
||||||
|
let mut schedule = Schedule::default();
|
||||||
|
schedule.add_systems(visibility_propagate_system);
|
||||||
|
|
||||||
|
let parent = world.spawn(()).id();
|
||||||
|
let child = world.spawn(Visibility::default()).id();
|
||||||
|
world.entity_mut(parent).add_children(&[child]);
|
||||||
|
|
||||||
|
schedule.run(&mut world);
|
||||||
|
world.clear_trackers();
|
||||||
|
|
||||||
|
let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
|
||||||
|
// defaults to same behavior of parent not found: visible = true
|
||||||
|
assert!(child_visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ensure_visibility_enum_size() {
|
||||||
|
assert_eq!(1, size_of::<Visibility>());
|
||||||
|
assert_eq!(1, size_of::<Option<Visibility>>());
|
||||||
|
}
|
||||||
|
}
|
||||||
295
crates/bevy_camera/src/visibility/range.rs
Normal file
295
crates/bevy_camera/src/visibility/range.rs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
//! Specific distances from the camera in which entities are visible, also known
|
||||||
|
//! as *hierarchical levels of detail* or *HLOD*s.
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
ops::Range,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy_app::{App, Plugin, PostUpdate};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
entity::{Entity, EntityHashMap},
|
||||||
|
query::With,
|
||||||
|
reflect::ReflectComponent,
|
||||||
|
resource::Resource,
|
||||||
|
schedule::IntoScheduleConfigs as _,
|
||||||
|
system::{Local, Query, ResMut},
|
||||||
|
};
|
||||||
|
use bevy_math::FloatOrd;
|
||||||
|
use bevy_reflect::Reflect;
|
||||||
|
use bevy_transform::components::GlobalTransform;
|
||||||
|
use bevy_utils::Parallel;
|
||||||
|
|
||||||
|
use super::{check_visibility, VisibilitySystems};
|
||||||
|
use crate::{camera::Camera, primitives::Aabb};
|
||||||
|
|
||||||
|
/// A plugin that enables [`VisibilityRange`]s, which allow entities to be
|
||||||
|
/// hidden or shown based on distance to the camera.
|
||||||
|
pub struct VisibilityRangePlugin;
|
||||||
|
|
||||||
|
impl Plugin for VisibilityRangePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<VisibilityRange>()
|
||||||
|
.init_resource::<VisibleEntityRanges>()
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
check_visibility_ranges
|
||||||
|
.in_set(VisibilitySystems::CheckVisibility)
|
||||||
|
.before(check_visibility),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the range of distances that this entity must be from the camera in
|
||||||
|
/// order to be rendered.
|
||||||
|
///
|
||||||
|
/// This is also known as *hierarchical level of detail* or *HLOD*.
|
||||||
|
///
|
||||||
|
/// Use this component when you want to render a high-polygon mesh when the
|
||||||
|
/// camera is close and a lower-polygon mesh when the camera is far away. This
|
||||||
|
/// is a common technique for improving performance, because fine details are
|
||||||
|
/// hard to see in a mesh at a distance. To avoid an artifact known as *popping*
|
||||||
|
/// between levels, each level has a *margin*, within which the object
|
||||||
|
/// transitions gradually from invisible to visible using a dithering effect.
|
||||||
|
///
|
||||||
|
/// You can also use this feature to replace multiple meshes with a single mesh
|
||||||
|
/// when the camera is distant. This is the reason for the term "*hierarchical*
|
||||||
|
/// level of detail". Reducing the number of meshes can be useful for reducing
|
||||||
|
/// drawcall count. Note that you must place the [`VisibilityRange`] component
|
||||||
|
/// on each entity you want to be part of a LOD group, as [`VisibilityRange`]
|
||||||
|
/// isn't automatically propagated down to children.
|
||||||
|
///
|
||||||
|
/// A typical use of this feature might look like this:
|
||||||
|
///
|
||||||
|
/// | Entity | `start_margin` | `end_margin` |
|
||||||
|
/// |-------------------------|----------------|--------------|
|
||||||
|
/// | Root | N/A | N/A |
|
||||||
|
/// | ├─ High-poly mesh | [0, 0) | [20, 25) |
|
||||||
|
/// | ├─ Low-poly mesh | [20, 25) | [70, 75) |
|
||||||
|
/// | └─ Billboard *imposter* | [70, 75) | [150, 160) |
|
||||||
|
///
|
||||||
|
/// With this setup, the user will see a high-poly mesh when the camera is
|
||||||
|
/// closer than 20 units. As the camera zooms out, between 20 units to 25 units,
|
||||||
|
/// the high-poly mesh will gradually fade to a low-poly mesh. When the camera
|
||||||
|
/// is 70 to 75 units away, the low-poly mesh will fade to a single textured
|
||||||
|
/// quad. And between 150 and 160 units, the object fades away entirely. Note
|
||||||
|
/// that the `end_margin` of a higher LOD is always identical to the
|
||||||
|
/// `start_margin` of the next lower LOD; this is important for the crossfade
|
||||||
|
/// effect to function properly.
|
||||||
|
#[derive(Component, Clone, PartialEq, Default, Reflect)]
|
||||||
|
#[reflect(Component, PartialEq, Hash, Clone)]
|
||||||
|
pub struct VisibilityRange {
|
||||||
|
/// The range of distances, in world units, between which this entity will
|
||||||
|
/// smoothly fade into view as the camera zooms out.
|
||||||
|
///
|
||||||
|
/// If the start and end of this range are identical, the transition will be
|
||||||
|
/// abrupt, with no crossfading.
|
||||||
|
///
|
||||||
|
/// `start_margin.end` must be less than or equal to `end_margin.start`.
|
||||||
|
pub start_margin: Range<f32>,
|
||||||
|
|
||||||
|
/// The range of distances, in world units, between which this entity will
|
||||||
|
/// smoothly fade out of view as the camera zooms out.
|
||||||
|
///
|
||||||
|
/// If the start and end of this range are identical, the transition will be
|
||||||
|
/// abrupt, with no crossfading.
|
||||||
|
///
|
||||||
|
/// `end_margin.start` must be greater than or equal to `start_margin.end`.
|
||||||
|
pub end_margin: Range<f32>,
|
||||||
|
|
||||||
|
/// If set to true, Bevy will use the center of the axis-aligned bounding
|
||||||
|
/// box ([`Aabb`]) as the position of the mesh for the purposes of
|
||||||
|
/// visibility range computation.
|
||||||
|
///
|
||||||
|
/// Otherwise, if this field is set to false, Bevy will use the origin of
|
||||||
|
/// the mesh as the mesh's position.
|
||||||
|
///
|
||||||
|
/// Usually you will want to leave this set to false, because different LODs
|
||||||
|
/// may have different AABBs, and smooth crossfades between LOD levels
|
||||||
|
/// require that all LODs of a mesh be at *precisely* the same position. If
|
||||||
|
/// you aren't using crossfading, however, and your meshes aren't centered
|
||||||
|
/// around their origins, then this flag may be useful.
|
||||||
|
pub use_aabb: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for VisibilityRange {}
|
||||||
|
|
||||||
|
impl Hash for VisibilityRange {
|
||||||
|
fn hash<H>(&self, state: &mut H)
|
||||||
|
where
|
||||||
|
H: Hasher,
|
||||||
|
{
|
||||||
|
FloatOrd(self.start_margin.start).hash(state);
|
||||||
|
FloatOrd(self.start_margin.end).hash(state);
|
||||||
|
FloatOrd(self.end_margin.start).hash(state);
|
||||||
|
FloatOrd(self.end_margin.end).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisibilityRange {
|
||||||
|
/// Creates a new *abrupt* visibility range, with no crossfade.
|
||||||
|
///
|
||||||
|
/// There will be no crossfade; the object will immediately vanish if the
|
||||||
|
/// camera is closer than `start` units or farther than `end` units from the
|
||||||
|
/// model.
|
||||||
|
///
|
||||||
|
/// The `start` value must be less than or equal to the `end` value.
|
||||||
|
#[inline]
|
||||||
|
pub fn abrupt(start: f32, end: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
start_margin: start..start,
|
||||||
|
end_margin: end..end,
|
||||||
|
use_aabb: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if both the start and end transitions for this range are
|
||||||
|
/// abrupt: that is, there is no crossfading.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_abrupt(&self) -> bool {
|
||||||
|
self.start_margin.start == self.start_margin.end
|
||||||
|
&& self.end_margin.start == self.end_margin.end
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the object will be visible at all, given a camera
|
||||||
|
/// `camera_distance` units away.
|
||||||
|
///
|
||||||
|
/// Any amount of visibility, even with the heaviest dithering applied, is
|
||||||
|
/// considered visible according to this check.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_visible_at_all(&self, camera_distance: f32) -> bool {
|
||||||
|
camera_distance >= self.start_margin.start && camera_distance < self.end_margin.end
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the object is completely invisible, given a camera
|
||||||
|
/// `camera_distance` units away.
|
||||||
|
///
|
||||||
|
/// This is equivalent to `!VisibilityRange::is_visible_at_all()`.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_culled(&self, camera_distance: f32) -> bool {
|
||||||
|
!self.is_visible_at_all(camera_distance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores which entities are in within the [`VisibilityRange`]s of views.
|
||||||
|
///
|
||||||
|
/// This doesn't store the results of frustum or occlusion culling; use
|
||||||
|
/// [`super::ViewVisibility`] for that. Thus entities in this list may not
|
||||||
|
/// actually be visible.
|
||||||
|
///
|
||||||
|
/// For efficiency, these tables only store entities that have
|
||||||
|
/// [`VisibilityRange`] components. Entities without such a component won't be
|
||||||
|
/// in these tables at all.
|
||||||
|
///
|
||||||
|
/// The table is indexed by entity and stores a 32-bit bitmask with one bit for
|
||||||
|
/// each camera, where a 0 bit corresponds to "out of range" and a 1 bit
|
||||||
|
/// corresponds to "in range". Hence it's limited to storing information for 32
|
||||||
|
/// views.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct VisibleEntityRanges {
|
||||||
|
/// Stores which bit index each view corresponds to.
|
||||||
|
views: EntityHashMap<u8>,
|
||||||
|
|
||||||
|
/// Stores a bitmask in which each view has a single bit.
|
||||||
|
///
|
||||||
|
/// A 0 bit for a view corresponds to "out of range"; a 1 bit corresponds to
|
||||||
|
/// "in range".
|
||||||
|
entities: EntityHashMap<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisibleEntityRanges {
|
||||||
|
/// Clears out the [`VisibleEntityRanges`] in preparation for a new frame.
|
||||||
|
fn clear(&mut self) {
|
||||||
|
self.views.clear();
|
||||||
|
self.entities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the entity is in range of the given camera.
|
||||||
|
///
|
||||||
|
/// This only checks [`VisibilityRange`]s and doesn't perform any frustum or
|
||||||
|
/// occlusion culling. Thus the entity might not *actually* be visible.
|
||||||
|
///
|
||||||
|
/// The entity is assumed to have a [`VisibilityRange`] component. If the
|
||||||
|
/// entity doesn't have that component, this method will return false.
|
||||||
|
#[inline]
|
||||||
|
pub fn entity_is_in_range_of_view(&self, entity: Entity, view: Entity) -> bool {
|
||||||
|
let Some(visibility_bitmask) = self.entities.get(&entity) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(view_index) = self.views.get(&view) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
(visibility_bitmask & (1 << view_index)) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the entity is in range of any view.
|
||||||
|
///
|
||||||
|
/// This only checks [`VisibilityRange`]s and doesn't perform any frustum or
|
||||||
|
/// occlusion culling. Thus the entity might not *actually* be visible.
|
||||||
|
///
|
||||||
|
/// The entity is assumed to have a [`VisibilityRange`] component. If the
|
||||||
|
/// entity doesn't have that component, this method will return false.
|
||||||
|
#[inline]
|
||||||
|
pub fn entity_is_in_range_of_any_view(&self, entity: Entity) -> bool {
|
||||||
|
self.entities.contains_key(&entity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks all entities against all views in order to determine which entities
|
||||||
|
/// with [`VisibilityRange`]s are potentially visible.
|
||||||
|
///
|
||||||
|
/// This only checks distance from the camera and doesn't frustum or occlusion
|
||||||
|
/// cull.
|
||||||
|
pub fn check_visibility_ranges(
|
||||||
|
mut visible_entity_ranges: ResMut<VisibleEntityRanges>,
|
||||||
|
view_query: Query<(Entity, &GlobalTransform), With<Camera>>,
|
||||||
|
mut par_local: Local<Parallel<Vec<(Entity, u32)>>>,
|
||||||
|
entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>,
|
||||||
|
) {
|
||||||
|
visible_entity_ranges.clear();
|
||||||
|
|
||||||
|
// Early out if the visibility range feature isn't in use.
|
||||||
|
if entity_query.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign an index to each view.
|
||||||
|
let mut views = vec![];
|
||||||
|
for (view, view_transform) in view_query.iter().take(32) {
|
||||||
|
let view_index = views.len() as u8;
|
||||||
|
visible_entity_ranges.views.insert(view, view_index);
|
||||||
|
views.push((view, view_transform.translation_vec3a()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each entity/view pair. Only consider entities with
|
||||||
|
// [`VisibilityRange`] components.
|
||||||
|
entity_query.par_iter().for_each(
|
||||||
|
|(entity, entity_transform, maybe_model_aabb, visibility_range)| {
|
||||||
|
let mut visibility = 0;
|
||||||
|
for (view_index, &(_, view_position)) in views.iter().enumerate() {
|
||||||
|
// If instructed to use the AABB and the model has one, use its
|
||||||
|
// center as the model position. Otherwise, use the model's
|
||||||
|
// translation.
|
||||||
|
let model_position = match (visibility_range.use_aabb, maybe_model_aabb) {
|
||||||
|
(true, Some(model_aabb)) => entity_transform
|
||||||
|
.affine()
|
||||||
|
.transform_point3a(model_aabb.center),
|
||||||
|
_ => entity_transform.translation_vec3a(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if visibility_range.is_visible_at_all((view_position - model_position).length()) {
|
||||||
|
visibility |= 1 << view_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invisible entities have no entry at all in the hash map. This speeds
|
||||||
|
// up checks slightly in this common case.
|
||||||
|
if visibility != 0 {
|
||||||
|
par_local.borrow_local_mut().push((entity, visibility));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
visible_entity_ranges.entities.extend(par_local.drain());
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ use bevy_platform::collections::HashSet;
|
|||||||
use bevy_platform::time::Instant;
|
use bevy_platform::time::Instant;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{Camera, ExtractedCamera},
|
camera::{Camera, ExtractedCamera, ToNormalizedRenderTarget as _},
|
||||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||||
load_shader_library,
|
load_shader_library,
|
||||||
render_graph::{RenderGraphExt, ViewNodeRunner},
|
render_graph::{RenderGraphExt, ViewNodeRunner},
|
||||||
|
|||||||
@ -94,10 +94,10 @@ use core::{
|
|||||||
note = "consider annotating `{Self}` with `#[derive(Event)]`"
|
note = "consider annotating `{Self}` with `#[derive(Event)]`"
|
||||||
)]
|
)]
|
||||||
pub trait Event: Send + Sync + 'static {
|
pub trait Event: Send + Sync + 'static {
|
||||||
/// Generates the [`ComponentId`] for this event type.
|
/// Generates the [`EventKey`] for this event type.
|
||||||
///
|
///
|
||||||
/// If this type has already been registered,
|
/// If this type has already been registered,
|
||||||
/// this will return the existing [`ComponentId`].
|
/// this will return the existing [`EventKey`].
|
||||||
///
|
///
|
||||||
/// This is used by various dynamically typed observer APIs,
|
/// This is used by various dynamically typed observer APIs,
|
||||||
/// such as [`World::trigger_targets_dynamic`].
|
/// such as [`World::trigger_targets_dynamic`].
|
||||||
@ -105,12 +105,12 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// This method should not be overridden by implementers,
|
/// This method should not be overridden by implementers,
|
||||||
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
|
/// and should always correspond to the implementation of [`event_key`](Event::event_key).
|
||||||
fn register_component_id(world: &mut World) -> ComponentId {
|
fn register_event_key(world: &mut World) -> EventKey {
|
||||||
world.register_component::<EventWrapperComponent<Self>>()
|
EventKey(world.register_component::<EventWrapperComponent<Self>>())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches the [`ComponentId`] for this event type,
|
/// Fetches the [`EventKey`] for this event type,
|
||||||
/// if it has already been generated.
|
/// if it has already been generated.
|
||||||
///
|
///
|
||||||
/// This is used by various dynamically typed observer APIs,
|
/// This is used by various dynamically typed observer APIs,
|
||||||
@ -119,9 +119,12 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// This method should not be overridden by implementers,
|
/// This method should not be overridden by implementers,
|
||||||
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
|
/// and should always correspond to the implementation of
|
||||||
fn component_id(world: &World) -> Option<ComponentId> {
|
/// [`register_event_key`](Event::register_event_key).
|
||||||
world.component_id::<EventWrapperComponent<Self>>()
|
fn event_key(world: &World) -> Option<EventKey> {
|
||||||
|
world
|
||||||
|
.component_id::<EventWrapperComponent<Self>>()
|
||||||
|
.map(EventKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,3 +424,19 @@ pub(crate) struct EventInstance<E: BufferedEvent> {
|
|||||||
pub event_id: EventId<E>,
|
pub event_id: EventId<E>,
|
||||||
pub event: E,
|
pub event: E,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A unique identifier for an [`Event`], used by [observers].
|
||||||
|
///
|
||||||
|
/// You can look up the key for your event by calling the [`Event::event_key`] method.
|
||||||
|
///
|
||||||
|
/// [observers]: crate::observer
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||||
|
pub struct EventKey(pub(crate) ComponentId);
|
||||||
|
|
||||||
|
impl EventKey {
|
||||||
|
/// Returns the internal [`ComponentId`].
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn component_id(&self) -> ComponentId {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ mod update;
|
|||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
pub(crate) use base::EventInstance;
|
pub(crate) use base::EventInstance;
|
||||||
pub use base::{BufferedEvent, EntityEvent, Event, EventId};
|
pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey};
|
||||||
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
|
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
|
||||||
pub use collections::{Events, SendBatchIds};
|
pub use collections::{Events, SendBatchIds};
|
||||||
pub use event_cursor::EventCursor;
|
pub use event_cursor::EventCursor;
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::{DetectChangesMut, MutUntyped},
|
change_detection::{DetectChangesMut, MutUntyped},
|
||||||
component::{ComponentId, Tick},
|
component::Tick,
|
||||||
event::{BufferedEvent, Events},
|
event::{BufferedEvent, EventKey, Events},
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
struct RegisteredEvent {
|
struct RegisteredEvent {
|
||||||
component_id: ComponentId,
|
event_key: EventKey,
|
||||||
// Required to flush the secondary buffer and drop events even if left unchanged.
|
// Required to flush the secondary buffer and drop events even if left unchanged.
|
||||||
previously_updated: bool,
|
previously_updated: bool,
|
||||||
// SAFETY: The component ID and the function must be used to fetch the Events<T> resource
|
// SAFETY: The component ID and the function must be used to fetch the Events<T> resource
|
||||||
@ -51,7 +51,7 @@ impl EventRegistry {
|
|||||||
let component_id = world.init_resource::<Events<T>>();
|
let component_id = world.init_resource::<Events<T>>();
|
||||||
let mut registry = world.get_resource_or_init::<Self>();
|
let mut registry = world.get_resource_or_init::<Self>();
|
||||||
registry.event_updates.push(RegisteredEvent {
|
registry.event_updates.push(RegisteredEvent {
|
||||||
component_id,
|
event_key: EventKey(component_id),
|
||||||
previously_updated: false,
|
previously_updated: false,
|
||||||
update: |ptr| {
|
update: |ptr| {
|
||||||
// SAFETY: The resource was initialized with the type Events<T>.
|
// SAFETY: The resource was initialized with the type Events<T>.
|
||||||
@ -66,7 +66,9 @@ impl EventRegistry {
|
|||||||
pub fn run_updates(&mut self, world: &mut World, last_change_tick: Tick) {
|
pub fn run_updates(&mut self, world: &mut World, last_change_tick: Tick) {
|
||||||
for registered_event in &mut self.event_updates {
|
for registered_event in &mut self.event_updates {
|
||||||
// Bypass the type ID -> Component ID lookup with the cached component ID.
|
// Bypass the type ID -> Component ID lookup with the cached component ID.
|
||||||
if let Some(events) = world.get_resource_mut_by_id(registered_event.component_id) {
|
if let Some(events) =
|
||||||
|
world.get_resource_mut_by_id(registered_event.event_key.component_id())
|
||||||
|
{
|
||||||
let has_changed = events.has_changed_since(last_change_tick);
|
let has_changed = events.has_changed_since(last_change_tick);
|
||||||
if registered_event.previously_updated || has_changed {
|
if registered_event.previously_updated || has_changed {
|
||||||
// SAFETY: The update function pointer is called with the resource
|
// SAFETY: The update function pointer is called with the resource
|
||||||
@ -87,7 +89,7 @@ impl EventRegistry {
|
|||||||
let mut registry = world.get_resource_or_init::<Self>();
|
let mut registry = world.get_resource_or_init::<Self>();
|
||||||
registry
|
registry
|
||||||
.event_updates
|
.event_updates
|
||||||
.retain(|e| e.component_id != component_id);
|
.retain(|e| e.event_key.component_id() != component_id);
|
||||||
world.remove_resource::<Events<T>>();
|
world.remove_resource::<Events<T>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -79,7 +79,8 @@ pub mod prelude {
|
|||||||
entity::{ContainsEntity, Entity, EntityMapper},
|
entity::{ContainsEntity, Entity, EntityMapper},
|
||||||
error::{BevyError, Result},
|
error::{BevyError, Result},
|
||||||
event::{
|
event::{
|
||||||
BufferedEvent, EntityEvent, Event, EventMutator, EventReader, EventWriter, Events,
|
BufferedEvent, EntityEvent, Event, EventKey, EventMutator, EventReader, EventWriter,
|
||||||
|
Events,
|
||||||
},
|
},
|
||||||
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
||||||
lifecycle::{
|
lifecycle::{
|
||||||
|
|||||||
@ -55,7 +55,7 @@ use crate::{
|
|||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{
|
event::{
|
||||||
BufferedEvent, EntityEvent, Event, EventCursor, EventId, EventIterator,
|
BufferedEvent, EntityEvent, Event, EventCursor, EventId, EventIterator,
|
||||||
EventIteratorWithId, Events,
|
EventIteratorWithId, EventKey, Events,
|
||||||
},
|
},
|
||||||
query::FilteredAccessSet,
|
query::FilteredAccessSet,
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
@ -314,16 +314,16 @@ impl ComponentHooks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`ComponentId`] for [`Add`]
|
/// [`EventKey`] for [`Add`]
|
||||||
pub const ADD: ComponentId = ComponentId::new(0);
|
pub const ADD: EventKey = EventKey(ComponentId::new(0));
|
||||||
/// [`ComponentId`] for [`Insert`]
|
/// [`EventKey`] for [`Insert`]
|
||||||
pub const INSERT: ComponentId = ComponentId::new(1);
|
pub const INSERT: EventKey = EventKey(ComponentId::new(1));
|
||||||
/// [`ComponentId`] for [`Replace`]
|
/// [`EventKey`] for [`Replace`]
|
||||||
pub const REPLACE: ComponentId = ComponentId::new(2);
|
pub const REPLACE: EventKey = EventKey(ComponentId::new(2));
|
||||||
/// [`ComponentId`] for [`Remove`]
|
/// [`EventKey`] for [`Remove`]
|
||||||
pub const REMOVE: ComponentId = ComponentId::new(3);
|
pub const REMOVE: EventKey = EventKey(ComponentId::new(3));
|
||||||
/// [`ComponentId`] for [`Despawn`]
|
/// [`EventKey`] for [`Despawn`]
|
||||||
pub const DESPAWN: ComponentId = ComponentId::new(4);
|
pub const DESPAWN: EventKey = EventKey(ComponentId::new(4));
|
||||||
|
|
||||||
/// Trigger emitted when a component is inserted onto an entity that does not already have that
|
/// Trigger emitted when a component is inserted onto an entity that does not already have that
|
||||||
/// component. Runs before `Insert`.
|
/// component. Runs before `Insert`.
|
||||||
|
|||||||
@ -37,44 +37,44 @@ pub struct Observers {
|
|||||||
remove: CachedObservers,
|
remove: CachedObservers,
|
||||||
despawn: CachedObservers,
|
despawn: CachedObservers,
|
||||||
// Map from trigger type to set of observers listening to that trigger
|
// Map from trigger type to set of observers listening to that trigger
|
||||||
cache: HashMap<ComponentId, CachedObservers>,
|
cache: HashMap<EventKey, CachedObservers>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Observers {
|
impl Observers {
|
||||||
pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
pub(crate) fn get_observers_mut(&mut self, event_key: EventKey) -> &mut CachedObservers {
|
||||||
use crate::lifecycle::*;
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_key {
|
||||||
ADD => &mut self.add,
|
ADD => &mut self.add,
|
||||||
INSERT => &mut self.insert,
|
INSERT => &mut self.insert,
|
||||||
REPLACE => &mut self.replace,
|
REPLACE => &mut self.replace,
|
||||||
REMOVE => &mut self.remove,
|
REMOVE => &mut self.remove,
|
||||||
DESPAWN => &mut self.despawn,
|
DESPAWN => &mut self.despawn,
|
||||||
_ => self.cache.entry(event_type).or_default(),
|
_ => self.cache.entry(event_key).or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to get the observers for the given `event_type`.
|
/// Attempts to get the observers for the given `event_key`.
|
||||||
///
|
///
|
||||||
/// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`],
|
/// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`],
|
||||||
/// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module.
|
/// use the [`EventKey`] constants from the [`lifecycle`](crate::lifecycle) module.
|
||||||
pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
pub fn try_get_observers(&self, event_key: EventKey) -> Option<&CachedObservers> {
|
||||||
use crate::lifecycle::*;
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_key {
|
||||||
ADD => Some(&self.add),
|
ADD => Some(&self.add),
|
||||||
INSERT => Some(&self.insert),
|
INSERT => Some(&self.insert),
|
||||||
REPLACE => Some(&self.replace),
|
REPLACE => Some(&self.replace),
|
||||||
REMOVE => Some(&self.remove),
|
REMOVE => Some(&self.remove),
|
||||||
DESPAWN => Some(&self.despawn),
|
DESPAWN => Some(&self.despawn),
|
||||||
_ => self.cache.get(&event_type),
|
_ => self.cache.get(&event_key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
|
/// This will run the observers of the given `event_key`, targeting the given `entity` and `components`.
|
||||||
pub(crate) fn invoke<T>(
|
pub(crate) fn invoke<T>(
|
||||||
mut world: DeferredWorld,
|
mut world: DeferredWorld,
|
||||||
event_type: ComponentId,
|
event_key: EventKey,
|
||||||
current_target: Option<Entity>,
|
current_target: Option<Entity>,
|
||||||
original_target: Option<Entity>,
|
original_target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
@ -88,7 +88,7 @@ impl Observers {
|
|||||||
// SAFETY: There are no outstanding world references
|
// SAFETY: There are no outstanding world references
|
||||||
world.increment_trigger_id();
|
world.increment_trigger_id();
|
||||||
let observers = world.observers();
|
let observers = world.observers();
|
||||||
let Some(observers) = observers.try_get_observers(event_type) else {
|
let Some(observers) = observers.try_get_observers(event_key) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// SAFETY: The only outstanding reference to world is `observers`
|
// SAFETY: The only outstanding reference to world is `observers`
|
||||||
@ -102,7 +102,7 @@ impl Observers {
|
|||||||
world.reborrow(),
|
world.reborrow(),
|
||||||
ObserverTrigger {
|
ObserverTrigger {
|
||||||
observer,
|
observer,
|
||||||
event_type,
|
event_key,
|
||||||
components: components.clone().collect(),
|
components: components.clone().collect(),
|
||||||
current_target,
|
current_target,
|
||||||
original_target,
|
original_target,
|
||||||
@ -145,10 +145,10 @@ impl Observers {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
pub(crate) fn is_archetype_cached(event_key: EventKey) -> Option<ArchetypeFlags> {
|
||||||
use crate::lifecycle::*;
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_key {
|
||||||
ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||||
INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||||
REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
|
REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
|
||||||
|
|||||||
@ -289,9 +289,9 @@ impl Observer {
|
|||||||
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
|
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
|
||||||
/// is triggered.
|
/// is triggered.
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The type of the `event` [`ComponentId`] _must_ match the actual value
|
/// The type of the `event` [`EventKey`] _must_ match the actual value
|
||||||
/// of the event passed into the observer system.
|
/// of the event passed into the observer system.
|
||||||
pub unsafe fn with_event(mut self, event: ComponentId) -> Self {
|
pub unsafe fn with_event(mut self, event: EventKey) -> Self {
|
||||||
self.descriptor.events.push(event);
|
self.descriptor.events.push(event);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -350,7 +350,7 @@ impl Component for Observer {
|
|||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct ObserverDescriptor {
|
pub struct ObserverDescriptor {
|
||||||
/// The events the observer is watching.
|
/// The events the observer is watching.
|
||||||
pub(super) events: Vec<ComponentId>,
|
pub(super) events: Vec<EventKey>,
|
||||||
|
|
||||||
/// The components the observer is watching.
|
/// The components the observer is watching.
|
||||||
pub(super) components: Vec<ComponentId>,
|
pub(super) components: Vec<ComponentId>,
|
||||||
@ -362,9 +362,9 @@ pub struct ObserverDescriptor {
|
|||||||
impl ObserverDescriptor {
|
impl ObserverDescriptor {
|
||||||
/// Add the given `events` to the descriptor.
|
/// Add the given `events` to the descriptor.
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The type of each [`ComponentId`] in `events` _must_ match the actual value
|
/// The type of each [`EventKey`] in `events` _must_ match the actual value
|
||||||
/// of the event passed into the observer.
|
/// of the event passed into the observer.
|
||||||
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
|
pub unsafe fn with_events(mut self, events: Vec<EventKey>) -> Self {
|
||||||
self.events = events;
|
self.events = events;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -382,7 +382,7 @@ impl ObserverDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the `events` that the observer is watching.
|
/// Returns the `events` that the observer is watching.
|
||||||
pub fn events(&self) -> &[ComponentId] {
|
pub fn events(&self) -> &[EventKey] {
|
||||||
&self.events
|
&self.events
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,13 +410,13 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
|||||||
HookContext { entity, .. }: HookContext,
|
HookContext { entity, .. }: HookContext,
|
||||||
) {
|
) {
|
||||||
world.commands().queue(move |world: &mut World| {
|
world.commands().queue(move |world: &mut World| {
|
||||||
let event_id = E::register_component_id(world);
|
let event_key = E::register_event_key(world);
|
||||||
let mut components = alloc::vec![];
|
let mut components = alloc::vec![];
|
||||||
B::component_ids(&mut world.components_registrator(), &mut |id| {
|
B::component_ids(&mut world.components_registrator(), &mut |id| {
|
||||||
components.push(id);
|
components.push(id);
|
||||||
});
|
});
|
||||||
if let Some(mut observer) = world.get_mut::<Observer>(entity) {
|
if let Some(mut observer) = world.get_mut::<Observer>(entity) {
|
||||||
observer.descriptor.events.push(event_id);
|
observer.descriptor.events.push(event_key);
|
||||||
observer.descriptor.components.extend(components);
|
observer.descriptor.components.extend(components);
|
||||||
|
|
||||||
let system: &mut dyn Any = observer.system.as_mut();
|
let system: &mut dyn Any = observer.system.as_mut();
|
||||||
|
|||||||
@ -43,10 +43,10 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
|
|||||||
.get_mut::<Observer>(observer_entity)
|
.get_mut::<Observer>(observer_entity)
|
||||||
.expect("Source observer entity must have Observer");
|
.expect("Source observer entity must have Observer");
|
||||||
observer_state.descriptor.entities.push(target);
|
observer_state.descriptor.entities.push(target);
|
||||||
let event_types = observer_state.descriptor.events.clone();
|
let event_keys = observer_state.descriptor.events.clone();
|
||||||
let components = observer_state.descriptor.components.clone();
|
let components = observer_state.descriptor.components.clone();
|
||||||
for event_type in event_types {
|
for event_key in event_keys {
|
||||||
let observers = world.observers.get_observers_mut(event_type);
|
let observers = world.observers.get_observers_mut(event_key);
|
||||||
if components.is_empty() {
|
if components.is_empty() {
|
||||||
if let Some(map) = observers.entity_observers.get(&source).cloned() {
|
if let Some(map) = observers.entity_observers.get(&source).cloned() {
|
||||||
observers.entity_observers.insert(target, map);
|
observers.entity_observers.insert(target, map);
|
||||||
|
|||||||
@ -197,10 +197,10 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn trigger_with_caller<E: Event>(&mut self, mut event: E, caller: MaybeLocation) {
|
pub(crate) fn trigger_with_caller<E: Event>(&mut self, mut event: E, caller: MaybeLocation) {
|
||||||
let event_id = E::register_component_id(self);
|
let event_key = E::register_event_key(self);
|
||||||
// SAFETY: We just registered `event_id` with the type of `event`
|
// SAFETY: We just registered `event_key` with the type of `event`
|
||||||
unsafe {
|
unsafe {
|
||||||
self.trigger_dynamic_ref_with_caller(event_id, &mut event, caller);
|
self.trigger_dynamic_ref_with_caller(event_key, &mut event, caller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,22 +210,22 @@ impl World {
|
|||||||
/// or use the event after it has been modified by observers.
|
/// or use the event after it has been modified by observers.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn trigger_ref<E: Event>(&mut self, event: &mut E) {
|
pub fn trigger_ref<E: Event>(&mut self, event: &mut E) {
|
||||||
let event_id = E::register_component_id(self);
|
let event_key = E::register_event_key(self);
|
||||||
// SAFETY: We just registered `event_id` with the type of `event`
|
// SAFETY: We just registered `event_key` with the type of `event`
|
||||||
unsafe { self.trigger_dynamic_ref_with_caller(event_id, event, MaybeLocation::caller()) };
|
unsafe { self.trigger_dynamic_ref_with_caller(event_key, event, MaybeLocation::caller()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn trigger_dynamic_ref_with_caller<E: Event>(
|
unsafe fn trigger_dynamic_ref_with_caller<E: Event>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_id: ComponentId,
|
event_key: EventKey,
|
||||||
event_data: &mut E,
|
event_data: &mut E,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
) {
|
) {
|
||||||
let mut world = DeferredWorld::from(self);
|
let mut world = DeferredWorld::from(self);
|
||||||
// SAFETY: `event_data` is accessible as the type represented by `event_id`
|
// SAFETY: `event_data` is accessible as the type represented by `event_key`
|
||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, ()>(
|
world.trigger_observers_with_data::<_, ()>(
|
||||||
event_id,
|
event_key,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
core::iter::empty::<ComponentId>(),
|
core::iter::empty::<ComponentId>(),
|
||||||
@ -252,10 +252,10 @@ impl World {
|
|||||||
targets: impl TriggerTargets,
|
targets: impl TriggerTargets,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
) {
|
) {
|
||||||
let event_id = E::register_component_id(self);
|
let event_key = E::register_event_key(self);
|
||||||
// SAFETY: We just registered `event_id` with the type of `event`
|
// SAFETY: We just registered `event_key` with the type of `event`
|
||||||
unsafe {
|
unsafe {
|
||||||
self.trigger_targets_dynamic_ref_with_caller(event_id, &mut event, targets, caller);
|
self.trigger_targets_dynamic_ref_with_caller(event_key, &mut event, targets, caller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,9 +270,9 @@ impl World {
|
|||||||
event: &mut E,
|
event: &mut E,
|
||||||
targets: impl TriggerTargets,
|
targets: impl TriggerTargets,
|
||||||
) {
|
) {
|
||||||
let event_id = E::register_component_id(self);
|
let event_key = E::register_event_key(self);
|
||||||
// SAFETY: We just registered `event_id` with the type of `event`
|
// SAFETY: We just registered `event_key` with the type of `event`
|
||||||
unsafe { self.trigger_targets_dynamic_ref(event_id, event, targets) };
|
unsafe { self.trigger_targets_dynamic_ref(event_key, event, targets) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
/// Triggers the given [`EntityEvent`] for the given `targets`, which will run any [`Observer`]s watching for it.
|
||||||
@ -283,17 +283,17 @@ impl World {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
/// Caller must ensure that `event_data` is accessible as the type represented by `event_key`.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub unsafe fn trigger_targets_dynamic<E: EntityEvent, Targets: TriggerTargets>(
|
pub unsafe fn trigger_targets_dynamic<E: EntityEvent, Targets: TriggerTargets>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_id: ComponentId,
|
event_key: EventKey,
|
||||||
mut event_data: E,
|
mut event_data: E,
|
||||||
targets: Targets,
|
targets: Targets,
|
||||||
) {
|
) {
|
||||||
// SAFETY: `event_data` is accessible as the type represented by `event_id`
|
// SAFETY: `event_data` is accessible as the type represented by `event_key`
|
||||||
unsafe {
|
unsafe {
|
||||||
self.trigger_targets_dynamic_ref(event_id, &mut event_data, targets);
|
self.trigger_targets_dynamic_ref(event_key, &mut event_data, targets);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,16 +305,16 @@ impl World {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// Caller must ensure that `event_data` is accessible as the type represented by `event_id`.
|
/// Caller must ensure that `event_data` is accessible as the type represented by `event_key`.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub unsafe fn trigger_targets_dynamic_ref<E: EntityEvent, Targets: TriggerTargets>(
|
pub unsafe fn trigger_targets_dynamic_ref<E: EntityEvent, Targets: TriggerTargets>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_id: ComponentId,
|
event_key: EventKey,
|
||||||
event_data: &mut E,
|
event_data: &mut E,
|
||||||
targets: Targets,
|
targets: Targets,
|
||||||
) {
|
) {
|
||||||
self.trigger_targets_dynamic_ref_with_caller(
|
self.trigger_targets_dynamic_ref_with_caller(
|
||||||
event_id,
|
event_key,
|
||||||
event_data,
|
event_data,
|
||||||
targets,
|
targets,
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
@ -326,7 +326,7 @@ impl World {
|
|||||||
/// See `trigger_targets_dynamic_ref`
|
/// See `trigger_targets_dynamic_ref`
|
||||||
unsafe fn trigger_targets_dynamic_ref_with_caller<E: EntityEvent, Targets: TriggerTargets>(
|
unsafe fn trigger_targets_dynamic_ref_with_caller<E: EntityEvent, Targets: TriggerTargets>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_id: ComponentId,
|
event_key: EventKey,
|
||||||
event_data: &mut E,
|
event_data: &mut E,
|
||||||
targets: Targets,
|
targets: Targets,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
@ -334,10 +334,10 @@ impl World {
|
|||||||
let mut world = DeferredWorld::from(self);
|
let mut world = DeferredWorld::from(self);
|
||||||
let mut entity_targets = targets.entities().peekable();
|
let mut entity_targets = targets.entities().peekable();
|
||||||
if entity_targets.peek().is_none() {
|
if entity_targets.peek().is_none() {
|
||||||
// SAFETY: `event_data` is accessible as the type represented by `event_id`
|
// SAFETY: `event_data` is accessible as the type represented by `event_key`
|
||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_key,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
targets.components(),
|
targets.components(),
|
||||||
@ -348,10 +348,10 @@ impl World {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
for target_entity in entity_targets {
|
for target_entity in entity_targets {
|
||||||
// SAFETY: `event_data` is accessible as the type represented by `event_id`
|
// SAFETY: `event_data` is accessible as the type represented by `event_key`
|
||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_key,
|
||||||
Some(target_entity),
|
Some(target_entity),
|
||||||
Some(target_entity),
|
Some(target_entity),
|
||||||
targets.components(),
|
targets.components(),
|
||||||
@ -379,8 +379,8 @@ impl World {
|
|||||||
};
|
};
|
||||||
let descriptor = &observer_state.descriptor;
|
let descriptor = &observer_state.descriptor;
|
||||||
|
|
||||||
for &event_type in &descriptor.events {
|
for &event_key in &descriptor.events {
|
||||||
let cache = observers.get_observers_mut(event_type);
|
let cache = observers.get_observers_mut(event_key);
|
||||||
|
|
||||||
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
||||||
cache
|
cache
|
||||||
@ -400,7 +400,7 @@ impl World {
|
|||||||
.component_observers
|
.component_observers
|
||||||
.entry(component)
|
.entry(component)
|
||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
if let Some(flag) = Observers::is_archetype_cached(event_type) {
|
if let Some(flag) = Observers::is_archetype_cached(event_key) {
|
||||||
archetypes.update_flags(component, flag, true);
|
archetypes.update_flags(component, flag, true);
|
||||||
}
|
}
|
||||||
CachedComponentObservers::default()
|
CachedComponentObservers::default()
|
||||||
@ -430,8 +430,8 @@ impl World {
|
|||||||
let archetypes = &mut self.archetypes;
|
let archetypes = &mut self.archetypes;
|
||||||
let observers = &mut self.observers;
|
let observers = &mut self.observers;
|
||||||
|
|
||||||
for &event_type in &descriptor.events {
|
for &event_key in &descriptor.events {
|
||||||
let cache = observers.get_observers_mut(event_type);
|
let cache = observers.get_observers_mut(event_key);
|
||||||
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
|
||||||
cache.global_observers.remove(&entity);
|
cache.global_observers.remove(&entity);
|
||||||
} else if descriptor.components.is_empty() {
|
} else if descriptor.components.is_empty() {
|
||||||
@ -470,7 +470,7 @@ impl World {
|
|||||||
&& observers.entity_component_observers.is_empty()
|
&& observers.entity_component_observers.is_empty()
|
||||||
{
|
{
|
||||||
cache.component_observers.remove(component);
|
cache.component_observers.remove(component);
|
||||||
if let Some(flag) = Observers::is_archetype_cached(event_type) {
|
if let Some(flag) = Observers::is_archetype_cached(event_key) {
|
||||||
if let Some(by_component) = archetypes.by_component.get(component) {
|
if let Some(by_component) = archetypes.by_component.get(component) {
|
||||||
for archetype in by_component.keys() {
|
for archetype in by_component.keys() {
|
||||||
let archetype = &mut archetypes.archetypes[archetype.index()];
|
let archetype = &mut archetypes.archetypes[archetype.index()];
|
||||||
@ -734,7 +734,7 @@ mod tests {
|
|||||||
fn observer_multiple_events() {
|
fn observer_multiple_events() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.init_resource::<Order>();
|
world.init_resource::<Order>();
|
||||||
let on_remove = Remove::register_component_id(&mut world);
|
let on_remove = Remove::register_event_key(&mut world);
|
||||||
world.spawn(
|
world.spawn(
|
||||||
// SAFETY: Add and Remove are both unit types, so this is safe
|
// SAFETY: Add and Remove are both unit types, so this is safe
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -1008,7 +1008,7 @@ mod tests {
|
|||||||
fn observer_dynamic_trigger() {
|
fn observer_dynamic_trigger() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.init_resource::<Order>();
|
world.init_resource::<Order>();
|
||||||
let event_a = Remove::register_component_id(&mut world);
|
let event_a = Remove::register_event_key(&mut world);
|
||||||
|
|
||||||
// SAFETY: we registered `event_a` above and it matches the type of EventA
|
// SAFETY: we registered `event_a` above and it matches the type of EventA
|
||||||
let observe = unsafe {
|
let observe = unsafe {
|
||||||
|
|||||||
@ -49,8 +49,8 @@ impl<'w, E, B: Bundle> On<'w, E, B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the event type of this [`On`] instance.
|
/// Returns the event type of this [`On`] instance.
|
||||||
pub fn event_type(&self) -> ComponentId {
|
pub fn event_key(&self) -> EventKey {
|
||||||
self.trigger.event_type
|
self.trigger.event_key
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the triggered event.
|
/// Returns a reference to the triggered event.
|
||||||
@ -182,7 +182,7 @@ pub struct ObserverTrigger {
|
|||||||
/// The [`Entity`] of the observer handling the trigger.
|
/// The [`Entity`] of the observer handling the trigger.
|
||||||
pub observer: Entity,
|
pub observer: Entity,
|
||||||
/// The [`Event`] the trigger targeted.
|
/// The [`Event`] the trigger targeted.
|
||||||
pub event_type: ComponentId,
|
pub event_key: EventKey,
|
||||||
/// The [`ComponentId`]s the trigger targeted.
|
/// The [`ComponentId`]s the trigger targeted.
|
||||||
pub components: SmallVec<[ComponentId; 2]>,
|
pub components: SmallVec<[ComponentId; 2]>,
|
||||||
/// The entity that the entity-event targeted, if any.
|
/// The entity that the entity-event targeted, if any.
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::{
|
|||||||
change_detection::{MaybeLocation, MutUntyped},
|
change_detection::{MaybeLocation, MutUntyped},
|
||||||
component::{ComponentId, Mutable},
|
component::{ComponentId, Mutable},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{BufferedEvent, EntityEvent, Event, EventId, Events, SendBatchIds},
|
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, SendBatchIds},
|
||||||
lifecycle::{HookContext, INSERT, REPLACE},
|
lifecycle::{HookContext, INSERT, REPLACE},
|
||||||
observer::{Observers, TriggerTargets},
|
observer::{Observers, TriggerTargets},
|
||||||
prelude::{Component, QueryState},
|
prelude::{Component, QueryState},
|
||||||
@ -749,7 +749,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn trigger_observers(
|
pub(crate) unsafe fn trigger_observers(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: ComponentId,
|
event: EventKey,
|
||||||
target: Option<Entity>,
|
target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
@ -773,7 +773,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
|
pub(crate) unsafe fn trigger_observers_with_data<E, T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
event: ComponentId,
|
event: EventKey,
|
||||||
current_target: Option<Entity>,
|
current_target: Option<Entity>,
|
||||||
original_target: Option<Entity>,
|
original_target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
|
|||||||
@ -152,19 +152,19 @@ impl World {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn bootstrap(&mut self) {
|
fn bootstrap(&mut self) {
|
||||||
// The order that we register these events is vital to ensure that the constants are correct!
|
// The order that we register these events is vital to ensure that the constants are correct!
|
||||||
let on_add = Add::register_component_id(self);
|
let on_add = Add::register_event_key(self);
|
||||||
assert_eq!(ADD, on_add);
|
assert_eq!(ADD, on_add);
|
||||||
|
|
||||||
let on_insert = Insert::register_component_id(self);
|
let on_insert = Insert::register_event_key(self);
|
||||||
assert_eq!(INSERT, on_insert);
|
assert_eq!(INSERT, on_insert);
|
||||||
|
|
||||||
let on_replace = Replace::register_component_id(self);
|
let on_replace = Replace::register_event_key(self);
|
||||||
assert_eq!(REPLACE, on_replace);
|
assert_eq!(REPLACE, on_replace);
|
||||||
|
|
||||||
let on_remove = Remove::register_component_id(self);
|
let on_remove = Remove::register_event_key(self);
|
||||||
assert_eq!(REMOVE, on_remove);
|
assert_eq!(REMOVE, on_remove);
|
||||||
|
|
||||||
let on_despawn = Despawn::register_component_id(self);
|
let on_despawn = Despawn::register_event_key(self);
|
||||||
assert_eq!(DESPAWN, on_despawn);
|
assert_eq!(DESPAWN, on_despawn);
|
||||||
|
|
||||||
// This sets up `Disabled` as a disabling component, via the FromWorld impl
|
// This sets up `Disabled` as a disabling component, via the FromWorld impl
|
||||||
@ -304,6 +304,7 @@ impl World {
|
|||||||
/// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type.
|
/// Returns a mutable reference to the [`ComponentHooks`] for a [`Component`] type.
|
||||||
///
|
///
|
||||||
/// Will panic if `T` exists in any archetypes.
|
/// Will panic if `T` exists in any archetypes.
|
||||||
|
#[must_use]
|
||||||
pub fn register_component_hooks<T: Component>(&mut self) -> &mut ComponentHooks {
|
pub fn register_component_hooks<T: Component>(&mut self) -> &mut ComponentHooks {
|
||||||
let index = self.register_component::<T>();
|
let index = self.register_component::<T>();
|
||||||
assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components hooks cannot be modified if the component already exists in an archetype, use register_component if {} may already be in use", core::any::type_name::<T>());
|
assert!(!self.archetypes.archetypes.iter().any(|a| a.contains(index)), "Components hooks cannot be modified if the component already exists in an archetype, use register_component if {} may already be in use", core::any::type_name::<T>());
|
||||||
|
|||||||
@ -34,6 +34,7 @@ serde = { version = "1", default-features = false, features = [
|
|||||||
hexasphere = "15.0"
|
hexasphere = "15.0"
|
||||||
thiserror = { version = "2", default-features = false }
|
thiserror = { version = "2", default-features = false }
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
derive_more = { version = "2", default-features = false, features = ["from"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::mesh::Mesh;
|
||||||
mesh::Mesh,
|
|
||||||
view::{self, Visibility, VisibilityClass},
|
|
||||||
};
|
|
||||||
use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle};
|
use bevy_asset::{AsAssetId, AssetEvent, AssetId, Handle};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
@ -42,8 +39,7 @@ use derive_more::derive::From;
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
||||||
#[reflect(Component, Default, Clone, PartialEq)]
|
#[reflect(Component, Default, Clone, PartialEq)]
|
||||||
#[require(Transform, Visibility, VisibilityClass)]
|
#[require(Transform)]
|
||||||
#[component(on_add = view::add_visibility_class::<Mesh2d>)]
|
|
||||||
pub struct Mesh2d(pub Handle<Mesh>);
|
pub struct Mesh2d(pub Handle<Mesh>);
|
||||||
|
|
||||||
impl From<Mesh2d> for AssetId<Mesh> {
|
impl From<Mesh2d> for AssetId<Mesh> {
|
||||||
@ -98,8 +94,7 @@ impl AsAssetId for Mesh2d {
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
||||||
#[reflect(Component, Default, Clone, PartialEq)]
|
#[reflect(Component, Default, Clone, PartialEq)]
|
||||||
#[require(Transform, Visibility, VisibilityClass)]
|
#[require(Transform)]
|
||||||
#[component(on_add = view::add_visibility_class::<Mesh3d>)]
|
|
||||||
pub struct Mesh3d(pub Handle<Mesh>);
|
pub struct Mesh3d(pub Handle<Mesh>);
|
||||||
|
|
||||||
impl From<Mesh3d> for AssetId<Mesh> {
|
impl From<Mesh3d> for AssetId<Mesh> {
|
||||||
@ -3,6 +3,7 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate core;
|
extern crate core;
|
||||||
|
|
||||||
|
mod components;
|
||||||
mod conversions;
|
mod conversions;
|
||||||
mod index;
|
mod index;
|
||||||
mod mesh;
|
mod mesh;
|
||||||
@ -12,6 +13,7 @@ pub mod primitives;
|
|||||||
pub mod skinning;
|
pub mod skinning;
|
||||||
mod vertex;
|
mod vertex;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
pub use components::*;
|
||||||
pub use index::*;
|
pub use index::*;
|
||||||
pub use mesh::*;
|
pub use mesh::*;
|
||||||
pub use mikktspace::*;
|
pub use mikktspace::*;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ use bevy_input::{
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::camera::RenderTarget;
|
use bevy_render::camera::{RenderTarget, ToNormalizedRenderTarget as _};
|
||||||
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
|
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use bevy_input::mouse::MouseScrollUnit;
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bevy_render::camera::{Camera, NormalizedRenderTarget};
|
use bevy_render::camera::{Camera, NormalizedRenderTarget, ToNormalizedRenderTarget as _};
|
||||||
use bevy_window::PrimaryWindow;
|
use bevy_window::PrimaryWindow;
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|||||||
@ -76,6 +76,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", features = [
|
|||||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||||
|
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
|
||||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"serialize",
|
"serialize",
|
||||||
|
|||||||
668
crates/bevy_render/src/camera.rs
Normal file
668
crates/bevy_render/src/camera.rs
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
use crate::{
|
||||||
|
batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport},
|
||||||
|
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||||
|
extract_resource::{ExtractResource, ExtractResourcePlugin},
|
||||||
|
render_asset::RenderAssets,
|
||||||
|
render_graph::{CameraDriverNode, InternedRenderSubGraph, RenderGraph, RenderSubGraph},
|
||||||
|
render_resource::TextureView,
|
||||||
|
sync_world::{RenderEntity, SyncToRenderWorld},
|
||||||
|
texture::{GpuImage, ManualTextureViews},
|
||||||
|
view::{
|
||||||
|
ColorGrading, ExtractedView, ExtractedWindows, Hdr, Msaa, NoIndirectDrawing,
|
||||||
|
RenderVisibleEntities, RetainedViewEntity, ViewUniformOffset,
|
||||||
|
},
|
||||||
|
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
||||||
|
use bevy_asset::{AssetEvent, AssetEventSystems, AssetId, Assets};
|
||||||
|
pub use bevy_camera::*;
|
||||||
|
use bevy_camera::{
|
||||||
|
primitives::Frustum,
|
||||||
|
visibility::{RenderLayers, VisibleEntities},
|
||||||
|
};
|
||||||
|
use bevy_derive::{Deref, DerefMut};
|
||||||
|
use bevy_ecs::{
|
||||||
|
change_detection::DetectChanges,
|
||||||
|
component::Component,
|
||||||
|
entity::{ContainsEntity, Entity},
|
||||||
|
event::EventReader,
|
||||||
|
lifecycle::HookContext,
|
||||||
|
prelude::With,
|
||||||
|
query::Has,
|
||||||
|
reflect::ReflectComponent,
|
||||||
|
resource::Resource,
|
||||||
|
schedule::IntoScheduleConfigs,
|
||||||
|
system::{Commands, Query, Res, ResMut},
|
||||||
|
world::DeferredWorld,
|
||||||
|
};
|
||||||
|
use bevy_image::Image;
|
||||||
|
use bevy_math::{vec2, Mat4, URect, UVec2, UVec4, Vec2};
|
||||||
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
|
use bevy_reflect::prelude::*;
|
||||||
|
use bevy_transform::components::GlobalTransform;
|
||||||
|
use bevy_window::{
|
||||||
|
NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowResized,
|
||||||
|
WindowScaleFactorChanged,
|
||||||
|
};
|
||||||
|
use derive_more::derive::From;
|
||||||
|
use tracing::warn;
|
||||||
|
use wgpu::TextureFormat;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct CameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<CameraRenderGraph>()
|
||||||
|
.register_type::<TemporalJitter>()
|
||||||
|
.register_type::<MipBias>()
|
||||||
|
.register_required_components::<Camera, Msaa>()
|
||||||
|
.register_required_components::<Camera, SyncToRenderWorld>()
|
||||||
|
.add_plugins((
|
||||||
|
ExtractResourcePlugin::<ClearColor>::default(),
|
||||||
|
ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
|
||||||
|
bevy_camera::CameraPlugin,
|
||||||
|
))
|
||||||
|
.add_systems(PostStartup, camera_system.in_set(CameraUpdateSystems))
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
camera_system
|
||||||
|
.in_set(CameraUpdateSystems)
|
||||||
|
.before(AssetEventSystems)
|
||||||
|
.before(visibility::update_frusta),
|
||||||
|
);
|
||||||
|
app.world_mut()
|
||||||
|
.register_component_hooks::<Camera>()
|
||||||
|
.on_add(warn_on_no_render_graph);
|
||||||
|
|
||||||
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
|
render_app
|
||||||
|
.init_resource::<SortedCameras>()
|
||||||
|
.add_systems(ExtractSchedule, extract_cameras)
|
||||||
|
.add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews));
|
||||||
|
let camera_driver_node = CameraDriverNode::new(render_app.world_mut());
|
||||||
|
let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
|
||||||
|
render_graph.add_node(crate::graph::CameraDriverLabel, camera_driver_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warn_on_no_render_graph(world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
|
||||||
|
if !world.entity(entity).contains::<CameraRenderGraph>() {
|
||||||
|
warn!("{}Entity {entity} has a `Camera` component, but it doesn't have a render graph configured. Consider adding a `Camera2d` or `Camera3d` component, or manually adding a `CameraRenderGraph` component if you need a custom render graph.", caller.map(|location|format!("{location}: ")).unwrap_or_default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExtractResource for ClearColor {
|
||||||
|
type Source = ClearColor;
|
||||||
|
|
||||||
|
fn extract_resource(source: &Self::Source) -> Self {
|
||||||
|
source.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ExtractComponent for CameraMainTextureUsages {
|
||||||
|
type QueryData = &'static Self;
|
||||||
|
type QueryFilter = ();
|
||||||
|
type Out = Self;
|
||||||
|
|
||||||
|
fn extract_component(
|
||||||
|
item: bevy_ecs::query::QueryItem<'_, '_, Self::QueryData>,
|
||||||
|
) -> Option<Self::Out> {
|
||||||
|
Some(*item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configures the [`RenderGraph`] name assigned to be run for a given [`Camera`] entity.
|
||||||
|
#[derive(Component, Debug, Deref, DerefMut, Reflect, Clone)]
|
||||||
|
#[reflect(opaque)]
|
||||||
|
#[reflect(Component, Debug, Clone)]
|
||||||
|
pub struct CameraRenderGraph(InternedRenderSubGraph);
|
||||||
|
|
||||||
|
impl CameraRenderGraph {
|
||||||
|
/// Creates a new [`CameraRenderGraph`] from any string-like type.
|
||||||
|
#[inline]
|
||||||
|
pub fn new<T: RenderSubGraph>(name: T) -> Self {
|
||||||
|
Self(name.intern())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the graph name.
|
||||||
|
#[inline]
|
||||||
|
pub fn set<T: RenderSubGraph>(&mut self, name: T) {
|
||||||
|
self.0 = name.intern();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ToNormalizedRenderTarget {
|
||||||
|
/// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
|
||||||
|
fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToNormalizedRenderTarget for RenderTarget {
|
||||||
|
fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
|
||||||
|
match self {
|
||||||
|
RenderTarget::Window(window_ref) => window_ref
|
||||||
|
.normalize(primary_window)
|
||||||
|
.map(NormalizedRenderTarget::Window),
|
||||||
|
RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
|
||||||
|
RenderTarget::TextureView(id) => Some(NormalizedRenderTarget::TextureView(*id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalized version of the render target.
|
||||||
|
///
|
||||||
|
/// Once we have this we shouldn't need to resolve it down anymore.
|
||||||
|
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord, From)]
|
||||||
|
#[reflect(Clone, PartialEq, Hash)]
|
||||||
|
pub enum NormalizedRenderTarget {
|
||||||
|
/// Window to which the camera's view is rendered.
|
||||||
|
Window(NormalizedWindowRef),
|
||||||
|
/// Image to which the camera's view is rendered.
|
||||||
|
Image(ImageRenderTarget),
|
||||||
|
/// Texture View to which the camera's view is rendered.
|
||||||
|
/// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
|
||||||
|
TextureView(ManualTextureViewHandle),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NormalizedRenderTarget {
|
||||||
|
pub fn get_texture_view<'a>(
|
||||||
|
&self,
|
||||||
|
windows: &'a ExtractedWindows,
|
||||||
|
images: &'a RenderAssets<GpuImage>,
|
||||||
|
manual_texture_views: &'a ManualTextureViews,
|
||||||
|
) -> Option<&'a TextureView> {
|
||||||
|
match self {
|
||||||
|
NormalizedRenderTarget::Window(window_ref) => windows
|
||||||
|
.get(&window_ref.entity())
|
||||||
|
.and_then(|window| window.swap_chain_texture_view.as_ref()),
|
||||||
|
NormalizedRenderTarget::Image(image_target) => images
|
||||||
|
.get(&image_target.handle)
|
||||||
|
.map(|image| &image.texture_view),
|
||||||
|
NormalizedRenderTarget::TextureView(id) => {
|
||||||
|
manual_texture_views.get(id).map(|tex| &tex.texture_view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [`TextureFormat`] of this render target, if it exists.
|
||||||
|
pub fn get_texture_format<'a>(
|
||||||
|
&self,
|
||||||
|
windows: &'a ExtractedWindows,
|
||||||
|
images: &'a RenderAssets<GpuImage>,
|
||||||
|
manual_texture_views: &'a ManualTextureViews,
|
||||||
|
) -> Option<TextureFormat> {
|
||||||
|
match self {
|
||||||
|
NormalizedRenderTarget::Window(window_ref) => windows
|
||||||
|
.get(&window_ref.entity())
|
||||||
|
.and_then(|window| window.swap_chain_texture_format),
|
||||||
|
NormalizedRenderTarget::Image(image_target) => images
|
||||||
|
.get(&image_target.handle)
|
||||||
|
.map(|image| image.texture_format),
|
||||||
|
NormalizedRenderTarget::TextureView(id) => {
|
||||||
|
manual_texture_views.get(id).map(|tex| tex.format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_render_target_info<'a>(
|
||||||
|
&self,
|
||||||
|
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
|
||||||
|
images: &Assets<Image>,
|
||||||
|
manual_texture_views: &ManualTextureViews,
|
||||||
|
) -> Option<RenderTargetInfo> {
|
||||||
|
match self {
|
||||||
|
NormalizedRenderTarget::Window(window_ref) => resolutions
|
||||||
|
.into_iter()
|
||||||
|
.find(|(entity, _)| *entity == window_ref.entity())
|
||||||
|
.map(|(_, window)| RenderTargetInfo {
|
||||||
|
physical_size: window.physical_size(),
|
||||||
|
scale_factor: window.resolution.scale_factor(),
|
||||||
|
}),
|
||||||
|
NormalizedRenderTarget::Image(image_target) => {
|
||||||
|
let image = images.get(&image_target.handle)?;
|
||||||
|
Some(RenderTargetInfo {
|
||||||
|
physical_size: image.size(),
|
||||||
|
scale_factor: image_target.scale_factor.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
NormalizedRenderTarget::TextureView(id) => {
|
||||||
|
manual_texture_views.get(id).map(|tex| RenderTargetInfo {
|
||||||
|
physical_size: tex.size,
|
||||||
|
scale_factor: 1.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this render target is contained in the given changed windows or images.
|
||||||
|
fn is_changed(
|
||||||
|
&self,
|
||||||
|
changed_window_ids: &HashSet<Entity>,
|
||||||
|
changed_image_handles: &HashSet<&AssetId<Image>>,
|
||||||
|
) -> bool {
|
||||||
|
match self {
|
||||||
|
NormalizedRenderTarget::Window(window_ref) => {
|
||||||
|
changed_window_ids.contains(&window_ref.entity())
|
||||||
|
}
|
||||||
|
NormalizedRenderTarget::Image(image_target) => {
|
||||||
|
changed_image_handles.contains(&image_target.handle.id())
|
||||||
|
}
|
||||||
|
NormalizedRenderTarget::TextureView(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System in charge of updating a [`Camera`] when its window or projection changes.
|
||||||
|
///
|
||||||
|
/// The system detects window creation, resize, and scale factor change events to update the camera
|
||||||
|
/// [`Projection`] if needed.
|
||||||
|
///
|
||||||
|
/// ## World Resources
|
||||||
|
///
|
||||||
|
/// [`Res<Assets<Image>>`](Assets<Image>) -- For cameras that render to an image, this resource is used to
|
||||||
|
/// inspect information about the render target. This system will not access any other image assets.
|
||||||
|
///
|
||||||
|
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
||||||
|
/// [`PerspectiveProjection`]: crate::camera::PerspectiveProjection
|
||||||
|
pub fn camera_system(
|
||||||
|
mut window_resized_events: EventReader<WindowResized>,
|
||||||
|
mut window_created_events: EventReader<WindowCreated>,
|
||||||
|
mut window_scale_factor_changed_events: EventReader<WindowScaleFactorChanged>,
|
||||||
|
mut image_asset_events: EventReader<AssetEvent<Image>>,
|
||||||
|
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||||
|
windows: Query<(Entity, &Window)>,
|
||||||
|
images: Res<Assets<Image>>,
|
||||||
|
manual_texture_views: Res<ManualTextureViews>,
|
||||||
|
mut cameras: Query<(&mut Camera, &mut Projection)>,
|
||||||
|
) {
|
||||||
|
let primary_window = primary_window.iter().next();
|
||||||
|
|
||||||
|
let mut changed_window_ids = <HashSet<_>>::default();
|
||||||
|
changed_window_ids.extend(window_created_events.read().map(|event| event.window));
|
||||||
|
changed_window_ids.extend(window_resized_events.read().map(|event| event.window));
|
||||||
|
let scale_factor_changed_window_ids: HashSet<_> = window_scale_factor_changed_events
|
||||||
|
.read()
|
||||||
|
.map(|event| event.window)
|
||||||
|
.collect();
|
||||||
|
changed_window_ids.extend(scale_factor_changed_window_ids.clone());
|
||||||
|
|
||||||
|
let changed_image_handles: HashSet<&AssetId<Image>> = image_asset_events
|
||||||
|
.read()
|
||||||
|
.filter_map(|event| match event {
|
||||||
|
AssetEvent::Modified { id } | AssetEvent::Added { id } => Some(id),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for (mut camera, mut camera_projection) in &mut cameras {
|
||||||
|
let mut viewport_size = camera
|
||||||
|
.viewport
|
||||||
|
.as_ref()
|
||||||
|
.map(|viewport| viewport.physical_size);
|
||||||
|
|
||||||
|
if let Some(normalized_target) = &camera.target.normalize(primary_window) {
|
||||||
|
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
|
||||||
|
|| camera.is_added()
|
||||||
|
|| camera_projection.is_changed()
|
||||||
|
|| camera.computed.old_viewport_size != viewport_size
|
||||||
|
|| camera.computed.old_sub_camera_view != camera.sub_camera_view
|
||||||
|
{
|
||||||
|
let new_computed_target_info = normalized_target.get_render_target_info(
|
||||||
|
windows,
|
||||||
|
&images,
|
||||||
|
&manual_texture_views,
|
||||||
|
);
|
||||||
|
// Check for the scale factor changing, and resize the viewport if needed.
|
||||||
|
// This can happen when the window is moved between monitors with different DPIs.
|
||||||
|
// Without this, the viewport will take a smaller portion of the window moved to
|
||||||
|
// a higher DPI monitor.
|
||||||
|
if normalized_target
|
||||||
|
.is_changed(&scale_factor_changed_window_ids, &HashSet::default())
|
||||||
|
{
|
||||||
|
if let (Some(new_scale_factor), Some(old_scale_factor)) = (
|
||||||
|
new_computed_target_info
|
||||||
|
.as_ref()
|
||||||
|
.map(|info| info.scale_factor),
|
||||||
|
camera
|
||||||
|
.computed
|
||||||
|
.target_info
|
||||||
|
.as_ref()
|
||||||
|
.map(|info| info.scale_factor),
|
||||||
|
) {
|
||||||
|
let resize_factor = new_scale_factor / old_scale_factor;
|
||||||
|
if let Some(ref mut viewport) = camera.viewport {
|
||||||
|
let resize = |vec: UVec2| (vec.as_vec2() * resize_factor).as_uvec2();
|
||||||
|
viewport.physical_position = resize(viewport.physical_position);
|
||||||
|
viewport.physical_size = resize(viewport.physical_size);
|
||||||
|
viewport_size = Some(viewport.physical_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This check is needed because when changing WindowMode to Fullscreen, the viewport may have invalid
|
||||||
|
// arguments due to a sudden change on the window size to a lower value.
|
||||||
|
// If the size of the window is lower, the viewport will match that lower value.
|
||||||
|
if let Some(viewport) = &mut camera.viewport {
|
||||||
|
let target_info = &new_computed_target_info;
|
||||||
|
if let Some(target) = target_info {
|
||||||
|
viewport.clamp_to_size(target.physical_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
camera.computed.target_info = new_computed_target_info;
|
||||||
|
if let Some(size) = camera.logical_viewport_size() {
|
||||||
|
if size.x != 0.0 && size.y != 0.0 {
|
||||||
|
camera_projection.update(size.x, size.y);
|
||||||
|
camera.computed.clip_from_view = match &camera.sub_camera_view {
|
||||||
|
Some(sub_view) => {
|
||||||
|
camera_projection.get_clip_from_view_for_sub(sub_view)
|
||||||
|
}
|
||||||
|
None => camera_projection.get_clip_from_view(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if camera.computed.old_viewport_size != viewport_size {
|
||||||
|
camera.computed.old_viewport_size = viewport_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if camera.computed.old_sub_camera_view != camera.sub_camera_view {
|
||||||
|
camera.computed.old_sub_camera_view = camera.sub_camera_view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct ExtractedCamera {
|
||||||
|
pub target: Option<NormalizedRenderTarget>,
|
||||||
|
pub physical_viewport_size: Option<UVec2>,
|
||||||
|
pub physical_target_size: Option<UVec2>,
|
||||||
|
pub viewport: Option<Viewport>,
|
||||||
|
pub render_graph: InternedRenderSubGraph,
|
||||||
|
pub order: isize,
|
||||||
|
pub output_mode: CameraOutputMode,
|
||||||
|
pub msaa_writeback: bool,
|
||||||
|
pub clear_color: ClearColorConfig,
|
||||||
|
pub sorted_camera_index_for_target: usize,
|
||||||
|
pub exposure: f32,
|
||||||
|
pub hdr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_cameras(
|
||||||
|
mut commands: Commands,
|
||||||
|
query: Extract<
|
||||||
|
Query<(
|
||||||
|
Entity,
|
||||||
|
RenderEntity,
|
||||||
|
&Camera,
|
||||||
|
&CameraRenderGraph,
|
||||||
|
&GlobalTransform,
|
||||||
|
&VisibleEntities,
|
||||||
|
&Frustum,
|
||||||
|
Has<Hdr>,
|
||||||
|
Option<&ColorGrading>,
|
||||||
|
Option<&Exposure>,
|
||||||
|
Option<&TemporalJitter>,
|
||||||
|
Option<&MipBias>,
|
||||||
|
Option<&RenderLayers>,
|
||||||
|
Option<&Projection>,
|
||||||
|
Has<NoIndirectDrawing>,
|
||||||
|
)>,
|
||||||
|
>,
|
||||||
|
primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
|
||||||
|
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||||
|
mapper: Extract<Query<&RenderEntity>>,
|
||||||
|
) {
|
||||||
|
let primary_window = primary_window.iter().next();
|
||||||
|
for (
|
||||||
|
main_entity,
|
||||||
|
render_entity,
|
||||||
|
camera,
|
||||||
|
camera_render_graph,
|
||||||
|
transform,
|
||||||
|
visible_entities,
|
||||||
|
frustum,
|
||||||
|
hdr,
|
||||||
|
color_grading,
|
||||||
|
exposure,
|
||||||
|
temporal_jitter,
|
||||||
|
mip_bias,
|
||||||
|
render_layers,
|
||||||
|
projection,
|
||||||
|
no_indirect_drawing,
|
||||||
|
) in query.iter()
|
||||||
|
{
|
||||||
|
if !camera.is_active {
|
||||||
|
commands.entity(render_entity).remove::<(
|
||||||
|
ExtractedCamera,
|
||||||
|
ExtractedView,
|
||||||
|
RenderVisibleEntities,
|
||||||
|
TemporalJitter,
|
||||||
|
MipBias,
|
||||||
|
RenderLayers,
|
||||||
|
Projection,
|
||||||
|
NoIndirectDrawing,
|
||||||
|
ViewUniformOffset,
|
||||||
|
)>();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let color_grading = color_grading.unwrap_or(&ColorGrading::default()).clone();
|
||||||
|
|
||||||
|
if let (
|
||||||
|
Some(URect {
|
||||||
|
min: viewport_origin,
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
Some(viewport_size),
|
||||||
|
Some(target_size),
|
||||||
|
) = (
|
||||||
|
camera.physical_viewport_rect(),
|
||||||
|
camera.physical_viewport_size(),
|
||||||
|
camera.physical_target_size(),
|
||||||
|
) {
|
||||||
|
if target_size.x == 0 || target_size.y == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let render_visible_entities = RenderVisibleEntities {
|
||||||
|
entities: visible_entities
|
||||||
|
.entities
|
||||||
|
.iter()
|
||||||
|
.map(|(type_id, entities)| {
|
||||||
|
let entities = entities
|
||||||
|
.iter()
|
||||||
|
.map(|entity| {
|
||||||
|
let render_entity = mapper
|
||||||
|
.get(*entity)
|
||||||
|
.cloned()
|
||||||
|
.map(|entity| entity.id())
|
||||||
|
.unwrap_or(Entity::PLACEHOLDER);
|
||||||
|
(render_entity, (*entity).into())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(*type_id, entities)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut commands = commands.entity(render_entity);
|
||||||
|
commands.insert((
|
||||||
|
ExtractedCamera {
|
||||||
|
target: camera.target.normalize(primary_window),
|
||||||
|
viewport: camera.viewport.clone(),
|
||||||
|
physical_viewport_size: Some(viewport_size),
|
||||||
|
physical_target_size: Some(target_size),
|
||||||
|
render_graph: camera_render_graph.0,
|
||||||
|
order: camera.order,
|
||||||
|
output_mode: camera.output_mode,
|
||||||
|
msaa_writeback: camera.msaa_writeback,
|
||||||
|
clear_color: camera.clear_color,
|
||||||
|
// this will be set in sort_cameras
|
||||||
|
sorted_camera_index_for_target: 0,
|
||||||
|
exposure: exposure
|
||||||
|
.map(Exposure::exposure)
|
||||||
|
.unwrap_or_else(|| Exposure::default().exposure()),
|
||||||
|
hdr,
|
||||||
|
},
|
||||||
|
ExtractedView {
|
||||||
|
retained_view_entity: RetainedViewEntity::new(main_entity.into(), None, 0),
|
||||||
|
clip_from_view: camera.clip_from_view(),
|
||||||
|
world_from_view: *transform,
|
||||||
|
clip_from_world: None,
|
||||||
|
hdr,
|
||||||
|
viewport: UVec4::new(
|
||||||
|
viewport_origin.x,
|
||||||
|
viewport_origin.y,
|
||||||
|
viewport_size.x,
|
||||||
|
viewport_size.y,
|
||||||
|
),
|
||||||
|
color_grading,
|
||||||
|
},
|
||||||
|
render_visible_entities,
|
||||||
|
*frustum,
|
||||||
|
));
|
||||||
|
|
||||||
|
if let Some(temporal_jitter) = temporal_jitter {
|
||||||
|
commands.insert(temporal_jitter.clone());
|
||||||
|
} else {
|
||||||
|
commands.remove::<TemporalJitter>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mip_bias) = mip_bias {
|
||||||
|
commands.insert(mip_bias.clone());
|
||||||
|
} else {
|
||||||
|
commands.remove::<MipBias>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(render_layers) = render_layers {
|
||||||
|
commands.insert(render_layers.clone());
|
||||||
|
} else {
|
||||||
|
commands.remove::<RenderLayers>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(perspective) = projection {
|
||||||
|
commands.insert(perspective.clone());
|
||||||
|
} else {
|
||||||
|
commands.remove::<Projection>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if no_indirect_drawing
|
||||||
|
|| !matches!(
|
||||||
|
gpu_preprocessing_support.max_supported_mode,
|
||||||
|
GpuPreprocessingMode::Culling
|
||||||
|
)
|
||||||
|
{
|
||||||
|
commands.insert(NoIndirectDrawing);
|
||||||
|
} else {
|
||||||
|
commands.remove::<NoIndirectDrawing>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cameras sorted by their order field. This is updated in the [`sort_cameras`] system.
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct SortedCameras(pub Vec<SortedCamera>);
|
||||||
|
|
||||||
|
pub struct SortedCamera {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub order: isize,
|
||||||
|
pub target: Option<NormalizedRenderTarget>,
|
||||||
|
pub hdr: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort_cameras(
|
||||||
|
mut sorted_cameras: ResMut<SortedCameras>,
|
||||||
|
mut cameras: Query<(Entity, &mut ExtractedCamera)>,
|
||||||
|
) {
|
||||||
|
sorted_cameras.0.clear();
|
||||||
|
for (entity, camera) in cameras.iter() {
|
||||||
|
sorted_cameras.0.push(SortedCamera {
|
||||||
|
entity,
|
||||||
|
order: camera.order,
|
||||||
|
target: camera.target.clone(),
|
||||||
|
hdr: camera.hdr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// sort by order and ensure within an order, RenderTargets of the same type are packed together
|
||||||
|
sorted_cameras
|
||||||
|
.0
|
||||||
|
.sort_by(|c1, c2| (c1.order, &c1.target).cmp(&(c2.order, &c2.target)));
|
||||||
|
let mut previous_order_target = None;
|
||||||
|
let mut ambiguities = <HashSet<_>>::default();
|
||||||
|
let mut target_counts = <HashMap<_, _>>::default();
|
||||||
|
for sorted_camera in &mut sorted_cameras.0 {
|
||||||
|
let new_order_target = (sorted_camera.order, sorted_camera.target.clone());
|
||||||
|
if let Some(previous_order_target) = previous_order_target {
|
||||||
|
if previous_order_target == new_order_target {
|
||||||
|
ambiguities.insert(new_order_target.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(target) = &sorted_camera.target {
|
||||||
|
let count = target_counts
|
||||||
|
.entry((target.clone(), sorted_camera.hdr))
|
||||||
|
.or_insert(0usize);
|
||||||
|
let (_, mut camera) = cameras.get_mut(sorted_camera.entity).unwrap();
|
||||||
|
camera.sorted_camera_index_for_target = *count;
|
||||||
|
*count += 1;
|
||||||
|
}
|
||||||
|
previous_order_target = Some(new_order_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ambiguities.is_empty() {
|
||||||
|
warn!(
|
||||||
|
"Camera order ambiguities detected for active cameras with the following priorities: {:?}. \
|
||||||
|
To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. \
|
||||||
|
Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will \
|
||||||
|
result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, \
|
||||||
|
ambiguities could result in unpredictable render results.",
|
||||||
|
ambiguities
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A subpixel offset to jitter a perspective camera's frustum by.
|
||||||
|
///
|
||||||
|
/// Useful for temporal rendering techniques.
|
||||||
|
///
|
||||||
|
/// Do not use with [`OrthographicProjection`].
|
||||||
|
///
|
||||||
|
/// [`OrthographicProjection`]: crate::camera::OrthographicProjection
|
||||||
|
#[derive(Component, Clone, Default, Reflect)]
|
||||||
|
#[reflect(Default, Component, Clone)]
|
||||||
|
pub struct TemporalJitter {
|
||||||
|
/// Offset is in range [-0.5, 0.5].
|
||||||
|
pub offset: Vec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TemporalJitter {
|
||||||
|
pub fn jitter_projection(&self, clip_from_view: &mut Mat4, view_size: Vec2) {
|
||||||
|
if clip_from_view.w_axis.w == 1.0 {
|
||||||
|
warn!(
|
||||||
|
"TemporalJitter not supported with OrthographicProjection. Use PerspectiveProjection instead."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/docs/techniques/media/super-resolution-temporal/jitter-space.svg
|
||||||
|
let jitter = (self.offset * vec2(2.0, -2.0)) / view_size;
|
||||||
|
|
||||||
|
clip_from_view.z_axis.x += jitter.x;
|
||||||
|
clip_from_view.z_axis.y += jitter.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Camera component specifying a mip bias to apply when sampling from material textures.
|
||||||
|
///
|
||||||
|
/// Often used in conjunction with antialiasing post-process effects to reduce textures blurriness.
|
||||||
|
#[derive(Component, Reflect, Clone)]
|
||||||
|
#[reflect(Default, Component)]
|
||||||
|
pub struct MipBias(pub f32);
|
||||||
|
|
||||||
|
impl Default for MipBias {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(-1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,52 +0,0 @@
|
|||||||
mod camera;
|
|
||||||
mod camera_driver_node;
|
|
||||||
mod clear_color;
|
|
||||||
mod manual_texture_view;
|
|
||||||
mod projection;
|
|
||||||
|
|
||||||
pub use camera::*;
|
|
||||||
pub use camera_driver_node::*;
|
|
||||||
pub use clear_color::*;
|
|
||||||
pub use manual_texture_view::*;
|
|
||||||
pub use projection::*;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
extract_component::ExtractComponentPlugin, extract_resource::ExtractResourcePlugin,
|
|
||||||
render_graph::RenderGraph, ExtractSchedule, Render, RenderApp, RenderSystems,
|
|
||||||
};
|
|
||||||
use bevy_app::{App, Plugin};
|
|
||||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CameraPlugin;
|
|
||||||
|
|
||||||
impl Plugin for CameraPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.register_type::<Camera>()
|
|
||||||
.register_type::<ClearColor>()
|
|
||||||
.register_type::<CameraRenderGraph>()
|
|
||||||
.register_type::<CameraMainTextureUsages>()
|
|
||||||
.register_type::<Exposure>()
|
|
||||||
.register_type::<TemporalJitter>()
|
|
||||||
.register_type::<MipBias>()
|
|
||||||
.register_type::<MainPassResolutionOverride>()
|
|
||||||
.init_resource::<ManualTextureViews>()
|
|
||||||
.init_resource::<ClearColor>()
|
|
||||||
.add_plugins((
|
|
||||||
CameraProjectionPlugin,
|
|
||||||
ExtractResourcePlugin::<ManualTextureViews>::default(),
|
|
||||||
ExtractResourcePlugin::<ClearColor>::default(),
|
|
||||||
ExtractComponentPlugin::<CameraMainTextureUsages>::default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
||||||
render_app
|
|
||||||
.init_resource::<SortedCameras>()
|
|
||||||
.add_systems(ExtractSchedule, extract_cameras)
|
|
||||||
.add_systems(Render, sort_cameras.in_set(RenderSystems::ManageViews));
|
|
||||||
let camera_driver_node = CameraDriverNode::new(render_app.world_mut());
|
|
||||||
let mut render_graph = render_app.world_mut().resource_mut::<RenderGraph>();
|
|
||||||
render_graph.add_node(crate::graph::CameraDriverLabel, camera_driver_node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,10 +3,10 @@ use crate::{
|
|||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
sync_component::SyncComponentPlugin,
|
sync_component::SyncComponentPlugin,
|
||||||
sync_world::RenderEntity,
|
sync_world::RenderEntity,
|
||||||
view::ViewVisibility,
|
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_camera::visibility::ViewVisibility;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
bundle::NoBundleEffect,
|
bundle::NoBundleEffect,
|
||||||
component::Component,
|
component::Component,
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_camera::visibility::ViewVisibility;
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
prelude::Entity,
|
prelude::Entity,
|
||||||
@ -16,7 +17,7 @@ use bevy_ecs::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::sync_world::MainEntityHashMap;
|
use crate::sync_world::MainEntityHashMap;
|
||||||
use crate::{prelude::ViewVisibility, Extract, ExtractSchedule, RenderApp};
|
use crate::{Extract, ExtractSchedule, RenderApp};
|
||||||
|
|
||||||
/// Describes how to extract data needed for rendering from a component or
|
/// Describes how to extract data needed for rendering from a component or
|
||||||
/// components.
|
/// components.
|
||||||
|
|||||||
@ -38,7 +38,6 @@ pub mod gpu_readback;
|
|||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod pipelined_rendering;
|
pub mod pipelined_rendering;
|
||||||
pub mod primitives;
|
|
||||||
pub mod render_asset;
|
pub mod render_asset;
|
||||||
pub mod render_graph;
|
pub mod render_graph;
|
||||||
pub mod render_phase;
|
pub mod render_phase;
|
||||||
@ -50,6 +49,7 @@ pub mod sync_component;
|
|||||||
pub mod sync_world;
|
pub mod sync_world;
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod view;
|
pub mod view;
|
||||||
|
pub use bevy_camera::primitives;
|
||||||
|
|
||||||
/// The render prelude.
|
/// The render prelude.
|
||||||
///
|
///
|
||||||
@ -58,19 +58,22 @@ pub mod prelude {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
alpha::AlphaMode,
|
alpha::AlphaMode,
|
||||||
camera::{
|
camera::ToNormalizedRenderTarget as _,
|
||||||
Camera, ClearColor, ClearColorConfig, OrthographicProjection, PerspectiveProjection,
|
|
||||||
Projection,
|
|
||||||
},
|
|
||||||
mesh::{
|
mesh::{
|
||||||
morph::MorphWeights, primitives::MeshBuilder, primitives::Meshable, Mesh, Mesh2d,
|
morph::MorphWeights, primitives::MeshBuilder, primitives::Meshable, Mesh, Mesh2d,
|
||||||
Mesh3d,
|
Mesh3d,
|
||||||
},
|
},
|
||||||
render_resource::Shader,
|
render_resource::Shader,
|
||||||
texture::ImagePlugin,
|
texture::{ImagePlugin, ManualTextureViews},
|
||||||
view::{InheritedVisibility, Msaa, ViewVisibility, Visibility},
|
view::{InheritedVisibility, Msaa, ViewVisibility, Visibility},
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
};
|
};
|
||||||
|
// TODO: Remove this in a follow-up
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use bevy_camera::{
|
||||||
|
Camera, ClearColor, ClearColorConfig, OrthographicProjection, PerspectiveProjection,
|
||||||
|
Projection,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
use batching::gpu_preprocessing::BatchingPlugin;
|
use batching::gpu_preprocessing::BatchingPlugin;
|
||||||
|
|
||||||
@ -481,10 +484,6 @@ impl Plugin for RenderPlugin {
|
|||||||
app.register_type::<alpha::AlphaMode>()
|
app.register_type::<alpha::AlphaMode>()
|
||||||
// These types cannot be registered in bevy_color, as it does not depend on the rest of Bevy
|
// These types cannot be registered in bevy_color, as it does not depend on the rest of Bevy
|
||||||
.register_type::<bevy_color::Color>()
|
.register_type::<bevy_color::Color>()
|
||||||
.register_type::<primitives::Aabb>()
|
|
||||||
.register_type::<primitives::CascadesFrusta>()
|
|
||||||
.register_type::<primitives::CubemapFrusta>()
|
|
||||||
.register_type::<primitives::Frustum>()
|
|
||||||
.register_type::<RenderEntity>()
|
.register_type::<RenderEntity>()
|
||||||
.register_type::<TemporaryRenderEntity>()
|
.register_type::<TemporaryRenderEntity>()
|
||||||
.register_type::<MainEntity>()
|
.register_type::<MainEntity>()
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
use bevy_math::Vec3;
|
use bevy_camera::visibility::VisibilitySystems;
|
||||||
pub use bevy_mesh::*;
|
pub use bevy_mesh::*;
|
||||||
use morph::{MeshMorphWeights, MorphWeights};
|
use morph::{MeshMorphWeights, MorphWeights};
|
||||||
pub mod allocator;
|
pub mod allocator;
|
||||||
mod components;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::Aabb,
|
|
||||||
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||||
render_resource::TextureView,
|
render_resource::TextureView,
|
||||||
texture::GpuImage,
|
texture::GpuImage,
|
||||||
view::VisibilitySystems,
|
|
||||||
RenderApp,
|
RenderApp,
|
||||||
};
|
};
|
||||||
use allocator::MeshAllocatorPlugin;
|
use allocator::MeshAllocatorPlugin;
|
||||||
@ -21,7 +18,7 @@ use bevy_ecs::{
|
|||||||
SystemParamItem,
|
SystemParamItem,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
pub use components::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d, MeshTag};
|
pub use bevy_mesh::{mark_3d_meshes_as_changed_if_their_assets_changed, Mesh2d, Mesh3d, MeshTag};
|
||||||
use wgpu::IndexFormat;
|
use wgpu::IndexFormat;
|
||||||
|
|
||||||
/// Registers all [`MeshBuilder`] types.
|
/// Registers all [`MeshBuilder`] types.
|
||||||
@ -112,26 +109,6 @@ pub fn inherit_weights(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait MeshAabb {
|
|
||||||
/// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space
|
|
||||||
///
|
|
||||||
/// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of
|
|
||||||
/// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices.
|
|
||||||
fn compute_aabb(&self) -> Option<Aabb>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MeshAabb for Mesh {
|
|
||||||
fn compute_aabb(&self) -> Option<Aabb> {
|
|
||||||
let Some(VertexAttributeValues::Float32x3(values)) =
|
|
||||||
self.attribute(Mesh::ATTRIBUTE_POSITION)
|
|
||||||
else {
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The render world representation of a [`Mesh`].
|
/// The render world representation of a [`Mesh`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RenderMesh {
|
pub struct RenderMesh {
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
camera::{ClearColor, ExtractedCamera, NormalizedRenderTarget, SortedCameras},
|
camera::{ExtractedCamera, NormalizedRenderTarget, SortedCameras},
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||||
renderer::RenderContext,
|
renderer::RenderContext,
|
||||||
view::ExtractedWindows,
|
view::ExtractedWindows,
|
||||||
};
|
};
|
||||||
|
use bevy_camera::ClearColor;
|
||||||
use bevy_ecs::{entity::ContainsEntity, prelude::QueryState, world::World};
|
use bevy_ecs::{entity::ContainsEntity, prelude::QueryState, world::World};
|
||||||
use bevy_platform::collections::HashSet;
|
use bevy_platform::collections::HashSet;
|
||||||
use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp};
|
use wgpu::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor, StoreOp};
|
||||||
@ -1,4 +1,5 @@
|
|||||||
mod app;
|
mod app;
|
||||||
|
mod camera_driver_node;
|
||||||
mod context;
|
mod context;
|
||||||
mod edge;
|
mod edge;
|
||||||
mod graph;
|
mod graph;
|
||||||
@ -6,6 +7,7 @@ mod node;
|
|||||||
mod node_slot;
|
mod node_slot;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
|
pub use camera_driver_node::*;
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use edge::*;
|
pub use edge::*;
|
||||||
pub use graph::*;
|
pub use graph::*;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
camera::Viewport,
|
|
||||||
diagnostic::internal::{Pass, PassKind, WritePipelineStatistics, WriteTimestamp},
|
diagnostic::internal::{Pass, PassKind, WritePipelineStatistics, WriteTimestamp},
|
||||||
render_resource::{
|
render_resource::{
|
||||||
BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId,
|
BindGroup, BindGroupId, Buffer, BufferId, BufferSlice, RenderPipeline, RenderPipelineId,
|
||||||
@ -7,6 +6,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
};
|
};
|
||||||
|
use bevy_camera::Viewport;
|
||||||
use bevy_color::LinearRgba;
|
use bevy_color::LinearRgba;
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
use crate::{extract_resource::ExtractResource, render_resource::TextureView};
|
use bevy_camera::ManualTextureViewHandle;
|
||||||
use bevy_ecs::{prelude::Component, reflect::ReflectComponent, resource::Resource};
|
use bevy_ecs::{prelude::Component, resource::Resource};
|
||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault;
|
||||||
use bevy_math::UVec2;
|
use bevy_math::UVec2;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_render_macros::ExtractResource;
|
||||||
use wgpu::TextureFormat;
|
use wgpu::TextureFormat;
|
||||||
|
|
||||||
/// A unique id that corresponds to a specific [`ManualTextureView`] in the [`ManualTextureViews`] collection.
|
use crate::render_resource::TextureView;
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Component, Reflect)]
|
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Hash, Clone)]
|
|
||||||
pub struct ManualTextureViewHandle(pub u32);
|
|
||||||
|
|
||||||
/// A manually managed [`TextureView`] for use as a [`crate::camera::RenderTarget`].
|
/// A manually managed [`TextureView`] for use as a [`crate::camera::RenderTarget`].
|
||||||
#[derive(Debug, Clone, Component)]
|
#[derive(Debug, Clone, Component)]
|
||||||
@ -1,5 +1,6 @@
|
|||||||
mod fallback_image;
|
mod fallback_image;
|
||||||
mod gpu_image;
|
mod gpu_image;
|
||||||
|
mod manual_texture_view;
|
||||||
mod texture_attachment;
|
mod texture_attachment;
|
||||||
mod texture_cache;
|
mod texture_cache;
|
||||||
|
|
||||||
@ -14,11 +15,13 @@ use bevy_image::{
|
|||||||
};
|
};
|
||||||
pub use fallback_image::*;
|
pub use fallback_image::*;
|
||||||
pub use gpu_image::*;
|
pub use gpu_image::*;
|
||||||
|
pub use manual_texture_view::*;
|
||||||
pub use texture_attachment::*;
|
pub use texture_attachment::*;
|
||||||
pub use texture_cache::*;
|
pub use texture_cache::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render_asset::RenderAssetPlugin, renderer::RenderDevice, Render, RenderApp, RenderSystems,
|
extract_resource::ExtractResourcePlugin, render_asset::RenderAssetPlugin,
|
||||||
|
renderer::RenderDevice, Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_asset::{uuid_handle, AssetApp, Assets, Handle};
|
use bevy_asset::{uuid_handle, AssetApp, Assets, Handle};
|
||||||
@ -74,10 +77,14 @@ impl Plugin for ImagePlugin {
|
|||||||
app.init_asset_loader::<HdrTextureLoader>();
|
app.init_asset_loader::<HdrTextureLoader>();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.add_plugins(RenderAssetPlugin::<GpuImage>::default())
|
app.add_plugins((
|
||||||
.register_type::<Image>()
|
RenderAssetPlugin::<GpuImage>::default(),
|
||||||
.init_asset::<Image>()
|
ExtractResourcePlugin::<ManualTextureViews>::default(),
|
||||||
.register_asset_reflect::<Image>();
|
))
|
||||||
|
.init_resource::<ManualTextureViews>()
|
||||||
|
.register_type::<Image>()
|
||||||
|
.init_asset::<Image>()
|
||||||
|
.register_asset_reflect::<Image>();
|
||||||
|
|
||||||
let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
|
let mut image_assets = app.world_mut().resource_mut::<Assets<Image>>();
|
||||||
|
|
||||||
|
|||||||
@ -1,27 +1,26 @@
|
|||||||
pub mod visibility;
|
pub mod visibility;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
|
use bevy_camera::{
|
||||||
|
primitives::Frustum, CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure,
|
||||||
|
};
|
||||||
use bevy_diagnostic::FrameCount;
|
use bevy_diagnostic::FrameCount;
|
||||||
pub use visibility::*;
|
pub use visibility::*;
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{
|
camera::{ExtractedCamera, MipBias, NormalizedRenderTarget, TemporalJitter},
|
||||||
CameraMainTextureUsages, ClearColor, ClearColorConfig, Exposure, ExtractedCamera,
|
|
||||||
ManualTextureViews, MipBias, NormalizedRenderTarget, TemporalJitter,
|
|
||||||
},
|
|
||||||
experimental::occlusion_culling::OcclusionCulling,
|
experimental::occlusion_culling::OcclusionCulling,
|
||||||
extract_component::ExtractComponentPlugin,
|
extract_component::ExtractComponentPlugin,
|
||||||
load_shader_library,
|
load_shader_library,
|
||||||
primitives::Frustum,
|
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_phase::ViewRangefinder3d,
|
render_phase::ViewRangefinder3d,
|
||||||
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
|
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
sync_world::MainEntity,
|
sync_world::MainEntity,
|
||||||
texture::{
|
texture::{
|
||||||
CachedTexture, ColorAttachment, DepthAttachment, GpuImage, OutputColorAttachment,
|
CachedTexture, ColorAttachment, DepthAttachment, GpuImage, ManualTextureViews,
|
||||||
TextureCache,
|
OutputColorAttachment, TextureCache,
|
||||||
},
|
},
|
||||||
Render, RenderApp, RenderSystems,
|
Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
@ -100,13 +99,7 @@ impl Plugin for ViewPlugin {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
load_shader_library!(app, "view.wgsl");
|
load_shader_library!(app, "view.wgsl");
|
||||||
|
|
||||||
app.register_type::<InheritedVisibility>()
|
app.register_type::<Msaa>()
|
||||||
.register_type::<ViewVisibility>()
|
|
||||||
.register_type::<Msaa>()
|
|
||||||
.register_type::<NoFrustumCulling>()
|
|
||||||
.register_type::<RenderLayers>()
|
|
||||||
.register_type::<Visibility>()
|
|
||||||
.register_type::<VisibleEntities>()
|
|
||||||
.register_type::<ColorGrading>()
|
.register_type::<ColorGrading>()
|
||||||
.register_type::<OcclusionCulling>()
|
.register_type::<OcclusionCulling>()
|
||||||
// NOTE: windows.is_changed() handles cases where a window was resized
|
// NOTE: windows.is_changed() handles cases where a window was resized
|
||||||
@ -114,8 +107,7 @@ impl Plugin for ViewPlugin {
|
|||||||
ExtractComponentPlugin::<Hdr>::default(),
|
ExtractComponentPlugin::<Hdr>::default(),
|
||||||
ExtractComponentPlugin::<Msaa>::default(),
|
ExtractComponentPlugin::<Msaa>::default(),
|
||||||
ExtractComponentPlugin::<OcclusionCulling>::default(),
|
ExtractComponentPlugin::<OcclusionCulling>::default(),
|
||||||
VisibilityPlugin,
|
RenderVisibilityRangePlugin,
|
||||||
VisibilityRangePlugin,
|
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||||
@ -729,9 +721,6 @@ impl From<ColorGrading> for ColorGradingUniform {
|
|||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
pub struct NoIndirectDrawing;
|
pub struct NoIndirectDrawing;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
|
||||||
pub struct NoCpuCulling;
|
|
||||||
|
|
||||||
impl ViewTarget {
|
impl ViewTarget {
|
||||||
pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float;
|
pub const TEXTURE_FORMAT_HDR: TextureFormat = TextureFormat::Rgba16Float;
|
||||||
|
|
||||||
|
|||||||
@ -1,269 +1,14 @@
|
|||||||
mod range;
|
|
||||||
mod render_layers;
|
|
||||||
|
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
|
|
||||||
use bevy_ecs::entity::EntityHashSet;
|
use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent};
|
||||||
use bevy_ecs::lifecycle::HookContext;
|
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||||
use bevy_ecs::world::DeferredWorld;
|
use bevy_utils::TypeIdMap;
|
||||||
use derive_more::derive::{Deref, DerefMut};
|
|
||||||
|
use crate::sync_world::MainEntity;
|
||||||
|
|
||||||
|
mod range;
|
||||||
|
pub use bevy_camera::visibility::*;
|
||||||
pub use range::*;
|
pub use range::*;
|
||||||
pub use render_layers::*;
|
|
||||||
|
|
||||||
use bevy_app::{Plugin, PostUpdate};
|
|
||||||
use bevy_asset::Assets;
|
|
||||||
use bevy_ecs::{hierarchy::validate_parent_has_component, prelude::*};
|
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
|
||||||
use bevy_transform::{components::GlobalTransform, TransformSystems};
|
|
||||||
use bevy_utils::{Parallel, TypeIdMap};
|
|
||||||
use smallvec::SmallVec;
|
|
||||||
|
|
||||||
use super::NoCpuCulling;
|
|
||||||
use crate::{
|
|
||||||
camera::{Camera, Projection},
|
|
||||||
mesh::{Mesh, Mesh3d, MeshAabb},
|
|
||||||
primitives::{Aabb, Frustum, Sphere},
|
|
||||||
sync_world::MainEntity,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// User indication of whether an entity is visible. Propagates down the entity hierarchy.
|
|
||||||
///
|
|
||||||
/// If an entity is hidden in this way, all [`Children`] (and all of their children and so on) who
|
|
||||||
/// are set to [`Inherited`](Self::Inherited) will also be hidden.
|
|
||||||
///
|
|
||||||
/// This is done by the `visibility_propagate_system` which uses the entity hierarchy and
|
|
||||||
/// `Visibility` to set the values of each entity's [`InheritedVisibility`] component.
|
|
||||||
#[derive(Component, Clone, Copy, Reflect, Debug, PartialEq, Eq, Default)]
|
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
|
||||||
#[require(InheritedVisibility, ViewVisibility)]
|
|
||||||
pub enum Visibility {
|
|
||||||
/// An entity with `Visibility::Inherited` will inherit the Visibility of its [`ChildOf`] target.
|
|
||||||
///
|
|
||||||
/// A root-level entity that is set to `Inherited` will be visible.
|
|
||||||
#[default]
|
|
||||||
Inherited,
|
|
||||||
/// An entity with `Visibility::Hidden` will be unconditionally hidden.
|
|
||||||
Hidden,
|
|
||||||
/// An entity with `Visibility::Visible` will be unconditionally visible.
|
|
||||||
///
|
|
||||||
/// Note that an entity with `Visibility::Visible` will be visible regardless of whether the
|
|
||||||
/// [`ChildOf`] target entity is hidden.
|
|
||||||
Visible,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Visibility {
|
|
||||||
/// Toggles between `Visibility::Inherited` and `Visibility::Visible`.
|
|
||||||
/// If the value is `Visibility::Hidden`, it remains unaffected.
|
|
||||||
#[inline]
|
|
||||||
pub fn toggle_inherited_visible(&mut self) {
|
|
||||||
*self = match *self {
|
|
||||||
Visibility::Inherited => Visibility::Visible,
|
|
||||||
Visibility::Visible => Visibility::Inherited,
|
|
||||||
_ => *self,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// Toggles between `Visibility::Inherited` and `Visibility::Hidden`.
|
|
||||||
/// If the value is `Visibility::Visible`, it remains unaffected.
|
|
||||||
#[inline]
|
|
||||||
pub fn toggle_inherited_hidden(&mut self) {
|
|
||||||
*self = match *self {
|
|
||||||
Visibility::Inherited => Visibility::Hidden,
|
|
||||||
Visibility::Hidden => Visibility::Inherited,
|
|
||||||
_ => *self,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/// Toggles between `Visibility::Visible` and `Visibility::Hidden`.
|
|
||||||
/// If the value is `Visibility::Inherited`, it remains unaffected.
|
|
||||||
#[inline]
|
|
||||||
pub fn toggle_visible_hidden(&mut self) {
|
|
||||||
*self = match *self {
|
|
||||||
Visibility::Visible => Visibility::Hidden,
|
|
||||||
Visibility::Hidden => Visibility::Visible,
|
|
||||||
_ => *self,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allows `&Visibility == Visibility`
|
|
||||||
impl PartialEq<Visibility> for &Visibility {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &Visibility) -> bool {
|
|
||||||
// Use the base Visibility == Visibility implementation.
|
|
||||||
<Visibility as PartialEq<Visibility>>::eq(*self, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allows `Visibility == &Visibility`
|
|
||||||
impl PartialEq<&Visibility> for Visibility {
|
|
||||||
#[inline]
|
|
||||||
fn eq(&self, other: &&Visibility) -> bool {
|
|
||||||
// Use the base Visibility == Visibility implementation.
|
|
||||||
<Visibility as PartialEq<Visibility>>::eq(self, *other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether or not an entity is visible in the hierarchy.
|
|
||||||
/// This will not be accurate until [`VisibilityPropagate`] runs in the [`PostUpdate`] schedule.
|
|
||||||
///
|
|
||||||
/// If this is false, then [`ViewVisibility`] should also be false.
|
|
||||||
///
|
|
||||||
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
|
||||||
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
|
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
|
||||||
#[component(on_insert = validate_parent_has_component::<Self>)]
|
|
||||||
pub struct InheritedVisibility(bool);
|
|
||||||
|
|
||||||
impl InheritedVisibility {
|
|
||||||
/// An entity that is invisible in the hierarchy.
|
|
||||||
pub const HIDDEN: Self = Self(false);
|
|
||||||
/// An entity that is visible in the hierarchy.
|
|
||||||
pub const VISIBLE: Self = Self(true);
|
|
||||||
|
|
||||||
/// Returns `true` if the entity is visible in the hierarchy.
|
|
||||||
/// Otherwise, returns `false`.
|
|
||||||
#[inline]
|
|
||||||
pub fn get(self) -> bool {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bucket into which we group entities for the purposes of visibility.
|
|
||||||
///
|
|
||||||
/// Bevy's various rendering subsystems (3D, 2D, etc.) want to be able to
|
|
||||||
/// quickly winnow the set of entities to only those that the subsystem is
|
|
||||||
/// tasked with rendering, to avoid spending time examining irrelevant entities.
|
|
||||||
/// At the same time, Bevy wants the [`check_visibility`] system to determine
|
|
||||||
/// all entities' visibilities at the same time, regardless of what rendering
|
|
||||||
/// subsystem is responsible for drawing them. Additionally, your application
|
|
||||||
/// may want to add more types of renderable objects that Bevy determines
|
|
||||||
/// visibility for just as it does for Bevy's built-in objects.
|
|
||||||
///
|
|
||||||
/// The solution to this problem is *visibility classes*. A visibility class is
|
|
||||||
/// a type, typically the type of a component, that represents the subsystem
|
|
||||||
/// that renders it: for example, `Mesh3d`, `Mesh2d`, and `Sprite`. The
|
|
||||||
/// [`VisibilityClass`] component stores the visibility class or classes that
|
|
||||||
/// the entity belongs to. (Generally, an object will belong to only one
|
|
||||||
/// visibility class, but in rare cases it may belong to multiple.)
|
|
||||||
///
|
|
||||||
/// When adding a new renderable component, you'll typically want to write an
|
|
||||||
/// add-component hook that adds the type ID of that component to the
|
|
||||||
/// [`VisibilityClass`] array. See `custom_phase_item` for an example.
|
|
||||||
//
|
|
||||||
// Note: This can't be a `ComponentId` because the visibility classes are copied
|
|
||||||
// into the render world, and component IDs are per-world.
|
|
||||||
#[derive(Clone, Component, Default, Reflect, Deref, DerefMut)]
|
|
||||||
#[reflect(Component, Default, Clone)]
|
|
||||||
pub struct VisibilityClass(pub SmallVec<[TypeId; 1]>);
|
|
||||||
|
|
||||||
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
|
|
||||||
///
|
|
||||||
/// Each frame, this will be reset to `false` during [`VisibilityPropagate`] systems in [`PostUpdate`].
|
|
||||||
/// Later in the frame, systems in [`CheckVisibility`] will mark any visible entities using [`ViewVisibility::set`].
|
|
||||||
/// Because of this, values of this type will be marked as changed every frame, even when they do not change.
|
|
||||||
///
|
|
||||||
/// If you wish to add custom visibility system that sets this value, make sure you add it to the [`CheckVisibility`] set.
|
|
||||||
///
|
|
||||||
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
|
||||||
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
|
|
||||||
#[derive(Component, Deref, Debug, Default, Clone, Copy, Reflect, PartialEq, Eq)]
|
|
||||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
|
||||||
pub struct ViewVisibility(bool);
|
|
||||||
|
|
||||||
impl ViewVisibility {
|
|
||||||
/// An entity that cannot be seen from any views.
|
|
||||||
pub const HIDDEN: Self = Self(false);
|
|
||||||
|
|
||||||
/// Returns `true` if the entity is visible in any view.
|
|
||||||
/// Otherwise, returns `false`.
|
|
||||||
#[inline]
|
|
||||||
pub fn get(self) -> bool {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the visibility to `true`. This should not be considered reversible for a given frame,
|
|
||||||
/// as this component tracks whether or not the entity visible in _any_ view.
|
|
||||||
///
|
|
||||||
/// This will be automatically reset to `false` every frame in [`VisibilityPropagate`] and then set
|
|
||||||
/// to the proper value in [`CheckVisibility`].
|
|
||||||
///
|
|
||||||
/// You should only manually set this if you are defining a custom visibility system,
|
|
||||||
/// in which case the system should be placed in the [`CheckVisibility`] set.
|
|
||||||
/// For normal user-defined entity visibility, see [`Visibility`].
|
|
||||||
///
|
|
||||||
/// [`VisibilityPropagate`]: VisibilitySystems::VisibilityPropagate
|
|
||||||
/// [`CheckVisibility`]: VisibilitySystems::CheckVisibility
|
|
||||||
#[inline]
|
|
||||||
pub fn set(&mut self) {
|
|
||||||
self.0 = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use this component to opt-out of built-in frustum culling for entities, see
|
|
||||||
/// [`Frustum`].
|
|
||||||
///
|
|
||||||
/// It can be used for example:
|
|
||||||
/// - when a [`Mesh`] is updated but its [`Aabb`] is not, which might happen with animations,
|
|
||||||
/// - when using some light effects, like wanting a [`Mesh`] out of the [`Frustum`]
|
|
||||||
/// to appear in the reflection of a [`Mesh`] within.
|
|
||||||
#[derive(Debug, Component, Default, Reflect)]
|
|
||||||
#[reflect(Component, Default, Debug)]
|
|
||||||
pub struct NoFrustumCulling;
|
|
||||||
|
|
||||||
/// Collection of entities visible from the current view.
|
|
||||||
///
|
|
||||||
/// This component contains all entities which are visible from the currently
|
|
||||||
/// rendered view. The collection is updated automatically by the [`VisibilitySystems::CheckVisibility`]
|
|
||||||
/// system set. Renderers can use the equivalent [`RenderVisibleEntities`] to optimize rendering of
|
|
||||||
/// a particular view, to prevent drawing items not visible from that view.
|
|
||||||
///
|
|
||||||
/// This component is intended to be attached to the same entity as the [`Camera`] and
|
|
||||||
/// the [`Frustum`] defining the view.
|
|
||||||
#[derive(Clone, Component, Default, Debug, Reflect)]
|
|
||||||
#[reflect(Component, Default, Debug, Clone)]
|
|
||||||
pub struct VisibleEntities {
|
|
||||||
#[reflect(ignore, clone)]
|
|
||||||
pub entities: TypeIdMap<Vec<Entity>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisibleEntities {
|
|
||||||
pub fn get(&self, type_id: TypeId) -> &[Entity] {
|
|
||||||
match self.entities.get(&type_id) {
|
|
||||||
Some(entities) => &entities[..],
|
|
||||||
None => &[],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_mut(&mut self, type_id: TypeId) -> &mut Vec<Entity> {
|
|
||||||
self.entities.entry(type_id).or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self, type_id: TypeId) -> impl DoubleEndedIterator<Item = &Entity> {
|
|
||||||
self.get(type_id).iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self, type_id: TypeId) -> usize {
|
|
||||||
self.get(type_id).len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self, type_id: TypeId) -> bool {
|
|
||||||
self.get(type_id).is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self, type_id: TypeId) {
|
|
||||||
self.get_mut(type_id).clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_all(&mut self) {
|
|
||||||
// Don't just nuke the hash table; we want to reuse allocations.
|
|
||||||
for entities in self.entities.values_mut() {
|
|
||||||
entities.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, entity: Entity, type_id: TypeId) {
|
|
||||||
self.get_mut(type_id).push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collection of entities visible from the current view.
|
/// Collection of entities visible from the current view.
|
||||||
///
|
///
|
||||||
@ -307,667 +52,3 @@ impl RenderVisibleEntities {
|
|||||||
self.get::<QF>().is_empty()
|
self.get::<QF>().is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
|
||||||
pub enum VisibilitySystems {
|
|
||||||
/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,
|
|
||||||
/// calculating and inserting an [`Aabb`] to relevant entities.
|
|
||||||
CalculateBounds,
|
|
||||||
/// Label for [`update_frusta`] in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
|
|
||||||
UpdateFrusta,
|
|
||||||
/// Label for the system propagating the [`InheritedVisibility`] in a
|
|
||||||
/// [`ChildOf`] / [`Children`] hierarchy.
|
|
||||||
VisibilityPropagate,
|
|
||||||
/// Label for the [`check_visibility`] system updating [`ViewVisibility`]
|
|
||||||
/// of each entity and the [`VisibleEntities`] of each view.\
|
|
||||||
///
|
|
||||||
/// System order ambiguities between systems in this set are ignored:
|
|
||||||
/// the order of systems within this set is irrelevant, as [`check_visibility`]
|
|
||||||
/// assumes that its operations are irreversible during the frame.
|
|
||||||
CheckVisibility,
|
|
||||||
/// Label for the `mark_newly_hidden_entities_invisible` system, which sets
|
|
||||||
/// [`ViewVisibility`] to [`ViewVisibility::HIDDEN`] for entities that no
|
|
||||||
/// view has marked as visible.
|
|
||||||
MarkNewlyHiddenEntitiesInvisible,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VisibilityPlugin;
|
|
||||||
|
|
||||||
impl Plugin for VisibilityPlugin {
|
|
||||||
fn build(&self, app: &mut bevy_app::App) {
|
|
||||||
use VisibilitySystems::*;
|
|
||||||
|
|
||||||
app.register_type::<VisibilityClass>()
|
|
||||||
.configure_sets(
|
|
||||||
PostUpdate,
|
|
||||||
(CalculateBounds, UpdateFrusta, VisibilityPropagate)
|
|
||||||
.before(CheckVisibility)
|
|
||||||
.after(TransformSystems::Propagate),
|
|
||||||
)
|
|
||||||
.configure_sets(
|
|
||||||
PostUpdate,
|
|
||||||
MarkNewlyHiddenEntitiesInvisible.after(CheckVisibility),
|
|
||||||
)
|
|
||||||
.init_resource::<PreviousVisibleEntities>()
|
|
||||||
.add_systems(
|
|
||||||
PostUpdate,
|
|
||||||
(
|
|
||||||
calculate_bounds.in_set(CalculateBounds),
|
|
||||||
(visibility_propagate_system, reset_view_visibility)
|
|
||||||
.in_set(VisibilityPropagate),
|
|
||||||
check_visibility.in_set(CheckVisibility),
|
|
||||||
mark_newly_hidden_entities_invisible.in_set(MarkNewlyHiddenEntitiesInvisible),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Computes and adds an [`Aabb`] component to entities with a
|
|
||||||
/// [`Mesh3d`] component and without a [`NoFrustumCulling`] component.
|
|
||||||
///
|
|
||||||
/// This system is used in system set [`VisibilitySystems::CalculateBounds`].
|
|
||||||
pub fn calculate_bounds(
|
|
||||||
mut commands: Commands,
|
|
||||||
meshes: Res<Assets<Mesh>>,
|
|
||||||
without_aabb: Query<(Entity, &Mesh3d), (Without<Aabb>, Without<NoFrustumCulling>)>,
|
|
||||||
) {
|
|
||||||
for (entity, mesh_handle) in &without_aabb {
|
|
||||||
if let Some(mesh) = meshes.get(mesh_handle) {
|
|
||||||
if let Some(aabb) = mesh.compute_aabb() {
|
|
||||||
commands.entity(entity).try_insert(aabb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates [`Frustum`].
|
|
||||||
///
|
|
||||||
/// This system is used in [`CameraProjectionPlugin`](crate::camera::CameraProjectionPlugin).
|
|
||||||
pub fn update_frusta(
|
|
||||||
mut views: Query<
|
|
||||||
(&GlobalTransform, &Projection, &mut Frustum),
|
|
||||||
Or<(Changed<GlobalTransform>, Changed<Projection>)>,
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
for (transform, projection, mut frustum) in &mut views {
|
|
||||||
*frustum = projection.compute_frustum(transform);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visibility_propagate_system(
|
|
||||||
changed: Query<
|
|
||||||
(Entity, &Visibility, Option<&ChildOf>, Option<&Children>),
|
|
||||||
(
|
|
||||||
With<InheritedVisibility>,
|
|
||||||
Or<(Changed<Visibility>, Changed<ChildOf>)>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
mut visibility_query: Query<(&Visibility, &mut InheritedVisibility)>,
|
|
||||||
children_query: Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
|
|
||||||
) {
|
|
||||||
for (entity, visibility, child_of, children) in &changed {
|
|
||||||
let is_visible = match visibility {
|
|
||||||
Visibility::Visible => true,
|
|
||||||
Visibility::Hidden => false,
|
|
||||||
// fall back to true if no parent is found or parent lacks components
|
|
||||||
Visibility::Inherited => child_of
|
|
||||||
.and_then(|c| visibility_query.get(c.parent()).ok())
|
|
||||||
.is_none_or(|(_, x)| x.get()),
|
|
||||||
};
|
|
||||||
let (_, mut inherited_visibility) = visibility_query
|
|
||||||
.get_mut(entity)
|
|
||||||
.expect("With<InheritedVisibility> ensures this query will return a value");
|
|
||||||
|
|
||||||
// Only update the visibility if it has changed.
|
|
||||||
// This will also prevent the visibility from propagating multiple times in the same frame
|
|
||||||
// if this entity's visibility has been updated recursively by its parent.
|
|
||||||
if inherited_visibility.get() != is_visible {
|
|
||||||
inherited_visibility.0 = is_visible;
|
|
||||||
|
|
||||||
// Recursively update the visibility of each child.
|
|
||||||
for &child in children.into_iter().flatten() {
|
|
||||||
let _ =
|
|
||||||
propagate_recursive(is_visible, child, &mut visibility_query, &children_query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn propagate_recursive(
|
|
||||||
parent_is_visible: bool,
|
|
||||||
entity: Entity,
|
|
||||||
visibility_query: &mut Query<(&Visibility, &mut InheritedVisibility)>,
|
|
||||||
children_query: &Query<&Children, (With<Visibility>, With<InheritedVisibility>)>,
|
|
||||||
// BLOCKED: https://github.com/rust-lang/rust/issues/31436
|
|
||||||
// We use a result here to use the `?` operator. Ideally we'd use a try block instead
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
// Get the visibility components for the current entity.
|
|
||||||
// If the entity does not have the required components, just return early.
|
|
||||||
let (visibility, mut inherited_visibility) = visibility_query.get_mut(entity).map_err(drop)?;
|
|
||||||
|
|
||||||
let is_visible = match visibility {
|
|
||||||
Visibility::Visible => true,
|
|
||||||
Visibility::Hidden => false,
|
|
||||||
Visibility::Inherited => parent_is_visible,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only update the visibility if it has changed.
|
|
||||||
if inherited_visibility.get() != is_visible {
|
|
||||||
inherited_visibility.0 = is_visible;
|
|
||||||
|
|
||||||
// Recursively update the visibility of each child.
|
|
||||||
for &child in children_query.get(entity).ok().into_iter().flatten() {
|
|
||||||
let _ = propagate_recursive(is_visible, child, visibility_query, children_query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores all entities that were visible in the previous frame.
|
|
||||||
///
|
|
||||||
/// As systems that check visibility judge entities visible, they remove them
|
|
||||||
/// from this set. Afterward, the `mark_newly_hidden_entities_invisible` system
|
|
||||||
/// runs and marks every mesh still remaining in this set as hidden.
|
|
||||||
#[derive(Resource, Default, Deref, DerefMut)]
|
|
||||||
pub struct PreviousVisibleEntities(EntityHashSet);
|
|
||||||
|
|
||||||
/// Resets the view visibility of every entity.
|
|
||||||
/// Entities that are visible will be marked as such later this frame
|
|
||||||
/// by a [`VisibilitySystems::CheckVisibility`] system.
|
|
||||||
fn reset_view_visibility(
|
|
||||||
mut query: Query<(Entity, &ViewVisibility)>,
|
|
||||||
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
|
||||||
) {
|
|
||||||
previous_visible_entities.clear();
|
|
||||||
|
|
||||||
query.iter_mut().for_each(|(entity, view_visibility)| {
|
|
||||||
// Record the entities that were previously visible.
|
|
||||||
if view_visibility.get() {
|
|
||||||
previous_visible_entities.insert(entity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// System updating the visibility of entities each frame.
|
|
||||||
///
|
|
||||||
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each
|
|
||||||
/// frame, it updates the [`ViewVisibility`] of all entities, and for each view
|
|
||||||
/// also compute the [`VisibleEntities`] for that view.
|
|
||||||
///
|
|
||||||
/// To ensure that an entity is checked for visibility, make sure that it has a
|
|
||||||
/// [`VisibilityClass`] component and that that component is nonempty.
|
|
||||||
pub fn check_visibility(
|
|
||||||
mut thread_queues: Local<Parallel<TypeIdMap<Vec<Entity>>>>,
|
|
||||||
mut view_query: Query<(
|
|
||||||
Entity,
|
|
||||||
&mut VisibleEntities,
|
|
||||||
&Frustum,
|
|
||||||
Option<&RenderLayers>,
|
|
||||||
&Camera,
|
|
||||||
Has<NoCpuCulling>,
|
|
||||||
)>,
|
|
||||||
mut visible_aabb_query: Query<(
|
|
||||||
Entity,
|
|
||||||
&InheritedVisibility,
|
|
||||||
&mut ViewVisibility,
|
|
||||||
&VisibilityClass,
|
|
||||||
Option<&RenderLayers>,
|
|
||||||
Option<&Aabb>,
|
|
||||||
&GlobalTransform,
|
|
||||||
Has<NoFrustumCulling>,
|
|
||||||
Has<VisibilityRange>,
|
|
||||||
)>,
|
|
||||||
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
|
|
||||||
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
|
||||||
) {
|
|
||||||
let visible_entity_ranges = visible_entity_ranges.as_deref();
|
|
||||||
|
|
||||||
for (view, mut visible_entities, frustum, maybe_view_mask, camera, no_cpu_culling) in
|
|
||||||
&mut view_query
|
|
||||||
{
|
|
||||||
if !camera.is_active {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let view_mask = maybe_view_mask.unwrap_or_default();
|
|
||||||
|
|
||||||
visible_aabb_query.par_iter_mut().for_each_init(
|
|
||||||
|| thread_queues.borrow_local_mut(),
|
|
||||||
|queue, query_item| {
|
|
||||||
let (
|
|
||||||
entity,
|
|
||||||
inherited_visibility,
|
|
||||||
mut view_visibility,
|
|
||||||
visibility_class,
|
|
||||||
maybe_entity_mask,
|
|
||||||
maybe_model_aabb,
|
|
||||||
transform,
|
|
||||||
no_frustum_culling,
|
|
||||||
has_visibility_range,
|
|
||||||
) = query_item;
|
|
||||||
|
|
||||||
// Skip computing visibility for entities that are configured to be hidden.
|
|
||||||
// ViewVisibility has already been reset in `reset_view_visibility`.
|
|
||||||
if !inherited_visibility.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let entity_mask = maybe_entity_mask.unwrap_or_default();
|
|
||||||
if !view_mask.intersects(entity_mask) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If outside of the visibility range, cull.
|
|
||||||
if has_visibility_range
|
|
||||||
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
|
|
||||||
!visible_entity_ranges.entity_is_in_range_of_view(entity, view)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have an aabb, do frustum culling
|
|
||||||
if !no_frustum_culling && !no_cpu_culling {
|
|
||||||
if let Some(model_aabb) = maybe_model_aabb {
|
|
||||||
let world_from_local = transform.affine();
|
|
||||||
let model_sphere = Sphere {
|
|
||||||
center: world_from_local.transform_point3a(model_aabb.center),
|
|
||||||
radius: transform.radius_vec3a(model_aabb.half_extents),
|
|
||||||
};
|
|
||||||
// Do quick sphere-based frustum culling
|
|
||||||
if !frustum.intersects_sphere(&model_sphere, false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Do aabb-based frustum culling
|
|
||||||
if !frustum.intersects_obb(model_aabb, &world_from_local, true, false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we don't trigger changed notifications
|
|
||||||
// unnecessarily by checking whether the flag is set before
|
|
||||||
// setting it.
|
|
||||||
if !**view_visibility {
|
|
||||||
view_visibility.set();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the entity to the queue for all visibility classes the
|
|
||||||
// entity is in.
|
|
||||||
for visibility_class_id in visibility_class.iter() {
|
|
||||||
queue.entry(*visibility_class_id).or_default().push(entity);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
visible_entities.clear_all();
|
|
||||||
|
|
||||||
// Drain all the thread queues into the `visible_entities` list.
|
|
||||||
for class_queues in thread_queues.iter_mut() {
|
|
||||||
for (class, entities) in class_queues {
|
|
||||||
let visible_entities_for_class = visible_entities.get_mut(*class);
|
|
||||||
for entity in entities.drain(..) {
|
|
||||||
// As we mark entities as visible, we remove them from the
|
|
||||||
// `previous_visible_entities` list. At the end, all of the
|
|
||||||
// entities remaining in `previous_visible_entities` will be
|
|
||||||
// entities that were visible last frame but are no longer
|
|
||||||
// visible this frame.
|
|
||||||
previous_visible_entities.remove(&entity);
|
|
||||||
|
|
||||||
visible_entities_for_class.push(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Marks any entities that weren't judged visible this frame as invisible.
|
|
||||||
///
|
|
||||||
/// As visibility-determining systems run, they remove entities that they judge
|
|
||||||
/// visible from [`PreviousVisibleEntities`]. At the end of visibility
|
|
||||||
/// determination, all entities that remain in [`PreviousVisibleEntities`] must
|
|
||||||
/// be invisible. This system goes through those entities and marks them newly
|
|
||||||
/// invisible (which sets the change flag for them).
|
|
||||||
fn mark_newly_hidden_entities_invisible(
|
|
||||||
mut view_visibilities: Query<&mut ViewVisibility>,
|
|
||||||
mut previous_visible_entities: ResMut<PreviousVisibleEntities>,
|
|
||||||
) {
|
|
||||||
// Whatever previous visible entities are left are entities that were
|
|
||||||
// visible last frame but just became invisible.
|
|
||||||
for entity in previous_visible_entities.drain() {
|
|
||||||
if let Ok(mut view_visibility) = view_visibilities.get_mut(entity) {
|
|
||||||
*view_visibility = ViewVisibility::HIDDEN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A generic component add hook that automatically adds the appropriate
|
|
||||||
/// [`VisibilityClass`] to an entity.
|
|
||||||
///
|
|
||||||
/// This can be handy when creating custom renderable components. To use this
|
|
||||||
/// hook, add it to your renderable component like this:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// #[derive(Component)]
|
|
||||||
/// #[component(on_add = add_visibility_class::<MyComponent>)]
|
|
||||||
/// struct MyComponent {
|
|
||||||
/// ...
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn add_visibility_class<C>(
|
|
||||||
mut world: DeferredWorld<'_>,
|
|
||||||
HookContext { entity, .. }: HookContext,
|
|
||||||
) where
|
|
||||||
C: 'static,
|
|
||||||
{
|
|
||||||
if let Some(mut visibility_class) = world.get_mut::<VisibilityClass>(entity) {
|
|
||||||
visibility_class.push(TypeId::of::<C>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use bevy_app::prelude::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn visibility_propagation() {
|
|
||||||
let mut app = App::new();
|
|
||||||
app.add_systems(Update, visibility_propagate_system);
|
|
||||||
|
|
||||||
let root1 = app.world_mut().spawn(Visibility::Hidden).id();
|
|
||||||
let root1_child1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
let root1_child2 = app.world_mut().spawn(Visibility::Hidden).id();
|
|
||||||
let root1_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
let root1_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1)
|
|
||||||
.add_children(&[root1_child1, root1_child2]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1_child1)
|
|
||||||
.add_children(&[root1_child1_grandchild1]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1_child2)
|
|
||||||
.add_children(&[root1_child2_grandchild1]);
|
|
||||||
|
|
||||||
let root2 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
let root2_child1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
let root2_child2 = app.world_mut().spawn(Visibility::Hidden).id();
|
|
||||||
let root2_child1_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
let root2_child2_grandchild1 = app.world_mut().spawn(Visibility::default()).id();
|
|
||||||
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root2)
|
|
||||||
.add_children(&[root2_child1, root2_child2]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root2_child1)
|
|
||||||
.add_children(&[root2_child1_grandchild1]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root2_child2)
|
|
||||||
.add_children(&[root2_child2_grandchild1]);
|
|
||||||
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
let is_visible = |e: Entity| {
|
|
||||||
app.world()
|
|
||||||
.entity(e)
|
|
||||||
.get::<InheritedVisibility>()
|
|
||||||
.unwrap()
|
|
||||||
.get()
|
|
||||||
};
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1),
|
|
||||||
"invisibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1_child1),
|
|
||||||
"invisibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1_child2),
|
|
||||||
"invisibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1_child1_grandchild1),
|
|
||||||
"invisibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1_child2_grandchild1),
|
|
||||||
"invisibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
is_visible(root2),
|
|
||||||
"visibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
is_visible(root2_child1),
|
|
||||||
"visibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root2_child2),
|
|
||||||
"visibility propagates down tree from root, but local invisibility is preserved"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
is_visible(root2_child1_grandchild1),
|
|
||||||
"visibility propagates down tree from root"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root2_child2_grandchild1),
|
|
||||||
"child's invisibility propagates down to grandchild"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_visibility_propagation_on_parent_change() {
|
|
||||||
// Setup the world and schedule
|
|
||||||
let mut app = App::new();
|
|
||||||
|
|
||||||
app.add_systems(Update, visibility_propagate_system);
|
|
||||||
|
|
||||||
// Create entities with visibility and hierarchy
|
|
||||||
let parent1 = app.world_mut().spawn((Visibility::Hidden,)).id();
|
|
||||||
let parent2 = app.world_mut().spawn((Visibility::Visible,)).id();
|
|
||||||
let child1 = app.world_mut().spawn((Visibility::Inherited,)).id();
|
|
||||||
let child2 = app.world_mut().spawn((Visibility::Inherited,)).id();
|
|
||||||
|
|
||||||
// Build hierarchy
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(parent1)
|
|
||||||
.add_children(&[child1, child2]);
|
|
||||||
|
|
||||||
// Run the system initially to set up visibility
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
// Change parent visibility to Hidden
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(parent2)
|
|
||||||
.insert(Visibility::Visible);
|
|
||||||
// Simulate a change in the parent component
|
|
||||||
app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent
|
|
||||||
|
|
||||||
// Run the system again to propagate changes
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
let is_visible = |e: Entity| {
|
|
||||||
app.world()
|
|
||||||
.entity(e)
|
|
||||||
.get::<InheritedVisibility>()
|
|
||||||
.unwrap()
|
|
||||||
.get()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Retrieve and assert visibility
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!is_visible(child1),
|
|
||||||
"Child1 should inherit visibility from parent"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
is_visible(child2),
|
|
||||||
"Child2 should inherit visibility from parent"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn visibility_propagation_unconditional_visible() {
|
|
||||||
use Visibility::{Hidden, Inherited, Visible};
|
|
||||||
|
|
||||||
let mut app = App::new();
|
|
||||||
app.add_systems(Update, visibility_propagate_system);
|
|
||||||
|
|
||||||
let root1 = app.world_mut().spawn(Visible).id();
|
|
||||||
let root1_child1 = app.world_mut().spawn(Inherited).id();
|
|
||||||
let root1_child2 = app.world_mut().spawn(Hidden).id();
|
|
||||||
let root1_child1_grandchild1 = app.world_mut().spawn(Visible).id();
|
|
||||||
let root1_child2_grandchild1 = app.world_mut().spawn(Visible).id();
|
|
||||||
|
|
||||||
let root2 = app.world_mut().spawn(Inherited).id();
|
|
||||||
let root3 = app.world_mut().spawn(Hidden).id();
|
|
||||||
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1)
|
|
||||||
.add_children(&[root1_child1, root1_child2]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1_child1)
|
|
||||||
.add_children(&[root1_child1_grandchild1]);
|
|
||||||
app.world_mut()
|
|
||||||
.entity_mut(root1_child2)
|
|
||||||
.add_children(&[root1_child2_grandchild1]);
|
|
||||||
|
|
||||||
app.update();
|
|
||||||
|
|
||||||
let is_visible = |e: Entity| {
|
|
||||||
app.world()
|
|
||||||
.entity(e)
|
|
||||||
.get::<InheritedVisibility>()
|
|
||||||
.unwrap()
|
|
||||||
.get()
|
|
||||||
};
|
|
||||||
assert!(
|
|
||||||
is_visible(root1),
|
|
||||||
"an unconditionally visible root is visible"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
is_visible(root1_child1),
|
|
||||||
"an inheriting child of an unconditionally visible parent is visible"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!is_visible(root1_child2),
|
|
||||||
"a hidden child on an unconditionally visible parent is hidden"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
is_visible(root1_child1_grandchild1),
|
|
||||||
"an unconditionally visible child of an inheriting parent is visible"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
is_visible(root1_child2_grandchild1),
|
|
||||||
"an unconditionally visible child of a hidden parent is visible"
|
|
||||||
);
|
|
||||||
assert!(is_visible(root2), "an inheriting root is visible");
|
|
||||||
assert!(!is_visible(root3), "a hidden root is hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn visibility_propagation_change_detection() {
|
|
||||||
let mut world = World::new();
|
|
||||||
let mut schedule = Schedule::default();
|
|
||||||
schedule.add_systems(visibility_propagate_system);
|
|
||||||
|
|
||||||
// Set up an entity hierarchy.
|
|
||||||
|
|
||||||
let id1 = world.spawn(Visibility::default()).id();
|
|
||||||
|
|
||||||
let id2 = world.spawn(Visibility::default()).id();
|
|
||||||
world.entity_mut(id1).add_children(&[id2]);
|
|
||||||
|
|
||||||
let id3 = world.spawn(Visibility::Hidden).id();
|
|
||||||
world.entity_mut(id2).add_children(&[id3]);
|
|
||||||
|
|
||||||
let id4 = world.spawn(Visibility::default()).id();
|
|
||||||
world.entity_mut(id3).add_children(&[id4]);
|
|
||||||
|
|
||||||
// Test the hierarchy.
|
|
||||||
|
|
||||||
// Make sure the hierarchy is up-to-date.
|
|
||||||
schedule.run(&mut world);
|
|
||||||
world.clear_trackers();
|
|
||||||
|
|
||||||
let mut q = world.query::<Ref<InheritedVisibility>>();
|
|
||||||
|
|
||||||
assert!(!q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id4).unwrap().is_changed());
|
|
||||||
|
|
||||||
world.clear_trackers();
|
|
||||||
world.entity_mut(id1).insert(Visibility::Hidden);
|
|
||||||
schedule.run(&mut world);
|
|
||||||
|
|
||||||
assert!(q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id4).unwrap().is_changed());
|
|
||||||
|
|
||||||
world.clear_trackers();
|
|
||||||
schedule.run(&mut world);
|
|
||||||
|
|
||||||
assert!(!q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id4).unwrap().is_changed());
|
|
||||||
|
|
||||||
world.clear_trackers();
|
|
||||||
world.entity_mut(id3).insert(Visibility::Inherited);
|
|
||||||
schedule.run(&mut world);
|
|
||||||
|
|
||||||
assert!(!q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id4).unwrap().is_changed());
|
|
||||||
|
|
||||||
world.clear_trackers();
|
|
||||||
world.entity_mut(id2).insert(Visibility::Visible);
|
|
||||||
schedule.run(&mut world);
|
|
||||||
|
|
||||||
assert!(!q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(q.get(&world, id4).unwrap().is_changed());
|
|
||||||
|
|
||||||
world.clear_trackers();
|
|
||||||
schedule.run(&mut world);
|
|
||||||
|
|
||||||
assert!(!q.get(&world, id1).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id2).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id3).unwrap().is_changed());
|
|
||||||
assert!(!q.get(&world, id4).unwrap().is_changed());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn visibility_propagation_with_invalid_parent() {
|
|
||||||
let mut world = World::new();
|
|
||||||
let mut schedule = Schedule::default();
|
|
||||||
schedule.add_systems(visibility_propagate_system);
|
|
||||||
|
|
||||||
let parent = world.spawn(()).id();
|
|
||||||
let child = world.spawn(Visibility::default()).id();
|
|
||||||
world.entity_mut(parent).add_children(&[child]);
|
|
||||||
|
|
||||||
schedule.run(&mut world);
|
|
||||||
world.clear_trackers();
|
|
||||||
|
|
||||||
let child_visible = world.entity(child).get::<InheritedVisibility>().unwrap().0;
|
|
||||||
// defaults to same behavior of parent not found: visible = true
|
|
||||||
assert!(child_visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ensure_visibility_enum_size() {
|
|
||||||
assert_eq!(1, size_of::<Visibility>());
|
|
||||||
assert_eq!(1, size_of::<Option<Visibility>>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,37 +1,26 @@
|
|||||||
//! Specific distances from the camera in which entities are visible, also known
|
//! Specific distances from the camera in which entities are visible, also known
|
||||||
//! as *hierarchical levels of detail* or *HLOD*s.
|
//! as *hierarchical levels of detail* or *HLOD*s.
|
||||||
|
|
||||||
use core::{
|
use super::VisibilityRange;
|
||||||
hash::{Hash, Hasher},
|
use bevy_app::{App, Plugin};
|
||||||
ops::Range,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bevy_app::{App, Plugin, PostUpdate};
|
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
entity::Entity,
|
||||||
entity::{Entity, EntityHashMap},
|
|
||||||
lifecycle::RemovedComponents,
|
lifecycle::RemovedComponents,
|
||||||
query::{Changed, With},
|
query::Changed,
|
||||||
reflect::ReflectComponent,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::IntoScheduleConfigs as _,
|
schedule::IntoScheduleConfigs as _,
|
||||||
system::{Local, Query, Res, ResMut},
|
system::{Query, Res, ResMut},
|
||||||
};
|
};
|
||||||
use bevy_math::{vec4, FloatOrd, Vec4};
|
use bevy_math::{vec4, Vec4};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::Reflect;
|
use bevy_utils::prelude::default;
|
||||||
use bevy_transform::components::GlobalTransform;
|
|
||||||
use bevy_utils::{prelude::default, Parallel};
|
|
||||||
use nonmax::NonMaxU16;
|
use nonmax::NonMaxU16;
|
||||||
use wgpu::{BufferBindingType, BufferUsages};
|
use wgpu::{BufferBindingType, BufferUsages};
|
||||||
|
|
||||||
use super::{check_visibility, VisibilitySystems};
|
|
||||||
use crate::sync_world::{MainEntity, MainEntityHashMap};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::Camera,
|
|
||||||
primitives::Aabb,
|
|
||||||
render_resource::BufferVec,
|
render_resource::BufferVec,
|
||||||
renderer::{RenderDevice, RenderQueue},
|
renderer::{RenderDevice, RenderQueue},
|
||||||
|
sync_world::{MainEntity, MainEntityHashMap},
|
||||||
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,21 +37,12 @@ pub const VISIBILITY_RANGES_STORAGE_BUFFER_COUNT: u32 = 4;
|
|||||||
/// buffer instead (most notably, on WebGL 2).
|
/// buffer instead (most notably, on WebGL 2).
|
||||||
const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64;
|
const VISIBILITY_RANGE_UNIFORM_BUFFER_SIZE: usize = 64;
|
||||||
|
|
||||||
/// A plugin that enables [`VisibilityRange`]s, which allow entities to be
|
/// A plugin that enables [`RenderVisibilityRanges`]s, which allow entities to be
|
||||||
/// hidden or shown based on distance to the camera.
|
/// hidden or shown based on distance to the camera.
|
||||||
pub struct VisibilityRangePlugin;
|
pub struct RenderVisibilityRangePlugin;
|
||||||
|
|
||||||
impl Plugin for VisibilityRangePlugin {
|
impl Plugin for RenderVisibilityRangePlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.register_type::<VisibilityRange>()
|
|
||||||
.init_resource::<VisibleEntityRanges>()
|
|
||||||
.add_systems(
|
|
||||||
PostUpdate,
|
|
||||||
check_visibility_ranges
|
|
||||||
.in_set(VisibilitySystems::CheckVisibility)
|
|
||||||
.before(check_visibility),
|
|
||||||
);
|
|
||||||
|
|
||||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -77,137 +57,6 @@ impl Plugin for VisibilityRangePlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies the range of distances that this entity must be from the camera in
|
|
||||||
/// order to be rendered.
|
|
||||||
///
|
|
||||||
/// This is also known as *hierarchical level of detail* or *HLOD*.
|
|
||||||
///
|
|
||||||
/// Use this component when you want to render a high-polygon mesh when the
|
|
||||||
/// camera is close and a lower-polygon mesh when the camera is far away. This
|
|
||||||
/// is a common technique for improving performance, because fine details are
|
|
||||||
/// hard to see in a mesh at a distance. To avoid an artifact known as *popping*
|
|
||||||
/// between levels, each level has a *margin*, within which the object
|
|
||||||
/// transitions gradually from invisible to visible using a dithering effect.
|
|
||||||
///
|
|
||||||
/// You can also use this feature to replace multiple meshes with a single mesh
|
|
||||||
/// when the camera is distant. This is the reason for the term "*hierarchical*
|
|
||||||
/// level of detail". Reducing the number of meshes can be useful for reducing
|
|
||||||
/// drawcall count. Note that you must place the [`VisibilityRange`] component
|
|
||||||
/// on each entity you want to be part of a LOD group, as [`VisibilityRange`]
|
|
||||||
/// isn't automatically propagated down to children.
|
|
||||||
///
|
|
||||||
/// A typical use of this feature might look like this:
|
|
||||||
///
|
|
||||||
/// | Entity | `start_margin` | `end_margin` |
|
|
||||||
/// |-------------------------|----------------|--------------|
|
|
||||||
/// | Root | N/A | N/A |
|
|
||||||
/// | ├─ High-poly mesh | [0, 0) | [20, 25) |
|
|
||||||
/// | ├─ Low-poly mesh | [20, 25) | [70, 75) |
|
|
||||||
/// | └─ Billboard *imposter* | [70, 75) | [150, 160) |
|
|
||||||
///
|
|
||||||
/// With this setup, the user will see a high-poly mesh when the camera is
|
|
||||||
/// closer than 20 units. As the camera zooms out, between 20 units to 25 units,
|
|
||||||
/// the high-poly mesh will gradually fade to a low-poly mesh. When the camera
|
|
||||||
/// is 70 to 75 units away, the low-poly mesh will fade to a single textured
|
|
||||||
/// quad. And between 150 and 160 units, the object fades away entirely. Note
|
|
||||||
/// that the `end_margin` of a higher LOD is always identical to the
|
|
||||||
/// `start_margin` of the next lower LOD; this is important for the crossfade
|
|
||||||
/// effect to function properly.
|
|
||||||
#[derive(Component, Clone, PartialEq, Default, Reflect)]
|
|
||||||
#[reflect(Component, PartialEq, Hash, Clone)]
|
|
||||||
pub struct VisibilityRange {
|
|
||||||
/// The range of distances, in world units, between which this entity will
|
|
||||||
/// smoothly fade into view as the camera zooms out.
|
|
||||||
///
|
|
||||||
/// If the start and end of this range are identical, the transition will be
|
|
||||||
/// abrupt, with no crossfading.
|
|
||||||
///
|
|
||||||
/// `start_margin.end` must be less than or equal to `end_margin.start`.
|
|
||||||
pub start_margin: Range<f32>,
|
|
||||||
|
|
||||||
/// The range of distances, in world units, between which this entity will
|
|
||||||
/// smoothly fade out of view as the camera zooms out.
|
|
||||||
///
|
|
||||||
/// If the start and end of this range are identical, the transition will be
|
|
||||||
/// abrupt, with no crossfading.
|
|
||||||
///
|
|
||||||
/// `end_margin.start` must be greater than or equal to `start_margin.end`.
|
|
||||||
pub end_margin: Range<f32>,
|
|
||||||
|
|
||||||
/// If set to true, Bevy will use the center of the axis-aligned bounding
|
|
||||||
/// box ([`Aabb`]) as the position of the mesh for the purposes of
|
|
||||||
/// visibility range computation.
|
|
||||||
///
|
|
||||||
/// Otherwise, if this field is set to false, Bevy will use the origin of
|
|
||||||
/// the mesh as the mesh's position.
|
|
||||||
///
|
|
||||||
/// Usually you will want to leave this set to false, because different LODs
|
|
||||||
/// may have different AABBs, and smooth crossfades between LOD levels
|
|
||||||
/// require that all LODs of a mesh be at *precisely* the same position. If
|
|
||||||
/// you aren't using crossfading, however, and your meshes aren't centered
|
|
||||||
/// around their origins, then this flag may be useful.
|
|
||||||
pub use_aabb: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for VisibilityRange {}
|
|
||||||
|
|
||||||
impl Hash for VisibilityRange {
|
|
||||||
fn hash<H>(&self, state: &mut H)
|
|
||||||
where
|
|
||||||
H: Hasher,
|
|
||||||
{
|
|
||||||
FloatOrd(self.start_margin.start).hash(state);
|
|
||||||
FloatOrd(self.start_margin.end).hash(state);
|
|
||||||
FloatOrd(self.end_margin.start).hash(state);
|
|
||||||
FloatOrd(self.end_margin.end).hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisibilityRange {
|
|
||||||
/// Creates a new *abrupt* visibility range, with no crossfade.
|
|
||||||
///
|
|
||||||
/// There will be no crossfade; the object will immediately vanish if the
|
|
||||||
/// camera is closer than `start` units or farther than `end` units from the
|
|
||||||
/// model.
|
|
||||||
///
|
|
||||||
/// The `start` value must be less than or equal to the `end` value.
|
|
||||||
#[inline]
|
|
||||||
pub fn abrupt(start: f32, end: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
start_margin: start..start,
|
|
||||||
end_margin: end..end,
|
|
||||||
use_aabb: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if both the start and end transitions for this range are
|
|
||||||
/// abrupt: that is, there is no crossfading.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_abrupt(&self) -> bool {
|
|
||||||
self.start_margin.start == self.start_margin.end
|
|
||||||
&& self.end_margin.start == self.end_margin.end
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the object will be visible at all, given a camera
|
|
||||||
/// `camera_distance` units away.
|
|
||||||
///
|
|
||||||
/// Any amount of visibility, even with the heaviest dithering applied, is
|
|
||||||
/// considered visible according to this check.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_visible_at_all(&self, camera_distance: f32) -> bool {
|
|
||||||
camera_distance >= self.start_margin.start && camera_distance < self.end_margin.end
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the object is completely invisible, given a camera
|
|
||||||
/// `camera_distance` units away.
|
|
||||||
///
|
|
||||||
/// This is equivalent to `!VisibilityRange::is_visible_at_all()`.
|
|
||||||
#[inline]
|
|
||||||
pub fn is_culled(&self, camera_distance: f32) -> bool {
|
|
||||||
!self.is_visible_at_all(camera_distance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores information related to [`VisibilityRange`]s in the render world.
|
/// Stores information related to [`VisibilityRange`]s in the render world.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct RenderVisibilityRanges {
|
pub struct RenderVisibilityRanges {
|
||||||
@ -313,128 +162,6 @@ impl RenderVisibilityRanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores which entities are in within the [`VisibilityRange`]s of views.
|
|
||||||
///
|
|
||||||
/// This doesn't store the results of frustum or occlusion culling; use
|
|
||||||
/// [`super::ViewVisibility`] for that. Thus entities in this list may not
|
|
||||||
/// actually be visible.
|
|
||||||
///
|
|
||||||
/// For efficiency, these tables only store entities that have
|
|
||||||
/// [`VisibilityRange`] components. Entities without such a component won't be
|
|
||||||
/// in these tables at all.
|
|
||||||
///
|
|
||||||
/// The table is indexed by entity and stores a 32-bit bitmask with one bit for
|
|
||||||
/// each camera, where a 0 bit corresponds to "out of range" and a 1 bit
|
|
||||||
/// corresponds to "in range". Hence it's limited to storing information for 32
|
|
||||||
/// views.
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
pub struct VisibleEntityRanges {
|
|
||||||
/// Stores which bit index each view corresponds to.
|
|
||||||
views: EntityHashMap<u8>,
|
|
||||||
|
|
||||||
/// Stores a bitmask in which each view has a single bit.
|
|
||||||
///
|
|
||||||
/// A 0 bit for a view corresponds to "out of range"; a 1 bit corresponds to
|
|
||||||
/// "in range".
|
|
||||||
entities: EntityHashMap<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VisibleEntityRanges {
|
|
||||||
/// Clears out the [`VisibleEntityRanges`] in preparation for a new frame.
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.views.clear();
|
|
||||||
self.entities.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the entity is in range of the given camera.
|
|
||||||
///
|
|
||||||
/// This only checks [`VisibilityRange`]s and doesn't perform any frustum or
|
|
||||||
/// occlusion culling. Thus the entity might not *actually* be visible.
|
|
||||||
///
|
|
||||||
/// The entity is assumed to have a [`VisibilityRange`] component. If the
|
|
||||||
/// entity doesn't have that component, this method will return false.
|
|
||||||
#[inline]
|
|
||||||
pub fn entity_is_in_range_of_view(&self, entity: Entity, view: Entity) -> bool {
|
|
||||||
let Some(visibility_bitmask) = self.entities.get(&entity) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let Some(view_index) = self.views.get(&view) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
(visibility_bitmask & (1 << view_index)) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the entity is in range of any view.
|
|
||||||
///
|
|
||||||
/// This only checks [`VisibilityRange`]s and doesn't perform any frustum or
|
|
||||||
/// occlusion culling. Thus the entity might not *actually* be visible.
|
|
||||||
///
|
|
||||||
/// The entity is assumed to have a [`VisibilityRange`] component. If the
|
|
||||||
/// entity doesn't have that component, this method will return false.
|
|
||||||
#[inline]
|
|
||||||
pub fn entity_is_in_range_of_any_view(&self, entity: Entity) -> bool {
|
|
||||||
self.entities.contains_key(&entity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks all entities against all views in order to determine which entities
|
|
||||||
/// with [`VisibilityRange`]s are potentially visible.
|
|
||||||
///
|
|
||||||
/// This only checks distance from the camera and doesn't frustum or occlusion
|
|
||||||
/// cull.
|
|
||||||
pub fn check_visibility_ranges(
|
|
||||||
mut visible_entity_ranges: ResMut<VisibleEntityRanges>,
|
|
||||||
view_query: Query<(Entity, &GlobalTransform), With<Camera>>,
|
|
||||||
mut par_local: Local<Parallel<Vec<(Entity, u32)>>>,
|
|
||||||
entity_query: Query<(Entity, &GlobalTransform, Option<&Aabb>, &VisibilityRange)>,
|
|
||||||
) {
|
|
||||||
visible_entity_ranges.clear();
|
|
||||||
|
|
||||||
// Early out if the visibility range feature isn't in use.
|
|
||||||
if entity_query.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign an index to each view.
|
|
||||||
let mut views = vec![];
|
|
||||||
for (view, view_transform) in view_query.iter().take(32) {
|
|
||||||
let view_index = views.len() as u8;
|
|
||||||
visible_entity_ranges.views.insert(view, view_index);
|
|
||||||
views.push((view, view_transform.translation_vec3a()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each entity/view pair. Only consider entities with
|
|
||||||
// [`VisibilityRange`] components.
|
|
||||||
entity_query.par_iter().for_each(
|
|
||||||
|(entity, entity_transform, maybe_model_aabb, visibility_range)| {
|
|
||||||
let mut visibility = 0;
|
|
||||||
for (view_index, &(_, view_position)) in views.iter().enumerate() {
|
|
||||||
// If instructed to use the AABB and the model has one, use its
|
|
||||||
// center as the model position. Otherwise, use the model's
|
|
||||||
// translation.
|
|
||||||
let model_position = match (visibility_range.use_aabb, maybe_model_aabb) {
|
|
||||||
(true, Some(model_aabb)) => entity_transform
|
|
||||||
.affine()
|
|
||||||
.transform_point3a(model_aabb.center),
|
|
||||||
_ => entity_transform.translation_vec3a(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if visibility_range.is_visible_at_all((view_position - model_position).length()) {
|
|
||||||
visibility |= 1 << view_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invisible entities have no entry at all in the hash map. This speeds
|
|
||||||
// up checks slightly in this common case.
|
|
||||||
if visibility != 0 {
|
|
||||||
par_local.borrow_local_mut().push((entity, visibility));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
visible_entity_ranges.entities.extend(par_local.drain());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts all [`VisibilityRange`] components from the main world to the
|
/// Extracts all [`VisibilityRange`] components from the main world to the
|
||||||
/// render world and inserts them into [`RenderVisibilityRanges`].
|
/// render world and inserts them into [`RenderVisibilityRanges`].
|
||||||
pub fn extract_visibility_ranges(
|
pub fn extract_visibility_ranges(
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use super::ExtractedWindows;
|
use super::ExtractedWindows;
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{ManualTextureViewHandle, ManualTextureViews, NormalizedRenderTarget, RenderTarget},
|
camera::{NormalizedRenderTarget, ToNormalizedRenderTarget as _},
|
||||||
gpu_readback,
|
gpu_readback,
|
||||||
prelude::Shader,
|
prelude::Shader,
|
||||||
render_asset::{RenderAssetUsages, RenderAssets},
|
render_asset::{RenderAssetUsages, RenderAssets},
|
||||||
@ -11,13 +11,14 @@ use crate::{
|
|||||||
SpecializedRenderPipelines, Texture, TextureUsages, TextureView, VertexState,
|
SpecializedRenderPipelines, Texture, TextureUsages, TextureView, VertexState,
|
||||||
},
|
},
|
||||||
renderer::RenderDevice,
|
renderer::RenderDevice,
|
||||||
texture::{GpuImage, OutputColorAttachment},
|
texture::{GpuImage, ManualTextureViews, OutputColorAttachment},
|
||||||
view::{prepare_view_attachments, prepare_view_targets, ViewTargetAttachments, WindowSurfaces},
|
view::{prepare_view_attachments, prepare_view_targets, ViewTargetAttachments, WindowSurfaces},
|
||||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems,
|
ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
use alloc::{borrow::Cow, sync::Arc};
|
use alloc::{borrow::Cow, sync::Arc};
|
||||||
use bevy_app::{First, Plugin, Update};
|
use bevy_app::{First, Plugin, Update};
|
||||||
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||||
|
use bevy_camera::{ManualTextureViewHandle, RenderTarget};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::EntityHashMap, event::event_update_system, prelude::*, system::SystemState,
|
entity::EntityHashMap, event::event_update_system, prelude::*, system::SystemState,
|
||||||
|
|||||||
@ -51,8 +51,8 @@ use bevy_image::{prelude::*, TextureAtlasPlugin};
|
|||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
batching::sort_binned_render_phase,
|
batching::sort_binned_render_phase,
|
||||||
load_shader_library,
|
load_shader_library,
|
||||||
mesh::{Mesh, Mesh2d, MeshAabb},
|
mesh::{Mesh, Mesh2d},
|
||||||
primitives::Aabb,
|
primitives::{Aabb, MeshAabb},
|
||||||
render_phase::AddRenderCommand,
|
render_phase::AddRenderCommand,
|
||||||
render_resource::SpecializedRenderPipelines,
|
render_resource::SpecializedRenderPipelines,
|
||||||
view::{NoFrustumCulling, VisibilitySystems},
|
view::{NoFrustumCulling, VisibilitySystems},
|
||||||
|
|||||||
@ -12,7 +12,11 @@ use bevy_input::{mouse::MouseButton, touch::Touches, ButtonInput};
|
|||||||
use bevy_math::Vec2;
|
use bevy_math::Vec2;
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||||
use bevy_render::{camera::NormalizedRenderTarget, prelude::Camera, view::InheritedVisibility};
|
use bevy_render::{
|
||||||
|
camera::{NormalizedRenderTarget, ToNormalizedRenderTarget as _},
|
||||||
|
prelude::Camera,
|
||||||
|
view::InheritedVisibility,
|
||||||
|
};
|
||||||
use bevy_window::{PrimaryWindow, Window};
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|||||||
@ -368,7 +368,7 @@ mod tests {
|
|||||||
use bevy_image::Image;
|
use bevy_image::Image;
|
||||||
use bevy_math::{Rect, UVec2, Vec2};
|
use bevy_math::{Rect, UVec2, Vec2};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_render::{camera::ManualTextureViews, prelude::Camera};
|
use bevy_render::{prelude::Camera, texture::ManualTextureViews};
|
||||||
use bevy_transform::systems::mark_dirty_trees;
|
use bevy_transform::systems::mark_dirty_trees;
|
||||||
use bevy_transform::systems::{propagate_parent_transforms, sync_simple_transforms};
|
use bevy_transform::systems::{propagate_parent_transforms, sync_simple_transforms};
|
||||||
use bevy_utils::prelude::default;
|
use bevy_utils::prelude::default;
|
||||||
|
|||||||
@ -221,8 +221,8 @@ mod tests {
|
|||||||
use bevy_image::Image;
|
use bevy_image::Image;
|
||||||
use bevy_math::UVec2;
|
use bevy_math::UVec2;
|
||||||
use bevy_render::camera::Camera;
|
use bevy_render::camera::Camera;
|
||||||
use bevy_render::camera::ManualTextureViews;
|
|
||||||
use bevy_render::camera::RenderTarget;
|
use bevy_render::camera::RenderTarget;
|
||||||
|
use bevy_render::texture::ManualTextureViews;
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
use bevy_window::PrimaryWindow;
|
use bevy_window::PrimaryWindow;
|
||||||
use bevy_window::Window;
|
use bevy_window::Window;
|
||||||
|
|||||||
@ -16,7 +16,6 @@ use bevy_ecs::{
|
|||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault as _;
|
||||||
use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
|
use bevy_math::{vec2, Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
|
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
|
||||||
use bevy_render::RenderApp;
|
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
render_resource::{binding_types::uniform_buffer, *},
|
render_resource::{binding_types::uniform_buffer, *},
|
||||||
@ -24,6 +23,7 @@ use bevy_render::{
|
|||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{RenderApp, RenderStartup};
|
||||||
use bevy_ui::{
|
use bevy_ui::{
|
||||||
BoxShadow, CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius,
|
BoxShadow, CalculatedClip, ComputedNode, ComputedNodeTarget, ResolvedBorderRadius,
|
||||||
UiGlobalTransform, Val,
|
UiGlobalTransform, Val,
|
||||||
@ -48,6 +48,7 @@ impl Plugin for BoxShadowPlugin {
|
|||||||
.init_resource::<ExtractedBoxShadows>()
|
.init_resource::<ExtractedBoxShadows>()
|
||||||
.init_resource::<BoxShadowMeta>()
|
.init_resource::<BoxShadowMeta>()
|
||||||
.init_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>()
|
.init_resource::<SpecializedRenderPipelines<BoxShadowPipeline>>()
|
||||||
|
.add_systems(RenderStartup, init_box_shadow_pipeline)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
extract_shadows.in_set(RenderUiSystems::ExtractBoxShadows),
|
extract_shadows.in_set(RenderUiSystems::ExtractBoxShadows),
|
||||||
@ -61,12 +62,6 @@ impl Plugin for BoxShadowPlugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
||||||
render_app.init_resource::<BoxShadowPipeline>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -111,23 +106,23 @@ pub struct BoxShadowPipeline {
|
|||||||
pub shader: Handle<Shader>,
|
pub shader: Handle<Shader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for BoxShadowPipeline {
|
pub fn init_box_shadow_pipeline(
|
||||||
fn from_world(world: &mut World) -> Self {
|
mut commands: Commands,
|
||||||
let render_device = world.resource::<RenderDevice>();
|
render_device: Res<RenderDevice>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
|
"box_shadow_view_layout",
|
||||||
|
&BindGroupLayoutEntries::single(
|
||||||
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
uniform_buffer::<ViewUniform>(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(
|
commands.insert_resource(BoxShadowPipeline {
|
||||||
"box_shadow_view_layout",
|
view_layout,
|
||||||
&BindGroupLayoutEntries::single(
|
shader: load_embedded_asset!(asset_server.as_ref(), "box_shadow.wgsl"),
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
});
|
||||||
uniform_buffer::<ViewUniform>(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
BoxShadowPipeline {
|
|
||||||
view_layout,
|
|
||||||
shader: load_embedded_asset!(world, "box_shadow.wgsl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
|||||||
@ -21,7 +21,6 @@ use bevy_math::{
|
|||||||
FloatOrd, Rect, Vec2,
|
FloatOrd, Rect, Vec2,
|
||||||
};
|
};
|
||||||
use bevy_math::{Affine2, Vec2Swizzles};
|
use bevy_math::{Affine2, Vec2Swizzles};
|
||||||
use bevy_render::sync_world::MainEntity;
|
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
render_resource::{binding_types::uniform_buffer, *},
|
render_resource::{binding_types::uniform_buffer, *},
|
||||||
@ -30,6 +29,7 @@ use bevy_render::{
|
|||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{sync_world::MainEntity, RenderStartup};
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_ui::{
|
use bevy_ui::{
|
||||||
BackgroundGradient, BorderGradient, ColorStop, ConicGradient, Gradient,
|
BackgroundGradient, BorderGradient, ColorStop, ConicGradient, Gradient,
|
||||||
@ -51,6 +51,7 @@ impl Plugin for GradientPlugin {
|
|||||||
.init_resource::<ExtractedColorStops>()
|
.init_resource::<ExtractedColorStops>()
|
||||||
.init_resource::<GradientMeta>()
|
.init_resource::<GradientMeta>()
|
||||||
.init_resource::<SpecializedRenderPipelines<GradientPipeline>>()
|
.init_resource::<SpecializedRenderPipelines<GradientPipeline>>()
|
||||||
|
.add_systems(RenderStartup, init_gradient_pipeline)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
extract_gradients
|
extract_gradients
|
||||||
@ -66,12 +67,6 @@ impl Plugin for GradientPlugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
||||||
render_app.init_resource::<GradientPipeline>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@ -102,23 +97,23 @@ pub struct GradientPipeline {
|
|||||||
pub shader: Handle<Shader>,
|
pub shader: Handle<Shader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for GradientPipeline {
|
pub fn init_gradient_pipeline(
|
||||||
fn from_world(world: &mut World) -> Self {
|
mut commands: Commands,
|
||||||
let render_device = world.resource::<RenderDevice>();
|
render_device: Res<RenderDevice>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
|
"ui_gradient_view_layout",
|
||||||
|
&BindGroupLayoutEntries::single(
|
||||||
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
uniform_buffer::<ViewUniform>(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(
|
commands.insert_resource(GradientPipeline {
|
||||||
"ui_gradient_view_layout",
|
view_layout,
|
||||||
&BindGroupLayoutEntries::single(
|
shader: load_embedded_asset!(asset_server.as_ref(), "gradient.wgsl"),
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
});
|
||||||
uniform_buffer::<ViewUniform>(true),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
GradientPipeline {
|
|
||||||
view_layout,
|
|
||||||
shader: load_embedded_asset!(world, "gradient.wgsl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_gradient_line_length(angle: f32, size: Vec2) -> f32 {
|
pub fn compute_gradient_line_length(angle: f32, size: Vec2) -> f32 {
|
||||||
|
|||||||
@ -36,7 +36,6 @@ use bevy_ecs::prelude::*;
|
|||||||
use bevy_ecs::system::SystemParam;
|
use bevy_ecs::system::SystemParam;
|
||||||
use bevy_image::prelude::*;
|
use bevy_image::prelude::*;
|
||||||
use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2};
|
use bevy_math::{Affine2, FloatOrd, Mat4, Rect, UVec4, Vec2};
|
||||||
use bevy_render::load_shader_library;
|
|
||||||
use bevy_render::render_graph::{NodeRunError, RenderGraphContext};
|
use bevy_render::render_graph::{NodeRunError, RenderGraphContext};
|
||||||
use bevy_render::render_phase::ViewSortedRenderPhases;
|
use bevy_render::render_phase::ViewSortedRenderPhases;
|
||||||
use bevy_render::renderer::RenderContext;
|
use bevy_render::renderer::RenderContext;
|
||||||
@ -53,6 +52,7 @@ use bevy_render::{
|
|||||||
view::{ExtractedView, ViewUniforms},
|
view::{ExtractedView, ViewUniforms},
|
||||||
Extract, RenderApp, RenderSystems,
|
Extract, RenderApp, RenderSystems,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{load_shader_library, RenderStartup};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
render_phase::{PhaseItem, PhaseItemExtraIndex},
|
||||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||||
@ -243,6 +243,7 @@ impl Plugin for UiRenderPlugin {
|
|||||||
)
|
)
|
||||||
.chain(),
|
.chain(),
|
||||||
)
|
)
|
||||||
|
.add_systems(RenderStartup, init_ui_pipeline)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
(
|
(
|
||||||
@ -292,14 +293,6 @@ impl Plugin for UiRenderPlugin {
|
|||||||
app.add_plugins(GradientPlugin);
|
app.add_plugins(GradientPlugin);
|
||||||
app.add_plugins(BoxShadowPlugin);
|
app.add_plugins(BoxShadowPlugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
|
||||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
render_app.init_resource::<UiPipeline>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {
|
fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use bevy_asset::{load_embedded_asset, Handle};
|
use bevy_asset::{load_embedded_asset, AssetServer, Handle};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault as _;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
@ -18,35 +18,35 @@ pub struct UiPipeline {
|
|||||||
pub shader: Handle<Shader>,
|
pub shader: Handle<Shader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for UiPipeline {
|
pub fn init_ui_pipeline(
|
||||||
fn from_world(world: &mut World) -> Self {
|
mut commands: Commands,
|
||||||
let render_device = world.resource::<RenderDevice>();
|
render_device: Res<RenderDevice>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
|
"ui_view_layout",
|
||||||
|
&BindGroupLayoutEntries::single(
|
||||||
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
uniform_buffer::<ViewUniform>(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(
|
let image_layout = render_device.create_bind_group_layout(
|
||||||
"ui_view_layout",
|
"ui_image_layout",
|
||||||
&BindGroupLayoutEntries::single(
|
&BindGroupLayoutEntries::sequential(
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
ShaderStages::FRAGMENT,
|
||||||
uniform_buffer::<ViewUniform>(true),
|
(
|
||||||
|
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||||
|
sampler(SamplerBindingType::Filtering),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let image_layout = render_device.create_bind_group_layout(
|
commands.insert_resource(UiPipeline {
|
||||||
"ui_image_layout",
|
view_layout,
|
||||||
&BindGroupLayoutEntries::sequential(
|
image_layout,
|
||||||
ShaderStages::FRAGMENT,
|
shader: load_embedded_asset!(asset_server.as_ref(), "ui.wgsl"),
|
||||||
(
|
});
|
||||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
|
||||||
sampler(SamplerBindingType::Filtering),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
UiPipeline {
|
|
||||||
view_layout,
|
|
||||||
image_layout,
|
|
||||||
shader: load_embedded_asset!(world, "ui.wgsl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
|||||||
@ -8,11 +8,9 @@ use bevy_ecs::{
|
|||||||
lifetimeless::{Read, SRes},
|
lifetimeless::{Read, SRes},
|
||||||
*,
|
*,
|
||||||
},
|
},
|
||||||
world::{FromWorld, World},
|
|
||||||
};
|
};
|
||||||
use bevy_image::BevyDefault as _;
|
use bevy_image::BevyDefault as _;
|
||||||
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_render::RenderApp;
|
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
globals::{GlobalsBuffer, GlobalsUniform},
|
globals::{GlobalsBuffer, GlobalsUniform},
|
||||||
load_shader_library,
|
load_shader_library,
|
||||||
@ -24,6 +22,7 @@ use bevy_render::{
|
|||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{RenderApp, RenderStartup};
|
||||||
use bevy_sprite::BorderRect;
|
use bevy_sprite::BorderRect;
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
@ -61,6 +60,7 @@ where
|
|||||||
.init_resource::<ExtractedUiMaterialNodes<M>>()
|
.init_resource::<ExtractedUiMaterialNodes<M>>()
|
||||||
.init_resource::<UiMaterialMeta<M>>()
|
.init_resource::<UiMaterialMeta<M>>()
|
||||||
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
|
.init_resource::<SpecializedRenderPipelines<UiMaterialPipeline<M>>>()
|
||||||
|
.add_systems(RenderStartup, init_ui_material_pipeline::<M>)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
extract_ui_material_nodes::<M>.in_set(RenderUiSystems::ExtractBackgrounds),
|
extract_ui_material_nodes::<M>.in_set(RenderUiSystems::ExtractBackgrounds),
|
||||||
@ -74,12 +74,6 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
||||||
render_app.init_resource::<UiMaterialPipeline<M>>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
@ -185,41 +179,41 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: UiMaterial> FromWorld for UiMaterialPipeline<M> {
|
pub fn init_ui_material_pipeline<M: UiMaterial>(
|
||||||
fn from_world(world: &mut World) -> Self {
|
mut commands: Commands,
|
||||||
let asset_server = world.resource::<AssetServer>();
|
render_device: Res<RenderDevice>,
|
||||||
let render_device = world.resource::<RenderDevice>();
|
asset_server: Res<AssetServer>,
|
||||||
let ui_layout = M::bind_group_layout(render_device);
|
) {
|
||||||
|
let ui_layout = M::bind_group_layout(&render_device);
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
"ui_view_layout",
|
"ui_view_layout",
|
||||||
&BindGroupLayoutEntries::sequential(
|
&BindGroupLayoutEntries::sequential(
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
(
|
(
|
||||||
uniform_buffer::<ViewUniform>(true),
|
uniform_buffer::<ViewUniform>(true),
|
||||||
uniform_buffer::<GlobalsUniform>(false),
|
uniform_buffer::<GlobalsUniform>(false),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let load_default = || load_embedded_asset!(asset_server, "ui_material.wgsl");
|
let load_default = || load_embedded_asset!(asset_server.as_ref(), "ui_material.wgsl");
|
||||||
|
|
||||||
UiMaterialPipeline {
|
commands.insert_resource(UiMaterialPipeline::<M> {
|
||||||
ui_layout,
|
ui_layout,
|
||||||
view_layout,
|
view_layout,
|
||||||
vertex_shader: match M::vertex_shader() {
|
vertex_shader: match M::vertex_shader() {
|
||||||
ShaderRef::Default => load_default(),
|
ShaderRef::Default => load_default(),
|
||||||
ShaderRef::Handle(handle) => handle,
|
ShaderRef::Handle(handle) => handle,
|
||||||
ShaderRef::Path(path) => asset_server.load(path),
|
ShaderRef::Path(path) => asset_server.load(path),
|
||||||
},
|
},
|
||||||
fragment_shader: match M::fragment_shader() {
|
fragment_shader: match M::fragment_shader() {
|
||||||
ShaderRef::Default => load_default(),
|
ShaderRef::Default => load_default(),
|
||||||
ShaderRef::Handle(handle) => handle,
|
ShaderRef::Handle(handle) => handle,
|
||||||
ShaderRef::Path(path) => asset_server.load(path),
|
ShaderRef::Path(path) => asset_server.load(path),
|
||||||
},
|
},
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
}
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DrawUiMaterial<M> = (
|
pub type DrawUiMaterial<M> = (
|
||||||
|
|||||||
@ -13,7 +13,6 @@ use bevy_ecs::{
|
|||||||
use bevy_image::prelude::*;
|
use bevy_image::prelude::*;
|
||||||
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
use bevy_math::{Affine2, FloatOrd, Rect, Vec2};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::HashMap;
|
||||||
use bevy_render::sync_world::MainEntity;
|
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_phase::*,
|
render_phase::*,
|
||||||
@ -23,6 +22,7 @@ use bevy_render::{
|
|||||||
view::*,
|
view::*,
|
||||||
Extract, ExtractSchedule, Render, RenderSystems,
|
Extract, ExtractSchedule, Render, RenderSystems,
|
||||||
};
|
};
|
||||||
|
use bevy_render::{sync_world::MainEntity, RenderStartup};
|
||||||
use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer};
|
use bevy_sprite::{SliceScaleMode, SpriteAssetEvents, SpriteImageMode, TextureSlicer};
|
||||||
use bevy_ui::widget;
|
use bevy_ui::widget;
|
||||||
use bevy_utils::default;
|
use bevy_utils::default;
|
||||||
@ -42,6 +42,7 @@ impl Plugin for UiTextureSlicerPlugin {
|
|||||||
.init_resource::<UiTextureSliceMeta>()
|
.init_resource::<UiTextureSliceMeta>()
|
||||||
.init_resource::<UiTextureSliceImageBindGroups>()
|
.init_resource::<UiTextureSliceImageBindGroups>()
|
||||||
.init_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>()
|
.init_resource::<SpecializedRenderPipelines<UiTextureSlicePipeline>>()
|
||||||
|
.add_systems(RenderStartup, init_ui_texture_slice_pipeline)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
ExtractSchedule,
|
ExtractSchedule,
|
||||||
extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice),
|
extract_ui_texture_slices.in_set(RenderUiSystems::ExtractTextureSlice),
|
||||||
@ -55,12 +56,6 @@ impl Plugin for UiTextureSlicerPlugin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, app: &mut App) {
|
|
||||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
|
||||||
render_app.init_resource::<UiTextureSlicePipeline>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -110,35 +105,35 @@ pub struct UiTextureSlicePipeline {
|
|||||||
pub shader: Handle<Shader>,
|
pub shader: Handle<Shader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromWorld for UiTextureSlicePipeline {
|
pub fn init_ui_texture_slice_pipeline(
|
||||||
fn from_world(world: &mut World) -> Self {
|
mut commands: Commands,
|
||||||
let render_device = world.resource::<RenderDevice>();
|
render_device: Res<RenderDevice>,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
let view_layout = render_device.create_bind_group_layout(
|
||||||
|
"ui_texture_slice_view_layout",
|
||||||
|
&BindGroupLayoutEntries::single(
|
||||||
|
ShaderStages::VERTEX_FRAGMENT,
|
||||||
|
uniform_buffer::<ViewUniform>(true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let view_layout = render_device.create_bind_group_layout(
|
let image_layout = render_device.create_bind_group_layout(
|
||||||
"ui_texture_slice_view_layout",
|
"ui_texture_slice_image_layout",
|
||||||
&BindGroupLayoutEntries::single(
|
&BindGroupLayoutEntries::sequential(
|
||||||
ShaderStages::VERTEX_FRAGMENT,
|
ShaderStages::FRAGMENT,
|
||||||
uniform_buffer::<ViewUniform>(true),
|
(
|
||||||
|
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||||
|
sampler(SamplerBindingType::Filtering),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
|
||||||
let image_layout = render_device.create_bind_group_layout(
|
commands.insert_resource(UiTextureSlicePipeline {
|
||||||
"ui_texture_slice_image_layout",
|
view_layout,
|
||||||
&BindGroupLayoutEntries::sequential(
|
image_layout,
|
||||||
ShaderStages::FRAGMENT,
|
shader: load_embedded_asset!(asset_server.as_ref(), "ui_texture_slice.wgsl"),
|
||||||
(
|
});
|
||||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
|
||||||
sampler(SamplerBindingType::Filtering),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
UiTextureSlicePipeline {
|
|
||||||
view_layout,
|
|
||||||
image_layout,
|
|
||||||
shader: load_embedded_asset!(world, "ui_texture_slice.wgsl"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: `bevy_render` reorganization
|
||||||
|
pull_requests: [19949]
|
||||||
|
---
|
||||||
|
|
||||||
|
You must now import `ToNormalizedRenderTarget` to use `RenderTarget::normalize`
|
||||||
|
`ManualTextureViews` is now in `bevy_render::texture`
|
||||||
|
Camera and visibility types have been moved to a new crate, `bevy_camera`, but continue to be re-exported by bevy_render.
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Observer Overhaul
|
title: Observer Overhaul
|
||||||
authors: ["@Jondolf", "@alice-i-cecile", "@hukasu]
|
authors: ["@Jondolf", "@alice-i-cecile", "@hukasu", "oscar-benderstone", "Zeophlite"]
|
||||||
pull_requests: [19596, 19663, 19611]
|
pull_requests: [19596, 19663, 19611, 19935]
|
||||||
---
|
---
|
||||||
|
|
||||||
## Rename `Trigger` to `On`
|
## Rename `Trigger` to `On`
|
||||||
@ -45,3 +45,8 @@ This was handy! We've enabled this functionality for all entity-events: simply c
|
|||||||
|
|
||||||
The name of the Observer's system is now accessible through `Observer::system_name`,
|
The name of the Observer's system is now accessible through `Observer::system_name`,
|
||||||
this opens up the possibility for the debug tools to show more meaningful names for observers.
|
this opens up the possibility for the debug tools to show more meaningful names for observers.
|
||||||
|
|
||||||
|
## Use `EventKey` instead of `ComponentId`
|
||||||
|
|
||||||
|
Internally, each `Event` type would generate a `Component` type, allowing us to use the corresponding `ComponentId` to track the event.
|
||||||
|
We have newtyped this to `EventKey` to help separate these concerns.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user