Merge commit '5e3927ba489f597dd189f63286dc7985840db1b5' into dlss3
This commit is contained in:
commit
1b714636ab
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -294,7 +294,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.33.1
|
||||
uses: crate-ci/typos@v1.34.0
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
29
Cargo.toml
29
Cargo.toml
@ -871,6 +871,7 @@ doc-scrape-examples = true
|
||||
name = "Texture Atlas"
|
||||
description = "Generates a texture atlas (sprite sheet) from individual sprites"
|
||||
category = "2D Rendering"
|
||||
# Loading asset folders is not supported in Wasm, but required to create the atlas.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -948,6 +949,7 @@ doc-scrape-examples = true
|
||||
name = "2D Wireframe"
|
||||
description = "Showcases wireframes for 2d meshes"
|
||||
category = "2D Rendering"
|
||||
# PolygonMode::Line wireframes are not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
# 3D Rendering
|
||||
@ -1015,6 +1017,7 @@ doc-scrape-examples = true
|
||||
name = "Anti-aliasing"
|
||||
description = "Compares different anti-aliasing techniques supported by Bevy"
|
||||
category = "3D Rendering"
|
||||
# TAA not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1059,6 +1062,7 @@ doc-scrape-examples = true
|
||||
name = "Auto Exposure"
|
||||
description = "A scene showcasing auto exposure"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders, which are not supported by WebGL.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1126,6 +1130,7 @@ doc-scrape-examples = true
|
||||
name = "Screen Space Ambient Occlusion"
|
||||
description = "A scene showcasing screen space ambient occlusion"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders, which are not supported by WebGL.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1225,6 +1230,7 @@ doc-scrape-examples = true
|
||||
name = "Order Independent Transparency"
|
||||
description = "Demonstrates how to use OIT"
|
||||
category = "3D Rendering"
|
||||
# Not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1324,7 +1330,7 @@ doc-scrape-examples = true
|
||||
name = "Skybox"
|
||||
description = "Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats."
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "solari"
|
||||
@ -1435,6 +1441,7 @@ doc-scrape-examples = true
|
||||
name = "Wireframe"
|
||||
description = "Showcases wireframe rendering"
|
||||
category = "3D Rendering"
|
||||
# Not supported on WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1446,6 +1453,8 @@ doc-scrape-examples = true
|
||||
name = "Irradiance Volumes"
|
||||
description = "Demonstrates irradiance volumes"
|
||||
category = "3D Rendering"
|
||||
# On WebGL and WebGPU, the number of texture bindings is too low
|
||||
# See <https://github.com/bevyengine/bevy/issues/11885>
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1458,6 +1467,7 @@ required-features = ["meshlet"]
|
||||
name = "Meshlet"
|
||||
description = "Meshlet rendering for dense high-poly scenes (experimental)"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders and WGPU extensions, not supported by WebGL nor WebGPU.
|
||||
wasm = false
|
||||
setup = [
|
||||
[
|
||||
@ -1493,7 +1503,7 @@ doc-scrape-examples = true
|
||||
name = "Lightmaps"
|
||||
description = "Rendering a scene with baked lightmaps"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "no_prepass"
|
||||
@ -1646,6 +1656,7 @@ doc-scrape-examples = true
|
||||
name = "Custom Loop"
|
||||
description = "Demonstrates how to create a custom runner (to update an app manually)"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1657,6 +1668,7 @@ doc-scrape-examples = true
|
||||
name = "Drag and Drop"
|
||||
description = "An example that shows how to handle drag and drop in an app"
|
||||
category = "Application"
|
||||
# Browser drag and drop is not supported
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1668,6 +1680,7 @@ doc-scrape-examples = true
|
||||
name = "Empty"
|
||||
description = "An empty application (does nothing)"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1691,6 +1704,7 @@ required-features = ["bevy_log"]
|
||||
name = "Headless"
|
||||
description = "An application that runs without default plugins"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1713,6 +1727,8 @@ doc-scrape-examples = true
|
||||
name = "Log layers"
|
||||
description = "Illustrate how to add custom log layers"
|
||||
category = "Application"
|
||||
# Accesses `time`, which is not available on the web
|
||||
# Also doesn't render anything
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1724,6 +1740,7 @@ doc-scrape-examples = true
|
||||
name = "Advanced log layers"
|
||||
description = "Illustrate how to transfer data between log layers and Bevy's ECS"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -4328,6 +4345,14 @@ description = "Demonstrates specular tints and maps"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "test_invalid_skinned_mesh"
|
||||
path = "tests/3d/test_invalid_skinned_mesh.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.test_invalid_skinned_mesh]
|
||||
hidden = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// For 2d replace `bevy_pbr::mesh_functions` with `bevy_sprite::mesh2d_functions`
|
||||
// and `mesh_position_local_to_clip` with `mesh2d_position_local_to_clip`.
|
||||
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
|
||||
|
||||
struct CustomMaterial {
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
|
||||
struct MyExtendedMaterial {
|
||||
quantize_steps: u32,
|
||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
|
||||
// Web examples WebGL2 support: structs must be 16 byte aligned.
|
||||
_webgl2_padding_8b: u32,
|
||||
_webgl2_padding_12b: u32,
|
||||
_webgl2_padding_16b: u32,
|
||||
#endif
|
||||
}
|
||||
|
||||
@group(3) @binding(100)
|
||||
|
||||
@ -10,7 +10,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
let mut events = Events::default();
|
||||
|
||||
for _ in 0..count {
|
||||
events.send(BenchEvent([0u8; SIZE]));
|
||||
events.write(BenchEvent([0u8; SIZE]));
|
||||
}
|
||||
|
||||
Self(events)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod iter;
|
||||
mod send;
|
||||
mod write;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
@ -11,19 +11,19 @@ fn send(c: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_4_events_{count}"), |b| {
|
||||
let mut bench = send::Benchmark::<4>::new(count);
|
||||
let mut bench = write::Benchmark::<4>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_16_events_{count}"), |b| {
|
||||
let mut bench = send::Benchmark::<16>::new(count);
|
||||
let mut bench = write::Benchmark::<16>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_512_events_{count}"), |b| {
|
||||
let mut bench = send::Benchmark::<512>::new(count);
|
||||
let mut bench = write::Benchmark::<512>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
// Force both internal buffers to be allocated.
|
||||
for _ in 0..2 {
|
||||
for _ in 0..count {
|
||||
events.send(BenchEvent([0u8; SIZE]));
|
||||
events.write(BenchEvent([0u8; SIZE]));
|
||||
}
|
||||
events.update();
|
||||
}
|
||||
@ -32,7 +32,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
pub fn run(&mut self) {
|
||||
for _ in 0..self.count {
|
||||
self.events
|
||||
.send(core::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
.write(core::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
}
|
||||
self.events.update();
|
||||
}
|
||||
@ -14,7 +14,6 @@ bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
|
||||
@ -146,10 +146,10 @@
|
||||
* * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below).
|
||||
*
|
||||
* And four presets:
|
||||
* SMAA_PRESET_LOW (%60 of the quality)
|
||||
* SMAA_PRESET_MEDIUM (%80 of the quality)
|
||||
* SMAA_PRESET_HIGH (%95 of the quality)
|
||||
* SMAA_PRESET_ULTRA (%99 of the quality)
|
||||
* SMAA_PRESET_LOW (60% of the quality)
|
||||
* SMAA_PRESET_MEDIUM (80% of the quality)
|
||||
* SMAA_PRESET_HIGH (95% of the quality)
|
||||
* SMAA_PRESET_ULTRA (99% of the quality)
|
||||
*
|
||||
* For example:
|
||||
* #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0)
|
||||
|
||||
@ -1864,7 +1864,7 @@ mod tests {
|
||||
app.update();
|
||||
|
||||
// Sending one event
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
|
||||
let test_events = app.world().resource::<Events<TestEvent>>();
|
||||
assert_eq!(test_events.len(), 1);
|
||||
@ -1872,8 +1872,8 @@ mod tests {
|
||||
app.update();
|
||||
|
||||
// Sending two events on the next frame
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
|
||||
let test_events = app.world().resource::<Events<TestEvent>>();
|
||||
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
|
||||
|
||||
@ -12,7 +12,7 @@ use core::fmt::Debug;
|
||||
#[cfg(feature = "trace")]
|
||||
use tracing::info_span;
|
||||
|
||||
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
|
||||
type ExtractFn = Box<dyn FnMut(&mut World, &mut World) + Send>;
|
||||
|
||||
/// A secondary application with its own [`World`]. These can run independently of each other.
|
||||
///
|
||||
@ -160,7 +160,7 @@ impl SubApp {
|
||||
/// The first argument is the `World` to extract data from, the second argument is the app `World`.
|
||||
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut World, &mut World) + Send + 'static,
|
||||
F: FnMut(&mut World, &mut World) + Send + 'static,
|
||||
{
|
||||
self.extract = Some(Box::new(extract));
|
||||
self
|
||||
@ -177,13 +177,13 @@ impl SubApp {
|
||||
/// ```
|
||||
/// # use bevy_app::SubApp;
|
||||
/// # let mut app = SubApp::new();
|
||||
/// let default_fn = app.take_extract();
|
||||
/// let mut default_fn = app.take_extract();
|
||||
/// app.set_extract(move |main, render| {
|
||||
/// // Do pre-extract custom logic
|
||||
/// // [...]
|
||||
///
|
||||
/// // Call Bevy's default, which executes the Extract phase
|
||||
/// if let Some(f) = default_fn.as_ref() {
|
||||
/// if let Some(f) = default_fn.as_mut() {
|
||||
/// f(main, render);
|
||||
/// }
|
||||
///
|
||||
|
||||
@ -167,7 +167,7 @@ impl AssetServer {
|
||||
fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) {
|
||||
world
|
||||
.resource_mut::<Events<AssetEvent<A>>>()
|
||||
.send(AssetEvent::LoadedWithDependencies { id: id.typed() });
|
||||
.write(AssetEvent::LoadedWithDependencies { id: id.typed() });
|
||||
}
|
||||
fn failed_sender<A: Asset>(
|
||||
world: &mut World,
|
||||
@ -177,7 +177,7 @@ impl AssetServer {
|
||||
) {
|
||||
world
|
||||
.resource_mut::<Events<AssetLoadFailedEvent<A>>>()
|
||||
.send(AssetLoadFailedEvent {
|
||||
.write(AssetLoadFailedEvent {
|
||||
id: id.typed(),
|
||||
path,
|
||||
error,
|
||||
@ -1685,7 +1685,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
|
||||
}
|
||||
|
||||
if !untyped_failures.is_empty() {
|
||||
world.send_event_batch(untyped_failures);
|
||||
world.write_event_batch(untyped_failures);
|
||||
}
|
||||
|
||||
fn queue_ancestors(
|
||||
|
||||
@ -16,7 +16,6 @@ bevy_ecs = { path = "../bevy_ecs", 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_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
|
||||
|
||||
@ -3,6 +3,7 @@ use bevy_asset::{Asset, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::Vec3;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::components::Transform;
|
||||
|
||||
/// The way Bevy manages the sound playback.
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
@ -10,10 +11,10 @@ use bevy_reflect::prelude::*;
|
||||
pub enum PlaybackMode {
|
||||
/// Play the sound once. Do nothing when it ends.
|
||||
///
|
||||
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
||||
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
||||
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
|
||||
/// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
|
||||
///
|
||||
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
||||
/// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
|
||||
/// added again.
|
||||
Once,
|
||||
/// Repeat the sound forever.
|
||||
@ -27,7 +28,7 @@ pub enum PlaybackMode {
|
||||
/// Initial settings to be used when audio starts playing.
|
||||
///
|
||||
/// If you would like to control the audio while it is playing, query for the
|
||||
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink]
|
||||
/// [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink)
|
||||
/// components. Changes to this component will *not* be applied to already-playing audio.
|
||||
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
@ -78,10 +79,10 @@ impl Default for PlaybackSettings {
|
||||
impl PlaybackSettings {
|
||||
/// Will play the associated audio source once.
|
||||
///
|
||||
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
||||
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
||||
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
|
||||
/// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
|
||||
///
|
||||
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
||||
/// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
|
||||
/// added again.
|
||||
pub const ONCE: PlaybackSettings = PlaybackSettings {
|
||||
mode: PlaybackMode::Once,
|
||||
@ -164,14 +165,15 @@ impl PlaybackSettings {
|
||||
|
||||
/// Settings for the listener for spatial audio sources.
|
||||
///
|
||||
/// This must be accompanied by `Transform` and `GlobalTransform`.
|
||||
/// Only one entity with a `SpatialListener` should be present at any given time.
|
||||
/// This is accompanied by [`Transform`] and [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
/// Only one entity with a [`SpatialListener`] should be present at any given time.
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[require(Transform)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
pub struct SpatialListener {
|
||||
/// Left ear position relative to the `GlobalTransform`.
|
||||
/// Left ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
pub left_ear_offset: Vec3,
|
||||
/// Right ear position relative to the `GlobalTransform`.
|
||||
/// Right ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
pub right_ear_offset: Vec3,
|
||||
}
|
||||
|
||||
@ -182,7 +184,7 @@ impl Default for SpatialListener {
|
||||
}
|
||||
|
||||
impl SpatialListener {
|
||||
/// Creates a new `SpatialListener` component.
|
||||
/// Creates a new [`SpatialListener`] component.
|
||||
///
|
||||
/// `gap` is the distance between the left and right "ears" of the listener. Ears are
|
||||
/// positioned on the x axis.
|
||||
@ -203,12 +205,12 @@ impl SpatialListener {
|
||||
pub struct SpatialScale(pub Vec3);
|
||||
|
||||
impl SpatialScale {
|
||||
/// Create a new `SpatialScale` with the same value for all 3 dimensions.
|
||||
/// Create a new [`SpatialScale`] with the same value for all 3 dimensions.
|
||||
pub const fn new(scale: f32) -> Self {
|
||||
Self(Vec3::splat(scale))
|
||||
}
|
||||
|
||||
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0`
|
||||
/// Create a new [`SpatialScale`] with the same value for `x` and `y`, and `0.0`
|
||||
/// for `z`.
|
||||
pub const fn new_2d(scale: f32) -> Self {
|
||||
Self(Vec3::new(scale, scale, 0.0))
|
||||
@ -238,11 +240,11 @@ pub struct DefaultSpatialScale(pub SpatialScale);
|
||||
/// If the handle refers to an unavailable asset (such as if it has not finished loading yet),
|
||||
/// the audio will not begin playing immediately. The audio will play when the asset is ready.
|
||||
///
|
||||
/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be
|
||||
/// When Bevy begins the audio playback, an [`AudioSink`](crate::AudioSink) component will be
|
||||
/// added to the entity. You can use that component to control the audio settings during playback.
|
||||
///
|
||||
/// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the
|
||||
/// `PlaybackSettings` component will *not* affect already-playing audio.
|
||||
/// [`PlaybackSettings`] component will *not* affect already-playing audio.
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component, Clone)]
|
||||
#[require(PlaybackSettings)]
|
||||
|
||||
@ -103,7 +103,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
Entity,
|
||||
&AudioPlayer<Source>,
|
||||
&PlaybackSettings,
|
||||
Option<&GlobalTransform>,
|
||||
&GlobalTransform,
|
||||
),
|
||||
(Without<AudioSink>, Without<SpatialAudioSink>),
|
||||
>,
|
||||
@ -118,7 +118,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
return;
|
||||
};
|
||||
|
||||
for (entity, source_handle, settings, maybe_emitter_transform) in &query_nonplaying {
|
||||
for (entity, source_handle, settings, emitter_transform) in &query_nonplaying {
|
||||
let Some(audio_source) = audio_sources.get(&source_handle.0) else {
|
||||
continue;
|
||||
};
|
||||
@ -136,14 +136,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
|
||||
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
|
||||
|
||||
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
|
||||
(emitter_transform.translation() * scale).into()
|
||||
} else {
|
||||
warn!("Spatial AudioPlayer with no GlobalTransform component. Using zero.");
|
||||
Vec3::ZERO.into()
|
||||
};
|
||||
|
||||
let emitter_translation = (emitter_transform.translation() * scale).into();
|
||||
let sink = match SpatialSink::try_new(
|
||||
stream_handle,
|
||||
emitter_translation,
|
||||
|
||||
@ -24,7 +24,6 @@ bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
|
||||
@ -39,14 +38,12 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
bitflags = "2.3"
|
||||
radsort = "0.1"
|
||||
nonmax = "0.5"
|
||||
smallvec = { version = "1", default-features = false }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
bytemuck = { version = "1" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -18,8 +18,6 @@ bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
|
||||
"bevy_ui_picking_backend",
|
||||
] }
|
||||
|
||||
@ -176,7 +176,7 @@ impl Default for SliderRange {
|
||||
}
|
||||
|
||||
/// Defines the amount by which to increment or decrement the slider value when using keyboard
|
||||
/// shorctuts. Defaults to 1.0.
|
||||
/// shortcuts. Defaults to 1.0.
|
||||
#[derive(Component, Debug, PartialEq, Clone)]
|
||||
#[component(immutable)]
|
||||
pub struct SliderStep(pub f32);
|
||||
|
||||
@ -18,14 +18,12 @@ bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local<u32>) {
|
||||
debug!("Handling event: {:?}", event);
|
||||
match event {
|
||||
CiTestingEvent::AppExit => {
|
||||
world.send_event(AppExit::Success);
|
||||
world.write_event(AppExit::Success);
|
||||
info!("Exiting after {} frames. Test successful!", *current_frame);
|
||||
}
|
||||
CiTestingEvent::ScreenshotAndExit => {
|
||||
@ -53,7 +53,7 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local<u32>) {
|
||||
}
|
||||
// Custom events are forwarded to the world.
|
||||
CiTestingEvent::Custom(event_string) => {
|
||||
world.send_event(CiTestingCustomEvent(event_string));
|
||||
world.write_event(CiTestingCustomEvent(event_string));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,7 +56,6 @@ critical-section = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
|
||||
@ -630,7 +630,7 @@ impl Archetype {
|
||||
#[inline]
|
||||
pub fn len(&self) -> u32 {
|
||||
// No entity may have more than one archetype row, so there are no duplicates,
|
||||
// and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity.
|
||||
// and there may only ever be u32::MAX entities, so the length never exceeds u32's capacity.
|
||||
self.entities.len() as u32
|
||||
}
|
||||
|
||||
|
||||
@ -1584,7 +1584,7 @@ impl<'w> BundleRemover<'w> {
|
||||
// Handle sparse set removes
|
||||
for component_id in self.bundle_info.as_ref().iter_explicit_components() {
|
||||
if self.old_archetype.as_ref().contains(component_id) {
|
||||
world.removed_components.send(component_id, entity);
|
||||
world.removed_components.write(component_id, entity);
|
||||
|
||||
// Make sure to drop components stored in sparse sets.
|
||||
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
|
||||
|
||||
@ -795,47 +795,34 @@ impl<'w> EntityClonerBuilder<'w, OptOut> {
|
||||
/// this behavior.
|
||||
pub fn deny<T: Bundle>(&mut self) -> &mut Self {
|
||||
let bundle_id = self.world.register_bundle::<T>().id();
|
||||
self.deny_by_bundle_id(bundle_id)
|
||||
}
|
||||
|
||||
/// Disallows all components of the bundle ID from being cloned.
|
||||
///
|
||||
/// If component `A` is denied here and component `B` requires `A`, then `A`
|
||||
/// is denied as well. See [`Self::without_required_by_components`] to alter
|
||||
/// this behavior.
|
||||
pub fn deny_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
|
||||
if let Some(bundle) = self.world.bundles().get(bundle_id) {
|
||||
let ids = bundle.explicit_components().iter();
|
||||
for &id in ids {
|
||||
self.filter.filter_deny(id, self.world);
|
||||
}
|
||||
}
|
||||
self
|
||||
self.deny_by_ids(bundle_id)
|
||||
}
|
||||
|
||||
/// Extends the list of components that shouldn't be cloned.
|
||||
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
|
||||
///
|
||||
/// If component `A` is denied here and component `B` requires `A`, then `A`
|
||||
/// is denied as well. See [`Self::without_required_by_components`] to alter
|
||||
/// this behavior.
|
||||
pub fn deny_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
|
||||
for id in ids {
|
||||
self.filter.filter_deny(id, self.world);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the list of components that shouldn't be cloned by type ids.
|
||||
///
|
||||
/// If component `A` is denied here and component `B` requires `A`, then `A`
|
||||
/// is denied as well. See [`Self::without_required_by_components`] to alter
|
||||
/// this behavior.
|
||||
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
|
||||
for type_id in ids {
|
||||
if let Some(id) = self.world.components().get_valid_id(type_id) {
|
||||
self.filter.filter_deny(id, self.world);
|
||||
pub fn deny_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
|
||||
ids.filter_ids(&mut |ids| match ids {
|
||||
FilterableId::Type(type_id) => {
|
||||
if let Some(id) = self.world.components().get_valid_id(type_id) {
|
||||
self.filter.filter_deny(id, self.world);
|
||||
}
|
||||
}
|
||||
}
|
||||
FilterableId::Component(component_id) => {
|
||||
self.filter.filter_deny(component_id, self.world);
|
||||
}
|
||||
FilterableId::Bundle(bundle_id) => {
|
||||
if let Some(bundle) = self.world.bundles().get(bundle_id) {
|
||||
let ids = bundle.explicit_components().iter();
|
||||
for &id in ids {
|
||||
self.filter.filter_deny(id, self.world);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -865,7 +852,7 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
|
||||
/// to alter this behavior.
|
||||
pub fn allow<T: Bundle>(&mut self) -> &mut Self {
|
||||
let bundle_id = self.world.register_bundle::<T>().id();
|
||||
self.allow_by_bundle_id(bundle_id)
|
||||
self.allow_by_ids(bundle_id)
|
||||
}
|
||||
|
||||
/// Adds all components of the bundle to the list of components to clone if
|
||||
@ -876,94 +863,55 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
|
||||
/// to alter this behavior.
|
||||
pub fn allow_if_new<T: Bundle>(&mut self) -> &mut Self {
|
||||
let bundle_id = self.world.register_bundle::<T>().id();
|
||||
self.allow_by_bundle_id_if_new(bundle_id)
|
||||
}
|
||||
|
||||
/// Adds all components of the bundle ID to the list of components to clone.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
|
||||
if let Some(bundle) = self.world.bundles().get(bundle_id) {
|
||||
let ids = bundle.explicit_components().iter();
|
||||
for &id in ids {
|
||||
self.filter
|
||||
.filter_allow(id, self.world, InsertMode::Replace);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds all components of the bundle ID to the list of components to clone
|
||||
/// if the target does not contain them.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_bundle_id_if_new(&mut self, bundle_id: BundleId) -> &mut Self {
|
||||
if let Some(bundle) = self.world.bundles().get(bundle_id) {
|
||||
let ids = bundle.explicit_components().iter();
|
||||
for &id in ids {
|
||||
self.filter.filter_allow(id, self.world, InsertMode::Keep);
|
||||
}
|
||||
}
|
||||
self
|
||||
self.allow_by_ids_if_new(bundle_id)
|
||||
}
|
||||
|
||||
/// Extends the list of components to clone.
|
||||
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
|
||||
for id in ids {
|
||||
self.filter
|
||||
.filter_allow(id, self.world, InsertMode::Replace);
|
||||
}
|
||||
pub fn allow_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
|
||||
self.allow_by_ids_inner(ids, InsertMode::Replace);
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the list of components to clone if the target does not contain them.
|
||||
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_ids_if_new(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
|
||||
for id in ids {
|
||||
self.filter.filter_allow(id, self.world, InsertMode::Keep);
|
||||
}
|
||||
pub fn allow_by_ids_if_new<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
|
||||
self.allow_by_ids_inner(ids, InsertMode::Keep);
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the list of components to clone using [`TypeId`]s.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
|
||||
for type_id in ids {
|
||||
if let Some(id) = self.world.components().get_valid_id(type_id) {
|
||||
fn allow_by_ids_inner<M: Marker>(
|
||||
&mut self,
|
||||
ids: impl FilterableIds<M>,
|
||||
insert_mode: InsertMode,
|
||||
) {
|
||||
ids.filter_ids(&mut |id| match id {
|
||||
FilterableId::Type(type_id) => {
|
||||
if let Some(id) = self.world.components().get_valid_id(type_id) {
|
||||
self.filter.filter_allow(id, self.world, insert_mode);
|
||||
}
|
||||
}
|
||||
FilterableId::Component(component_id) => {
|
||||
self.filter
|
||||
.filter_allow(id, self.world, InsertMode::Replace);
|
||||
.filter_allow(component_id, self.world, insert_mode);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Extends the list of components to clone using [`TypeId`]s if the target
|
||||
/// does not contain them.
|
||||
///
|
||||
/// If component `A` is allowed here and requires component `B`, then `B`
|
||||
/// is allowed as well. See [`Self::without_required_components`]
|
||||
/// to alter this behavior.
|
||||
pub fn allow_by_type_ids_if_new(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
|
||||
for type_id in ids {
|
||||
if let Some(id) = self.world.components().get_valid_id(type_id) {
|
||||
self.filter.filter_allow(id, self.world, InsertMode::Keep);
|
||||
FilterableId::Bundle(bundle_id) => {
|
||||
if let Some(bundle) = self.world.bundles().get(bundle_id) {
|
||||
let ids = bundle.explicit_components().iter();
|
||||
for &id in ids {
|
||||
self.filter.filter_allow(id, self.world, insert_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1309,6 +1257,77 @@ impl Required {
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
use super::*;
|
||||
|
||||
/// Marker trait to allow multiple blanket implementations for [`FilterableIds`].
|
||||
pub trait Marker {}
|
||||
/// Marker struct for [`FilterableIds`] implementation for single-value types.
|
||||
pub struct ScalarType {}
|
||||
impl Marker for ScalarType {}
|
||||
/// Marker struct for [`FilterableIds`] implementation for [`IntoIterator`] types.
|
||||
pub struct VectorType {}
|
||||
impl Marker for VectorType {}
|
||||
|
||||
/// Defines types of ids that [`EntityClonerBuilder`] can filter components by.
|
||||
#[derive(From)]
|
||||
pub enum FilterableId {
|
||||
Type(TypeId),
|
||||
Component(ComponentId),
|
||||
Bundle(BundleId),
|
||||
}
|
||||
|
||||
impl<'a, T> From<&'a T> for FilterableId
|
||||
where
|
||||
T: Into<FilterableId> + Copy,
|
||||
{
|
||||
#[inline]
|
||||
fn from(value: &'a T) -> Self {
|
||||
(*value).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to allow [`EntityClonerBuilder`] filter by any supported id type and their iterators,
|
||||
/// reducing the number of method permutations required for all id types.
|
||||
///
|
||||
/// The supported id types that can be used to filter components are defined by [`FilterableId`], which allows following types: [`TypeId`], [`ComponentId`] and [`BundleId`].
|
||||
///
|
||||
/// `M` is a generic marker to allow multiple blanket implementations of this trait.
|
||||
/// This works because `FilterableId<M1>` is a different trait from `FilterableId<M2>`, so multiple blanket implementations for different `M` are allowed.
|
||||
/// The reason this is required is because supporting `IntoIterator` requires blanket implementation, but that will conflict with implementation for `TypeId`
|
||||
/// since `IntoIterator` can technically be implemented for `TypeId` in the future.
|
||||
/// Functions like `allow_by_ids` rely on type inference to automatically select proper type for `M` at call site.
|
||||
pub trait FilterableIds<M: Marker> {
|
||||
/// Takes in a function that processes all types of [`FilterableId`] one-by-one.
|
||||
fn filter_ids(self, ids: &mut impl FnMut(FilterableId));
|
||||
}
|
||||
|
||||
impl<I, T> FilterableIds<VectorType> for I
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<FilterableId>,
|
||||
{
|
||||
#[inline]
|
||||
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
|
||||
for id in self.into_iter() {
|
||||
ids(id.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FilterableIds<ScalarType> for T
|
||||
where
|
||||
T: Into<FilterableId>,
|
||||
{
|
||||
#[inline]
|
||||
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
|
||||
ids(self.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use private::{FilterableId, FilterableIds, Marker};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -54,8 +54,8 @@ use {
|
||||
/// // run this once per update/frame
|
||||
/// events.update();
|
||||
///
|
||||
/// // somewhere else: send an event
|
||||
/// events.send(MyEvent { value: 1 });
|
||||
/// // somewhere else: write an event
|
||||
/// events.write(MyEvent { value: 1 });
|
||||
///
|
||||
/// // somewhere else: read the events
|
||||
/// for event in cursor.read(&events) {
|
||||
@ -118,22 +118,22 @@ impl<E: BufferedEvent> Events<E> {
|
||||
self.events_a.start_event_count
|
||||
}
|
||||
|
||||
/// "Sends" an `event` by writing it to the current event buffer.
|
||||
/// Writes an `event` to the current event buffer.
|
||||
/// [`EventReader`](super::EventReader)s can then read the event.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`.
|
||||
#[track_caller]
|
||||
pub fn send(&mut self, event: E) -> EventId<E> {
|
||||
self.send_with_caller(event, MaybeLocation::caller())
|
||||
pub fn write(&mut self, event: E) -> EventId<E> {
|
||||
self.write_with_caller(event, MaybeLocation::caller())
|
||||
}
|
||||
|
||||
pub(crate) fn send_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId<E> {
|
||||
pub(crate) fn write_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId<E> {
|
||||
let event_id = EventId {
|
||||
id: self.event_count,
|
||||
caller,
|
||||
_marker: PhantomData,
|
||||
};
|
||||
#[cfg(feature = "detailed_trace")]
|
||||
tracing::trace!("Events::send() -> id: {}", event_id);
|
||||
tracing::trace!("Events::write() -> id: {}", event_id);
|
||||
|
||||
let event_instance = EventInstance { event_id, event };
|
||||
|
||||
@ -143,30 +143,59 @@ impl<E: BufferedEvent> Events<E> {
|
||||
event_id
|
||||
}
|
||||
|
||||
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This is more efficient than sending each event individually.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`.
|
||||
/// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This is more efficient than writing each event individually.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`.
|
||||
#[track_caller]
|
||||
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
|
||||
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
|
||||
let last_count = self.event_count;
|
||||
|
||||
self.extend(events);
|
||||
|
||||
SendBatchIds {
|
||||
WriteBatchIds {
|
||||
last_count,
|
||||
event_count: self.event_count,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the default value of the event. Useful when the event is an empty struct.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`.
|
||||
#[track_caller]
|
||||
pub fn write_default(&mut self) -> EventId<E>
|
||||
where
|
||||
E: Default,
|
||||
{
|
||||
self.write(Default::default())
|
||||
}
|
||||
|
||||
/// "Sends" an `event` by writing it to the current event buffer.
|
||||
/// [`EventReader`](super::EventReader)s can then read the event.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`.
|
||||
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write` instead.")]
|
||||
#[track_caller]
|
||||
pub fn send(&mut self, event: E) -> EventId<E> {
|
||||
self.write(event)
|
||||
}
|
||||
|
||||
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This is more efficient than sending each event individually.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`.
|
||||
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write_batch` instead.")]
|
||||
#[track_caller]
|
||||
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
|
||||
self.write_batch(events)
|
||||
}
|
||||
|
||||
/// Sends the default value of the event. Useful when the event is an empty struct.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`.
|
||||
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write_default` instead.")]
|
||||
#[track_caller]
|
||||
pub fn send_default(&mut self) -> EventId<E>
|
||||
where
|
||||
E: Default,
|
||||
{
|
||||
self.send(Default::default())
|
||||
self.write_default()
|
||||
}
|
||||
|
||||
/// Gets a new [`EventCursor`]. This will include all events already in the event buffers.
|
||||
@ -351,14 +380,18 @@ impl<E: BufferedEvent> DerefMut for EventSequence<E> {
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
|
||||
pub struct SendBatchIds<E> {
|
||||
/// [`Iterator`] over written [`EventIds`](`EventId`) from a batch.
|
||||
pub struct WriteBatchIds<E> {
|
||||
last_count: usize,
|
||||
event_count: usize,
|
||||
_marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: BufferedEvent> Iterator for SendBatchIds<E> {
|
||||
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
|
||||
#[deprecated(since = "0.17.0", note = "Use `WriteBatchIds` instead.")]
|
||||
pub type SendBatchIds<E> = WriteBatchIds<E>;
|
||||
|
||||
impl<E: BufferedEvent> Iterator for WriteBatchIds<E> {
|
||||
type Item = EventId<E>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
@ -378,7 +411,7 @@ impl<E: BufferedEvent> Iterator for SendBatchIds<E> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: BufferedEvent> ExactSizeIterator for SendBatchIds<E> {
|
||||
impl<E: BufferedEvent> ExactSizeIterator for WriteBatchIds<E> {
|
||||
fn len(&self) -> usize {
|
||||
self.event_count.saturating_sub(self.last_count)
|
||||
}
|
||||
@ -400,22 +433,22 @@ mod tests {
|
||||
assert_eq!(test_events.iter_current_update_events().count(), 0);
|
||||
test_events.update();
|
||||
|
||||
// Sending one event
|
||||
test_events.send(TestEvent);
|
||||
// Writing one event
|
||||
test_events.write(TestEvent);
|
||||
|
||||
assert_eq!(test_events.len(), 1);
|
||||
assert_eq!(test_events.iter_current_update_events().count(), 1);
|
||||
test_events.update();
|
||||
|
||||
// Sending two events on the next frame
|
||||
test_events.send(TestEvent);
|
||||
test_events.send(TestEvent);
|
||||
// Writing two events on the next frame
|
||||
test_events.write(TestEvent);
|
||||
test_events.write(TestEvent);
|
||||
|
||||
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
|
||||
assert_eq!(test_events.iter_current_update_events().count(), 2);
|
||||
test_events.update();
|
||||
|
||||
// Sending zero events
|
||||
// Writing zero events
|
||||
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
|
||||
assert_eq!(test_events.iter_current_update_events().count(), 0);
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ use core::marker::PhantomData;
|
||||
/// }
|
||||
///
|
||||
/// for event in events_to_resend {
|
||||
/// events.send(MyEvent);
|
||||
/// events.write(MyEvent);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
|
||||
@ -13,7 +13,8 @@ mod writer;
|
||||
pub(crate) use base::EventInstance;
|
||||
pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey};
|
||||
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
|
||||
pub use collections::{Events, SendBatchIds};
|
||||
#[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")]
|
||||
pub use collections::{Events, SendBatchIds, WriteBatchIds};
|
||||
pub use event_cursor::EventCursor;
|
||||
#[cfg(feature = "multi_threaded")]
|
||||
pub use iterators::EventParIter;
|
||||
@ -68,7 +69,7 @@ mod tests {
|
||||
|
||||
let mut reader_a: EventCursor<TestEvent> = events.get_cursor();
|
||||
|
||||
events.send(event_0);
|
||||
events.write(event_0);
|
||||
|
||||
assert_eq!(
|
||||
get_events(&events, &mut reader_a),
|
||||
@ -94,7 +95,7 @@ mod tests {
|
||||
"second iteration of reader_b created after event results in zero events"
|
||||
);
|
||||
|
||||
events.send(event_1);
|
||||
events.write(event_1);
|
||||
|
||||
let mut reader_c = events.get_cursor();
|
||||
|
||||
@ -119,7 +120,7 @@ mod tests {
|
||||
|
||||
let mut reader_d = events.get_cursor();
|
||||
|
||||
events.send(event_2);
|
||||
events.write(event_2);
|
||||
|
||||
assert_eq!(
|
||||
get_events(&events, &mut reader_a),
|
||||
@ -153,17 +154,17 @@ mod tests {
|
||||
|
||||
assert!(reader.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert_eq!(*reader.read(&events).next().unwrap(), TestEvent { i: 0 });
|
||||
assert_eq!(reader.read(&events).next(), None);
|
||||
|
||||
events.send(TestEvent { i: 1 });
|
||||
events.write(TestEvent { i: 1 });
|
||||
clear_func(&mut events);
|
||||
assert!(reader.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 2 });
|
||||
events.write(TestEvent { i: 2 });
|
||||
events.update();
|
||||
events.send(TestEvent { i: 3 });
|
||||
events.write(TestEvent { i: 3 });
|
||||
|
||||
assert!(reader
|
||||
.read(&events)
|
||||
@ -185,22 +186,22 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_events_send_default() {
|
||||
fn test_events_write_default() {
|
||||
let mut events = Events::<EmptyTestEvent>::default();
|
||||
events.send_default();
|
||||
events.write_default();
|
||||
|
||||
let mut reader = events.get_cursor();
|
||||
assert_eq!(get_events(&events, &mut reader), vec![EmptyTestEvent]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_send_events_ids() {
|
||||
fn test_write_events_ids() {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
let event_0 = TestEvent { i: 0 };
|
||||
let event_1 = TestEvent { i: 1 };
|
||||
let event_2 = TestEvent { i: 2 };
|
||||
|
||||
let event_0_id = events.send(event_0);
|
||||
let event_0_id = events.write(event_0);
|
||||
|
||||
assert_eq!(
|
||||
events.get_event(event_0_id.id),
|
||||
@ -208,7 +209,7 @@ mod tests {
|
||||
"Getting a sent event by ID should return the original event"
|
||||
);
|
||||
|
||||
let mut event_ids = events.send_batch([event_1, event_2]);
|
||||
let mut event_ids = events.write_batch([event_1, event_2]);
|
||||
|
||||
let event_id = event_ids.next().expect("Event 1 must have been sent");
|
||||
|
||||
@ -253,14 +254,14 @@ mod tests {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
let mut reader = events.get_cursor();
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.send(TestEvent { i: 1 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 1 });
|
||||
assert_eq!(reader.read(&events).count(), 2);
|
||||
|
||||
let mut old_events = Vec::from_iter(events.update_drain());
|
||||
assert!(old_events.is_empty());
|
||||
|
||||
events.send(TestEvent { i: 2 });
|
||||
events.write(TestEvent { i: 2 });
|
||||
assert_eq!(reader.read(&events).count(), 1);
|
||||
|
||||
old_events.extend(events.update_drain());
|
||||
@ -278,7 +279,7 @@ mod tests {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
assert!(events.is_empty());
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert!(!events.is_empty());
|
||||
|
||||
events.update();
|
||||
@ -308,12 +309,12 @@ mod tests {
|
||||
let mut cursor = events.get_cursor();
|
||||
assert!(cursor.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
let sent_event = cursor.read(&events).next().unwrap();
|
||||
assert_eq!(sent_event, &TestEvent { i: 0 });
|
||||
assert!(cursor.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 2 });
|
||||
events.write(TestEvent { i: 2 });
|
||||
let sent_event = cursor.read(&events).next().unwrap();
|
||||
assert_eq!(sent_event, &TestEvent { i: 2 });
|
||||
assert!(cursor.read(&events).next().is_none());
|
||||
@ -330,7 +331,7 @@ mod tests {
|
||||
assert!(write_cursor.read_mut(&mut events).next().is_none());
|
||||
assert!(read_cursor.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
let sent_event = write_cursor.read_mut(&mut events).next().unwrap();
|
||||
assert_eq!(sent_event, &mut TestEvent { i: 0 });
|
||||
*sent_event = TestEvent { i: 1 }; // Mutate whole event
|
||||
@ -340,7 +341,7 @@ mod tests {
|
||||
);
|
||||
assert!(read_cursor.read(&events).next().is_none());
|
||||
|
||||
events.send(TestEvent { i: 2 });
|
||||
events.write(TestEvent { i: 2 });
|
||||
let sent_event = write_cursor.read_mut(&mut events).next().unwrap();
|
||||
assert_eq!(sent_event, &mut TestEvent { i: 2 });
|
||||
sent_event.i = 3; // Mutate sub value
|
||||
@ -360,7 +361,7 @@ mod tests {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
let mut reader = events.get_cursor();
|
||||
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert_eq!(reader.len(&events), 1);
|
||||
reader.clear(&events);
|
||||
assert_eq!(reader.len(&events), 0);
|
||||
@ -369,12 +370,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_event_cursor_len_update() {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
let reader = events.get_cursor();
|
||||
assert_eq!(reader.len(&events), 2);
|
||||
events.update();
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert_eq!(reader.len(&events), 3);
|
||||
events.update();
|
||||
assert_eq!(reader.len(&events), 1);
|
||||
@ -385,10 +386,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_event_cursor_len_current() {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
let reader = events.get_cursor_current();
|
||||
assert!(reader.is_empty(&events));
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert_eq!(reader.len(&events), 1);
|
||||
assert!(!reader.is_empty(&events));
|
||||
}
|
||||
@ -396,9 +397,9 @@ mod tests {
|
||||
#[test]
|
||||
fn test_event_cursor_iter_len_updated() {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.send(TestEvent { i: 1 });
|
||||
events.send(TestEvent { i: 2 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 1 });
|
||||
events.write(TestEvent { i: 2 });
|
||||
let mut reader = events.get_cursor();
|
||||
let mut iter = reader.read(&events);
|
||||
assert_eq!(iter.len(), 3);
|
||||
@ -420,7 +421,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_event_cursor_len_filled() {
|
||||
let mut events = Events::<TestEvent>::default();
|
||||
events.send(TestEvent { i: 0 });
|
||||
events.write(TestEvent { i: 0 });
|
||||
assert_eq!(events.get_cursor().len(&events), 1);
|
||||
assert!(!events.get_cursor().is_empty(&events));
|
||||
}
|
||||
@ -437,7 +438,7 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Events<TestEvent>>();
|
||||
for _ in 0..100 {
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
}
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
@ -479,7 +480,7 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Events<TestEvent>>();
|
||||
for _ in 0..100 {
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
}
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(
|
||||
@ -531,13 +532,13 @@ mod tests {
|
||||
let last = reader.run((), &mut world).unwrap();
|
||||
assert!(last.is_none(), "EventReader should be empty");
|
||||
|
||||
world.send_event(TestEvent { i: 0 });
|
||||
world.write_event(TestEvent { i: 0 });
|
||||
let last = reader.run((), &mut world).unwrap();
|
||||
assert_eq!(last, Some(TestEvent { i: 0 }));
|
||||
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.send_event(TestEvent { i: 2 });
|
||||
world.send_event(TestEvent { i: 3 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 2 });
|
||||
world.write_event(TestEvent { i: 3 });
|
||||
let last = reader.run((), &mut world).unwrap();
|
||||
assert_eq!(last, Some(TestEvent { i: 3 }));
|
||||
|
||||
@ -561,13 +562,13 @@ mod tests {
|
||||
let last = mutator.run((), &mut world).unwrap();
|
||||
assert!(last.is_none(), "EventMutator should be empty");
|
||||
|
||||
world.send_event(TestEvent { i: 0 });
|
||||
world.write_event(TestEvent { i: 0 });
|
||||
let last = mutator.run((), &mut world).unwrap();
|
||||
assert_eq!(last, Some(TestEvent { i: 0 }));
|
||||
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.send_event(TestEvent { i: 2 });
|
||||
world.send_event(TestEvent { i: 3 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 2 });
|
||||
world.write_event(TestEvent { i: 3 });
|
||||
let last = mutator.run((), &mut world).unwrap();
|
||||
assert_eq!(last, Some(TestEvent { i: 3 }));
|
||||
|
||||
@ -582,11 +583,11 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Events<TestEvent>>();
|
||||
|
||||
world.send_event(TestEvent { i: 0 });
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.send_event(TestEvent { i: 2 });
|
||||
world.send_event(TestEvent { i: 3 });
|
||||
world.send_event(TestEvent { i: 4 });
|
||||
world.write_event(TestEvent { i: 0 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 2 });
|
||||
world.write_event(TestEvent { i: 3 });
|
||||
world.write_event(TestEvent { i: 4 });
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(|mut events: EventReader<TestEvent>| {
|
||||
@ -608,11 +609,11 @@ mod tests {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Events<TestEvent>>();
|
||||
|
||||
world.send_event(TestEvent { i: 0 });
|
||||
world.send_event(TestEvent { i: 1 });
|
||||
world.send_event(TestEvent { i: 2 });
|
||||
world.send_event(TestEvent { i: 3 });
|
||||
world.send_event(TestEvent { i: 4 });
|
||||
world.write_event(TestEvent { i: 0 });
|
||||
world.write_event(TestEvent { i: 1 });
|
||||
world.write_event(TestEvent { i: 2 });
|
||||
world.write_event(TestEvent { i: 3 });
|
||||
world.write_event(TestEvent { i: 4 });
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(|mut events: EventReader<TestEvent>| {
|
||||
|
||||
@ -89,7 +89,7 @@ impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
|
||||
/// });
|
||||
/// });
|
||||
/// for value in 0..100 {
|
||||
/// world.send_event(MyEvent { value });
|
||||
/// world.write_event(MyEvent { value });
|
||||
/// }
|
||||
/// schedule.run(&mut world);
|
||||
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap();
|
||||
|
||||
@ -61,7 +61,7 @@ impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
|
||||
/// });
|
||||
/// });
|
||||
/// for value in 0..100 {
|
||||
/// world.send_event(MyEvent { value });
|
||||
/// world.write_event(MyEvent { value });
|
||||
/// }
|
||||
/// schedule.run(&mut world);
|
||||
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap();
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_ecs::{
|
||||
event::{BufferedEvent, EventId, Events, SendBatchIds},
|
||||
event::{BufferedEvent, EventId, Events, WriteBatchIds},
|
||||
system::{ResMut, SystemParam},
|
||||
};
|
||||
|
||||
/// Sends [`BufferedEvent`]s of type `T`.
|
||||
/// Writes [`BufferedEvent`]s of type `T`.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@ -34,14 +34,14 @@ use bevy_ecs::{
|
||||
///
|
||||
/// `EventWriter` can only write events of one specific type, which must be known at compile-time.
|
||||
/// This is not a problem most of the time, but you may find a situation where you cannot know
|
||||
/// ahead of time every kind of event you'll need to send. In this case, you can use the "type-erased event" pattern.
|
||||
/// ahead of time every kind of event you'll need to write. In this case, you can use the "type-erased event" pattern.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, event::Events};
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # pub struct MyEvent;
|
||||
/// fn send_untyped(mut commands: Commands) {
|
||||
/// // Send an event of a specific type without having to declare that
|
||||
/// fn write_untyped(mut commands: Commands) {
|
||||
/// // Write an event of a specific type without having to declare that
|
||||
/// // type as a SystemParam.
|
||||
/// //
|
||||
/// // Effectively, we're just moving the type parameter from the /type/ to the /method/,
|
||||
@ -51,7 +51,7 @@ use bevy_ecs::{
|
||||
/// // NOTE: the event won't actually be sent until commands get applied during
|
||||
/// // apply_deferred.
|
||||
/// commands.queue(|w: &mut World| {
|
||||
/// w.send_event(MyEvent);
|
||||
/// w.write_event(MyEvent);
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
@ -72,18 +72,18 @@ impl<'w, E: BufferedEvent> EventWriter<'w, E> {
|
||||
#[doc(alias = "send")]
|
||||
#[track_caller]
|
||||
pub fn write(&mut self, event: E) -> EventId<E> {
|
||||
self.events.send(event)
|
||||
self.events.write(event)
|
||||
}
|
||||
|
||||
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This is more efficient than sending each event individually.
|
||||
/// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
|
||||
/// This is more efficient than writing each event individually.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`.
|
||||
///
|
||||
/// See [`Events`] for details.
|
||||
#[doc(alias = "send_batch")]
|
||||
#[track_caller]
|
||||
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
|
||||
self.events.send_batch(events)
|
||||
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
|
||||
self.events.write_batch(events)
|
||||
}
|
||||
|
||||
/// Writes the default value of the event. Useful when the event is an empty struct.
|
||||
@ -96,6 +96,6 @@ impl<'w, E: BufferedEvent> EventWriter<'w, E> {
|
||||
where
|
||||
E: Default,
|
||||
{
|
||||
self.events.send_default()
|
||||
self.events.write_default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -455,15 +455,13 @@ pub fn validate_parent_has_component<C: Component>(
|
||||
let Some(child_of) = entity_ref.get::<ChildOf>() else {
|
||||
return;
|
||||
};
|
||||
if !world
|
||||
.get_entity(child_of.parent())
|
||||
.is_ok_and(|e| e.contains::<C>())
|
||||
{
|
||||
let parent = child_of.parent();
|
||||
if !world.get_entity(parent).is_ok_and(|e| e.contains::<C>()) {
|
||||
// TODO: print name here once Name lives in bevy_ecs
|
||||
let name: Option<String> = None;
|
||||
let debug_name = DebugName::type_name::<C>();
|
||||
warn!(
|
||||
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
|
||||
"warning[B0004]: {}{name} with the {ty_name} component has a parent ({parent}) without {ty_name}.\n\
|
||||
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
|
||||
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
|
||||
ty_name = debug_name.shortname(),
|
||||
|
||||
@ -465,10 +465,16 @@ impl RemovedComponentEvents {
|
||||
}
|
||||
|
||||
/// Sends a removal event for the specified component.
|
||||
#[deprecated(since = "0.17.0", note = "Use `RemovedComponentEvents:write` instead.")]
|
||||
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.write(component_id, entity);
|
||||
}
|
||||
|
||||
/// Writes a removal event for the specified component.
|
||||
pub fn write(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.event_sets
|
||||
.get_or_insert_with(component_id.into(), Default::default)
|
||||
.send(RemovedComponentEntity(entity));
|
||||
.write(RemovedComponentEntity(entity));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -133,7 +133,7 @@ pub trait Relationship: Component + Sized {
|
||||
.and_modify(move |mut relationship_target| {
|
||||
relationship_target.collection_mut_risky().add(entity);
|
||||
})
|
||||
.or_insert_with(|| {
|
||||
.or_insert_with(move || {
|
||||
let mut target = Self::RelationshipTarget::with_capacity(1);
|
||||
target.collection_mut_risky().add(entity);
|
||||
target
|
||||
|
||||
@ -875,7 +875,7 @@ pub mod common_conditions {
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.resource_mut::<Events<MyEvent>>().send(MyEvent);
|
||||
/// world.resource_mut::<Events<MyEvent>>().write(MyEvent);
|
||||
///
|
||||
/// // A `MyEvent` event has been pushed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
|
||||
@ -597,7 +597,7 @@ impl Table {
|
||||
#[inline]
|
||||
pub fn entity_count(&self) -> u32 {
|
||||
// No entity may have more than one table row, so there are no duplicates,
|
||||
// and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity.
|
||||
// and there may only ever be u32::MAX entities, so the length never exceeds u32's capacity.
|
||||
self.entities.len() as u32
|
||||
}
|
||||
|
||||
|
||||
@ -229,12 +229,19 @@ pub fn trigger_targets(
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends an arbitrary [`BufferedEvent`].
|
||||
/// A [`Command`] that writes an arbitrary [`BufferedEvent`].
|
||||
#[track_caller]
|
||||
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command {
|
||||
pub fn write_event<E: BufferedEvent>(event: E) -> impl Command {
|
||||
let caller = MaybeLocation::caller();
|
||||
move |world: &mut World| {
|
||||
let mut events = world.resource_mut::<Events<E>>();
|
||||
events.send_with_caller(event, caller);
|
||||
events.write_with_caller(event, caller);
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Command`] that writes an arbitrary [`BufferedEvent`].
|
||||
#[track_caller]
|
||||
#[deprecated(since = "0.17.0", note = "Use `write_event` instead.")]
|
||||
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command {
|
||||
write_event(event)
|
||||
}
|
||||
|
||||
@ -143,12 +143,38 @@ pub unsafe fn insert_by_id<T: Send + 'static>(
|
||||
|
||||
/// An [`EntityCommand`] that adds a component to an entity using
|
||||
/// the component's [`FromWorld`] implementation.
|
||||
///
|
||||
/// `T::from_world` will only be invoked if the component will actually be inserted.
|
||||
/// In other words, `T::from_world` will *not* be invoked if `mode` is [`InsertMode::Keep`]
|
||||
/// and the entity already has the component.
|
||||
#[track_caller]
|
||||
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
|
||||
let caller = MaybeLocation::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
let value = entity.world_scope(|world| T::from_world(world));
|
||||
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
||||
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
|
||||
let value = entity.world_scope(|world| T::from_world(world));
|
||||
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`EntityCommand`] that adds a component to an entity using
|
||||
/// some function that returns the component.
|
||||
///
|
||||
/// The function will only be invoked if the component will actually be inserted.
|
||||
/// In other words, the function will *not* be invoked if `mode` is [`InsertMode::Keep`]
|
||||
/// and the entity already has the component.
|
||||
#[track_caller]
|
||||
pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
|
||||
where
|
||||
F: FnOnce() -> T + Send + 'static,
|
||||
{
|
||||
let caller = MaybeLocation::caller();
|
||||
move |mut entity: EntityWorldMut| {
|
||||
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
|
||||
let value = component_fn();
|
||||
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1124,9 +1124,9 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
self.spawn(Observer::new(observer))
|
||||
}
|
||||
|
||||
/// Sends an arbitrary [`BufferedEvent`].
|
||||
/// Writes an arbitrary [`BufferedEvent`].
|
||||
///
|
||||
/// This is a convenience method for sending events
|
||||
/// This is a convenience method for writing events
|
||||
/// without requiring an [`EventWriter`](crate::event::EventWriter).
|
||||
///
|
||||
/// # Performance
|
||||
@ -1137,11 +1137,29 @@ impl<'w, 's> Commands<'w, 's> {
|
||||
/// If these events are performance-critical or very frequently sent,
|
||||
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
|
||||
#[track_caller]
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
|
||||
self.queue(command::send_event(event));
|
||||
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
|
||||
self.queue(command::write_event(event));
|
||||
self
|
||||
}
|
||||
|
||||
/// Writes an arbitrary [`BufferedEvent`].
|
||||
///
|
||||
/// This is a convenience method for writing events
|
||||
/// without requiring an [`EventWriter`](crate::event::EventWriter).
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
/// Since this is a command, exclusive world access is used, which means that it will not profit from
|
||||
/// system-level parallelism on supported platforms.
|
||||
///
|
||||
/// If these events are performance-critical or very frequently sent,
|
||||
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
|
||||
#[track_caller]
|
||||
#[deprecated(since = "0.17.0", note = "Use `Commands::write_event` instead.")]
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
|
||||
self.write_event(event)
|
||||
}
|
||||
|
||||
/// Runs the schedule corresponding to the given [`ScheduleLabel`].
|
||||
///
|
||||
/// Calls [`World::try_run_schedule`](World::try_run_schedule).
|
||||
@ -2273,35 +2291,53 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
|
||||
|
||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
||||
/// if `T` is not already present.
|
||||
///
|
||||
/// `default` will only be invoked if the component will actually be inserted.
|
||||
#[track_caller]
|
||||
pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
|
||||
self.or_insert(default())
|
||||
pub fn or_insert_with<F>(&mut self, default: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce() -> T + Send + 'static,
|
||||
{
|
||||
self.entity_commands
|
||||
.queue(entity_command::insert_with(default, InsertMode::Keep));
|
||||
self
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
|
||||
/// if `T` is not already present.
|
||||
///
|
||||
/// `default` will only be invoked if the component will actually be inserted.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// If the entity does not exist when this command is executed,
|
||||
/// the resulting error will be ignored.
|
||||
#[track_caller]
|
||||
pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
|
||||
self.or_try_insert(default())
|
||||
pub fn or_try_insert_with<F>(&mut self, default: F) -> &mut Self
|
||||
where
|
||||
F: FnOnce() -> T + Send + 'static,
|
||||
{
|
||||
self.entity_commands
|
||||
.queue_silenced(entity_command::insert_with(default, InsertMode::Keep));
|
||||
self
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `T::default` into this entity,
|
||||
/// if `T` is not already present.
|
||||
///
|
||||
/// `T::default` will only be invoked if the component will actually be inserted.
|
||||
#[track_caller]
|
||||
pub fn or_default(&mut self) -> &mut Self
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
self.or_insert(T::default())
|
||||
self.or_insert_with(T::default)
|
||||
}
|
||||
|
||||
/// [Insert](EntityCommands::insert) `T::from_world` into this entity,
|
||||
/// if `T` is not already present.
|
||||
///
|
||||
/// `T::from_world` will only be invoked if the component will actually be inserted.
|
||||
#[track_caller]
|
||||
pub fn or_from_world(&mut self) -> &mut Self
|
||||
where
|
||||
@ -2396,6 +2432,12 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for W<u8> {
|
||||
fn default() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_commands_entry() {
|
||||
let mut world = World::default();
|
||||
@ -2435,6 +2477,17 @@ mod tests {
|
||||
let id = commands.entity(entity).entry::<W<u64>>().entity().id();
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(id, entity);
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
commands
|
||||
.entity(entity)
|
||||
.entry::<W<u8>>()
|
||||
.or_insert_with(|| W(5))
|
||||
.or_insert_with(|| unreachable!())
|
||||
.or_try_insert_with(|| unreachable!())
|
||||
.or_default()
|
||||
.or_from_world();
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(5, world.get::<W<u8>>(entity).unwrap().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::{
|
||||
change_detection::{MaybeLocation, MutUntyped},
|
||||
component::{ComponentId, Mutable},
|
||||
entity::Entity,
|
||||
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, SendBatchIds},
|
||||
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, WriteBatchIds},
|
||||
lifecycle::{HookContext, INSERT, REPLACE},
|
||||
observer::{Observers, TriggerTargets},
|
||||
prelude::{Component, QueryState},
|
||||
@ -507,30 +507,51 @@ impl<'w> DeferredWorld<'w> {
|
||||
unsafe { self.world.get_non_send_resource_mut() }
|
||||
}
|
||||
|
||||
/// Sends a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.write_event_batch(core::iter::once(event))?.next()
|
||||
}
|
||||
|
||||
/// Writes a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(since = "0.17.0", note = "Use `DeferredWorld::write_event` instead.")]
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.send_event_batch(core::iter::once(event))?.next()
|
||||
self.write_event(event)
|
||||
}
|
||||
|
||||
/// Sends the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn write_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.write_event(E::default())
|
||||
}
|
||||
|
||||
/// Writes the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(
|
||||
since = "0.17.0",
|
||||
note = "Use `DeferredWorld::write_event_default` instead."
|
||||
)]
|
||||
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.send_event(E::default())
|
||||
self.write_event_default::<E>()
|
||||
}
|
||||
|
||||
/// Sends a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
pub fn write_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<SendBatchIds<E>> {
|
||||
) -> Option<WriteBatchIds<E>> {
|
||||
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
|
||||
log::error!(
|
||||
"Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ",
|
||||
@ -538,7 +559,22 @@ impl<'w> DeferredWorld<'w> {
|
||||
);
|
||||
return None;
|
||||
};
|
||||
Some(events_resource.send_batch(events))
|
||||
Some(events_resource.write_batch(events))
|
||||
}
|
||||
|
||||
/// Writes a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(
|
||||
since = "0.17.0",
|
||||
note = "Use `DeferredWorld::write_event_batch` instead."
|
||||
)]
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<WriteBatchIds<E>> {
|
||||
self.write_event_batch(events)
|
||||
}
|
||||
|
||||
/// Gets a pointer to the resource with the id [`ComponentId`] if it exists.
|
||||
|
||||
@ -2418,7 +2418,7 @@ impl<'w> EntityWorldMut<'w> {
|
||||
}
|
||||
|
||||
for component_id in archetype.components() {
|
||||
world.removed_components.send(component_id, self.entity);
|
||||
world.removed_components.write(component_id, self.entity);
|
||||
}
|
||||
|
||||
// Observers and on_remove hooks may reserve new entities, which
|
||||
|
||||
@ -50,7 +50,7 @@ use crate::{
|
||||
},
|
||||
entity::{Entities, Entity, EntityDoesNotExistError},
|
||||
entity_disabling::DefaultQueryFilters,
|
||||
event::{Event, EventId, Events, SendBatchIds},
|
||||
event::{Event, EventId, Events, WriteBatchIds},
|
||||
lifecycle::RemovedComponentEvents,
|
||||
observer::Observers,
|
||||
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
||||
@ -2626,30 +2626,48 @@ impl World {
|
||||
Some(result)
|
||||
}
|
||||
|
||||
/// Sends a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.write_event_batch(core::iter::once(event))?.next()
|
||||
}
|
||||
|
||||
/// Writes a [`BufferedEvent`].
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(since = "0.17.0", note = "Use `World::write_event` instead.")]
|
||||
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
|
||||
self.send_event_batch(core::iter::once(event))?.next()
|
||||
self.write_event(event)
|
||||
}
|
||||
|
||||
/// Sends the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the sent `event`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn write_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.write_event(E::default())
|
||||
}
|
||||
|
||||
/// Writes the default value of the [`BufferedEvent`] of type `E`.
|
||||
/// This method returns the [ID](`EventId`) of the written `event`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(since = "0.17.0", note = "Use `World::write_event_default` instead.")]
|
||||
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
|
||||
self.send_event(E::default())
|
||||
self.write_event_default::<E>()
|
||||
}
|
||||
|
||||
/// Sends a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the sent `events`,
|
||||
/// or [`None`] if the `event` could not be sent.
|
||||
/// Writes a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
pub fn write_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<SendBatchIds<E>> {
|
||||
) -> Option<WriteBatchIds<E>> {
|
||||
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
|
||||
log::error!(
|
||||
"Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ",
|
||||
@ -2657,7 +2675,19 @@ impl World {
|
||||
);
|
||||
return None;
|
||||
};
|
||||
Some(events_resource.send_batch(events))
|
||||
Some(events_resource.write_batch(events))
|
||||
}
|
||||
|
||||
/// Writes a batch of [`BufferedEvent`]s from an iterator.
|
||||
/// This method returns the [IDs](`EventId`) of the written `events`,
|
||||
/// or [`None`] if the `event` could not be written.
|
||||
#[inline]
|
||||
#[deprecated(since = "0.17.0", note = "Use `World::write_event_batch` instead.")]
|
||||
pub fn send_event_batch<E: BufferedEvent>(
|
||||
&mut self,
|
||||
events: impl IntoIterator<Item = E>,
|
||||
) -> Option<WriteBatchIds<E>> {
|
||||
self.write_event_batch(events)
|
||||
}
|
||||
|
||||
/// Inserts a new resource with the given `value`. Will replace the value if it already existed.
|
||||
|
||||
@ -13,7 +13,6 @@ keywords = ["bevy"]
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
|
||||
@ -16,7 +16,6 @@ proc-macro = true
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
|
||||
[lints]
|
||||
|
||||
@ -36,7 +36,6 @@ bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [
|
||||
] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
|
||||
@ -63,7 +63,6 @@ libm = ["bevy_math/libm"]
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"glam",
|
||||
], default-features = false, optional = true }
|
||||
|
||||
@ -2223,7 +2223,7 @@ mod tests {
|
||||
self.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<GamepadConnectionEvent>>()
|
||||
.send(GamepadConnectionEvent::new(
|
||||
.write(GamepadConnectionEvent::new(
|
||||
gamepad,
|
||||
Connected {
|
||||
name: "Test gamepad".to_string(),
|
||||
@ -2238,14 +2238,14 @@ mod tests {
|
||||
self.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<GamepadConnectionEvent>>()
|
||||
.send(GamepadConnectionEvent::new(gamepad, Disconnected));
|
||||
.write(GamepadConnectionEvent::new(gamepad, Disconnected));
|
||||
}
|
||||
|
||||
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
|
||||
self.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send(event);
|
||||
.write(event);
|
||||
}
|
||||
|
||||
pub fn send_raw_gamepad_event_batch(
|
||||
@ -2255,7 +2255,7 @@ mod tests {
|
||||
self.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2449,7 +2449,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch([
|
||||
.write_batch([
|
||||
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
|
||||
entity,
|
||||
GamepadAxis::LeftStickY,
|
||||
@ -2513,7 +2513,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
assert_eq!(
|
||||
ctx.app
|
||||
@ -2550,7 +2550,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
assert_eq!(
|
||||
ctx.app
|
||||
@ -2598,7 +2598,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
|
||||
let events = ctx
|
||||
@ -2654,7 +2654,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
assert_eq!(
|
||||
ctx.app
|
||||
@ -2692,7 +2692,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
|
||||
let events = ctx
|
||||
@ -2728,7 +2728,7 @@ mod tests {
|
||||
ctx.app
|
||||
.world_mut()
|
||||
.resource_mut::<Events<RawGamepadEvent>>()
|
||||
.send_batch(events);
|
||||
.write_batch(events);
|
||||
ctx.update();
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@ -546,7 +546,7 @@ mod tests {
|
||||
assert!(!app.world().is_focus_visible(child_of_b));
|
||||
|
||||
// entity_a should receive this event
|
||||
app.world_mut().send_event(key_a_event());
|
||||
app.world_mut().write_event(key_a_event());
|
||||
app.update();
|
||||
|
||||
assert_eq!(get_gathered(&app, entity_a), "A");
|
||||
@ -559,7 +559,7 @@ mod tests {
|
||||
assert!(!app.world().is_focus_visible(entity_a));
|
||||
|
||||
// This event should be lost
|
||||
app.world_mut().send_event(key_a_event());
|
||||
app.world_mut().write_event(key_a_event());
|
||||
app.update();
|
||||
|
||||
assert_eq!(get_gathered(&app, entity_a), "A");
|
||||
@ -580,7 +580,7 @@ mod tests {
|
||||
|
||||
// These events should be received by entity_b and child_of_b
|
||||
app.world_mut()
|
||||
.send_event_batch(core::iter::repeat_n(key_a_event(), 4));
|
||||
.write_event_batch(core::iter::repeat_n(key_a_event(), 4));
|
||||
app.update();
|
||||
|
||||
assert_eq!(get_gathered(&app, entity_a), "A");
|
||||
|
||||
@ -202,6 +202,10 @@ bevy_ui = ["dep:bevy_ui", "bevy_image"]
|
||||
bevy_ui_render = ["dep:bevy_ui_render"]
|
||||
bevy_image = ["dep:bevy_image"]
|
||||
|
||||
bevy_mesh = ["dep:bevy_mesh", "bevy_image"]
|
||||
bevy_camera = ["dep:bevy_camera", "bevy_mesh"]
|
||||
bevy_light = ["dep:bevy_light", "bevy_camera"]
|
||||
|
||||
# Used to disable code that is unsupported when Bevy is dynamically linked
|
||||
dynamic_linking = ["bevy_diagnostic/dynamic_linking"]
|
||||
|
||||
@ -218,7 +222,7 @@ bevy_render = [
|
||||
"dep:bevy_render",
|
||||
"bevy_scene?/bevy_render",
|
||||
"bevy_gizmos?/bevy_render",
|
||||
"bevy_image",
|
||||
"bevy_camera",
|
||||
"bevy_color/wgpu-types",
|
||||
"bevy_color/encase",
|
||||
]
|
||||
@ -431,6 +435,9 @@ bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev"
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
|
||||
bevy_feathers = { path = "../bevy_feathers", optional = true, version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", optional = true, version = "0.17.0-dev" }
|
||||
bevy_camera = { path = "../bevy_camera", optional = true, version = "0.17.0-dev" }
|
||||
bevy_light = { path = "../bevy_light", optional = true, version = "0.17.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
|
||||
@ -25,6 +25,8 @@ pub use bevy_app as app;
|
||||
pub use bevy_asset as asset;
|
||||
#[cfg(feature = "bevy_audio")]
|
||||
pub use bevy_audio as audio;
|
||||
#[cfg(feature = "bevy_camera")]
|
||||
pub use bevy_camera as camera;
|
||||
#[cfg(feature = "bevy_color")]
|
||||
pub use bevy_color as color;
|
||||
#[cfg(feature = "bevy_core_pipeline")]
|
||||
@ -48,9 +50,13 @@ pub use bevy_image as image;
|
||||
pub use bevy_input as input;
|
||||
#[cfg(feature = "bevy_input_focus")]
|
||||
pub use bevy_input_focus as input_focus;
|
||||
#[cfg(feature = "bevy_light")]
|
||||
pub use bevy_light as light;
|
||||
#[cfg(feature = "bevy_log")]
|
||||
pub use bevy_log as log;
|
||||
pub use bevy_math as math;
|
||||
#[cfg(feature = "bevy_mesh")]
|
||||
pub use bevy_mesh as mesh;
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
pub use bevy_pbr as pbr;
|
||||
#[cfg(feature = "bevy_picking")]
|
||||
|
||||
42
crates/bevy_light/Cargo.toml
Normal file
42
crates/bevy_light/Cargo.toml
Normal file
@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "bevy_light"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Keeps the lights on at 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_camera = { path = "../bevy_camera", 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_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
# other
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
experimental_pbr_pcss = []
|
||||
webgl = []
|
||||
webgpu = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
||||
all-features = true
|
||||
176
crates/bevy_light/LICENSE-APACHE
Normal file
176
crates/bevy_light/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_light/LICENSE-MIT
Normal file
19
crates/bevy_light/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.
|
||||
@ -2,7 +2,6 @@ use bevy_camera::Camera;
|
||||
use bevy_color::Color;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{extract_component::ExtractComponent, extract_resource::ExtractResource};
|
||||
|
||||
/// An ambient light, which lights the entire scene equally.
|
||||
///
|
||||
@ -16,14 +15,14 @@ use bevy_render::{extract_component::ExtractComponent, extract_resource::Extract
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::system::ResMut;
|
||||
/// # use bevy_pbr::AmbientLight;
|
||||
/// # use bevy_light::AmbientLight;
|
||||
/// fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
|
||||
/// ambient_light.brightness = 100.0;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`LightPlugin`]: crate::LightPlugin
|
||||
#[derive(Resource, Component, Clone, Debug, ExtractResource, ExtractComponent, Reflect)]
|
||||
#[derive(Resource, Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Resource, Component, Debug, Default, Clone)]
|
||||
#[require(Camera)]
|
||||
pub struct AmbientLight {
|
||||
@ -11,8 +11,8 @@ use crate::{DirectionalLight, DirectionalLightShadowMap};
|
||||
/// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_pbr::CascadeShadowConfig;
|
||||
/// # use bevy_pbr::CascadeShadowConfigBuilder;
|
||||
/// # use bevy_light::CascadeShadowConfig;
|
||||
/// # use bevy_light::CascadeShadowConfigBuilder;
|
||||
/// # use bevy_utils::default;
|
||||
/// #
|
||||
/// let config: CascadeShadowConfig = CascadeShadowConfigBuilder {
|
||||
@ -22,10 +22,7 @@ use super::{
|
||||
ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings,
|
||||
GlobalVisibleClusterableObjects, VisibleClusterableObjects,
|
||||
};
|
||||
use crate::{
|
||||
prelude::EnvironmentMapLight, ExtractedPointLight, LightProbe, PointLight, SpotLight,
|
||||
VolumetricLight,
|
||||
};
|
||||
use crate::{EnvironmentMapLight, LightProbe, PointLight, SpotLight, VolumetricLight};
|
||||
|
||||
const NDC_MIN: Vec2 = Vec2::NEG_ONE;
|
||||
const NDC_MAX: Vec2 = Vec2::ONE;
|
||||
@ -57,7 +54,7 @@ impl ClusterableObjectAssignmentData {
|
||||
/// Data needed to assign objects to clusters that's specific to the type of
|
||||
/// clusterable object.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum ClusterableObjectType {
|
||||
pub enum ClusterableObjectType {
|
||||
/// Data needed to assign point lights to clusters.
|
||||
PointLight {
|
||||
/// Whether shadows are enabled for this point light.
|
||||
@ -105,7 +102,7 @@ impl ClusterableObjectType {
|
||||
/// Generally, we sort first by type, then, for lights, by whether shadows
|
||||
/// are enabled (enabled before disabled), and then whether volumetrics are
|
||||
/// enabled (enabled before disabled).
|
||||
pub(crate) fn ordering(&self) -> (u8, bool, bool) {
|
||||
pub fn ordering(&self) -> (u8, bool, bool) {
|
||||
match *self {
|
||||
ClusterableObjectType::PointLight {
|
||||
shadows_enabled,
|
||||
@ -121,23 +118,6 @@ impl ClusterableObjectType {
|
||||
ClusterableObjectType::Decal => (4, false, false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`ClusterableObjectType`] data for a point or spot light.
|
||||
pub(crate) fn from_point_or_spot_light(
|
||||
point_light: &ExtractedPointLight,
|
||||
) -> ClusterableObjectType {
|
||||
match point_light.spot_light_angles {
|
||||
Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
|
||||
outer_angle,
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
volumetric: point_light.volumetric,
|
||||
},
|
||||
None => ClusterableObjectType::PointLight {
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
volumetric: point_light.volumetric,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Run this before update_point_light_frusta!
|
||||
@ -17,15 +17,10 @@ use bevy_image::Image;
|
||||
use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::extract_component::ExtractComponent;
|
||||
use bevy_transform::components::Transform;
|
||||
use tracing::warn;
|
||||
|
||||
pub(crate) use crate::cluster::assign::assign_objects_to_clusters;
|
||||
|
||||
pub(crate) mod assign;
|
||||
mod extract_and_prepare;
|
||||
pub use extract_and_prepare::*;
|
||||
pub mod assign;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
@ -106,14 +101,14 @@ pub enum ClusterConfig {
|
||||
#[derive(Component, Debug, Default)]
|
||||
pub struct Clusters {
|
||||
/// Tile size
|
||||
pub(crate) tile_size: UVec2,
|
||||
pub tile_size: UVec2,
|
||||
/// Number of clusters in `X` / `Y` / `Z` in the view frustum
|
||||
pub(crate) dimensions: UVec3,
|
||||
pub dimensions: UVec3,
|
||||
/// Distance to the far plane of the first depth slice. The first depth slice is special
|
||||
/// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
|
||||
pub(crate) near: f32,
|
||||
pub(crate) far: f32,
|
||||
pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
|
||||
pub near: f32,
|
||||
pub far: f32,
|
||||
pub clusterable_objects: Vec<VisibleClusterableObjects>,
|
||||
}
|
||||
|
||||
/// The [`VisibilityClass`] used for clusterables (decals, point lights, directional lights, and spot lights).
|
||||
@ -123,8 +118,8 @@ pub struct ClusterVisibilityClass;
|
||||
|
||||
#[derive(Clone, Component, Debug, Default)]
|
||||
pub struct VisibleClusterableObjects {
|
||||
pub(crate) entities: Vec<Entity>,
|
||||
counts: ClusterableObjectCounts,
|
||||
pub entities: Vec<Entity>,
|
||||
pub counts: ClusterableObjectCounts,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
@ -137,17 +132,17 @@ pub struct GlobalVisibleClusterableObjects {
|
||||
/// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if
|
||||
/// fewer than 3 SSBOs are available, which usually means on WebGL 2.
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
struct ClusterableObjectCounts {
|
||||
pub struct ClusterableObjectCounts {
|
||||
/// The number of point lights in the cluster.
|
||||
point_lights: u32,
|
||||
pub point_lights: u32,
|
||||
/// The number of spot lights in the cluster.
|
||||
spot_lights: u32,
|
||||
pub spot_lights: u32,
|
||||
/// The number of reflection probes in the cluster.
|
||||
reflection_probes: u32,
|
||||
pub reflection_probes: u32,
|
||||
/// The number of irradiance volumes in the cluster.
|
||||
irradiance_volumes: u32,
|
||||
pub irradiance_volumes: u32,
|
||||
/// The number of decals in the cluster.
|
||||
decals: u32,
|
||||
pub decals: u32,
|
||||
}
|
||||
|
||||
/// An object that projects a decal onto surfaces within its bounds.
|
||||
@ -160,7 +155,7 @@ struct ClusterableObjectCounts {
|
||||
/// but they require bindless textures. This means that they presently can't be
|
||||
/// used on WebGL 2, WebGPU, macOS, or iOS. Bevy's clustered decals can be used
|
||||
/// with forward or deferred rendering and don't require a prepass.
|
||||
#[derive(Component, Debug, Clone, Reflect, ExtractComponent)]
|
||||
#[derive(Component, Debug, Clone, Reflect)]
|
||||
#[reflect(Component, Debug, Clone)]
|
||||
#[require(Transform, Visibility, VisibilityClass)]
|
||||
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
|
||||
@ -1,6 +1,6 @@
|
||||
use bevy_math::UVec2;
|
||||
|
||||
use crate::{ClusterConfig, Clusters};
|
||||
use super::{ClusterConfig, Clusters};
|
||||
|
||||
fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters {
|
||||
let dims = config.dimensions_for_screen_size(screen_size);
|
||||
@ -10,8 +10,9 @@ use bevy_image::Image;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::components::Transform;
|
||||
|
||||
use super::{cascade::CascadeShadowConfig, light_consts, Cascades};
|
||||
use crate::cluster::ClusterVisibilityClass;
|
||||
use super::{
|
||||
cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,
|
||||
};
|
||||
|
||||
/// A Directional light.
|
||||
///
|
||||
@ -172,7 +173,7 @@ pub struct DirectionalLightTexture {
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_pbr::DirectionalLightShadowMap;
|
||||
/// # use bevy_light::DirectionalLightShadowMap;
|
||||
/// App::new()
|
||||
/// .insert_resource(DirectionalLightShadowMap { size: 4096 });
|
||||
/// ```
|
||||
@ -1,3 +1,5 @@
|
||||
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
||||
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_camera::{
|
||||
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
|
||||
@ -10,21 +12,27 @@ use bevy_camera::{
|
||||
};
|
||||
use bevy_ecs::{entity::EntityHashSet, prelude::*};
|
||||
use bevy_math::Vec3A;
|
||||
use bevy_mesh::Mesh3d;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{extract_component::ExtractComponent, mesh::Mesh3d};
|
||||
use bevy_transform::{components::GlobalTransform, TransformSystems};
|
||||
use bevy_utils::Parallel;
|
||||
use core::ops::DerefMut;
|
||||
|
||||
use crate::cluster::{add_clusters, assign_objects_to_clusters, VisibleClusterableObjects};
|
||||
|
||||
pub mod cluster;
|
||||
pub use cluster::ClusteredDecal;
|
||||
use cluster::{
|
||||
add_clusters, assign::assign_objects_to_clusters, ClusterConfig,
|
||||
GlobalVisibleClusterableObjects, VisibleClusterableObjects,
|
||||
};
|
||||
mod ambient_light;
|
||||
pub use ambient_light::AmbientLight;
|
||||
mod probe;
|
||||
pub use probe::{EnvironmentMapLight, IrradianceVolume, LightProbe};
|
||||
mod volumetric;
|
||||
pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
|
||||
pub mod cascade;
|
||||
use cascade::{
|
||||
build_directional_light_cascades, clear_directional_light_cascades, CascadeShadowConfig,
|
||||
Cascades,
|
||||
};
|
||||
use cascade::{build_directional_light_cascades, clear_directional_light_cascades};
|
||||
pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
|
||||
mod point_light;
|
||||
pub use point_light::{
|
||||
update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
|
||||
@ -111,9 +119,16 @@ impl Plugin for LightPlugin {
|
||||
.register_type::<NotShadowCaster>()
|
||||
.register_type::<NotShadowReceiver>()
|
||||
.register_type::<PointLight>()
|
||||
.register_type::<LightProbe>()
|
||||
.register_type::<EnvironmentMapLight>()
|
||||
.register_type::<IrradianceVolume>()
|
||||
.register_type::<VolumetricFog>()
|
||||
.register_type::<VolumetricLight>()
|
||||
.register_type::<PointLightShadowMap>()
|
||||
.register_type::<SpotLight>()
|
||||
.register_type::<ShadowFilteringMethod>()
|
||||
.register_type::<ClusterConfig>()
|
||||
.init_resource::<GlobalVisibleClusterableObjects>()
|
||||
.init_resource::<AmbientLight>()
|
||||
.init_resource::<DirectionalLightShadowMap>()
|
||||
.init_resource::<PointLightShadowMap>()
|
||||
@ -182,7 +197,7 @@ impl Plugin for LightPlugin {
|
||||
}
|
||||
|
||||
/// A convenient alias for `Or<(With<PointLight>, With<SpotLight>,
|
||||
/// With<DirectionalLight>)>`, for use with [`bevy_render::view::VisibleEntities`].
|
||||
/// With<DirectionalLight>)>`, for use with [`bevy_camera::visibility::VisibleEntities`].
|
||||
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
|
||||
|
||||
/// Add this component to make a [`Mesh3d`] not cast shadows.
|
||||
@ -197,10 +212,10 @@ pub struct NotShadowCaster;
|
||||
#[derive(Debug, Component, Reflect, Default)]
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct NotShadowReceiver;
|
||||
/// Add this component to make a [`Mesh3d`] using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0`
|
||||
/// Add this component to make a [`Mesh3d`] using a PBR material with `StandardMaterial::diffuse_transmission > 0.0`
|
||||
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
|
||||
///
|
||||
/// Not enabled by default, as it requires carefully setting up [`thickness`](crate::pbr_material::StandardMaterial::thickness)
|
||||
/// Not enabled by default, as it requires carefully setting up `StandardMaterial::thickness`
|
||||
/// (and potentially even baking a thickness texture!) to match the geometry of the mesh, in order to avoid self-shadow artifacts.
|
||||
///
|
||||
/// **Note:** Using [`NotShadowReceiver`] overrides this component.
|
||||
@ -208,12 +223,12 @@ pub struct NotShadowReceiver;
|
||||
#[reflect(Component, Default, Debug)]
|
||||
pub struct TransmittedShadowReceiver;
|
||||
|
||||
/// Add this component to a [`Camera3d`](bevy_core_pipeline::core_3d::Camera3d)
|
||||
/// Add this component to a [`Camera3d`](bevy_camera::Camera3d)
|
||||
/// to control how to anti-alias shadow edges.
|
||||
///
|
||||
/// The different modes use different approaches to
|
||||
/// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing).
|
||||
#[derive(Debug, Component, ExtractComponent, Reflect, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
|
||||
#[reflect(Component, Default, Debug, PartialEq, Clone)]
|
||||
pub enum ShadowFilteringMethod {
|
||||
/// Hardware 2x2.
|
||||
@ -166,7 +166,7 @@ pub struct PointLightTexture {
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_pbr::PointLightShadowMap;
|
||||
/// # use bevy_light::PointLightShadowMap;
|
||||
/// App::new()
|
||||
/// .insert_resource(PointLightShadowMap { size: 2048 });
|
||||
/// ```
|
||||
156
crates/bevy_light/src/probe.rs
Normal file
156
crates/bevy_light/src/probe.rs
Normal file
@ -0,0 +1,156 @@
|
||||
use bevy_asset::Handle;
|
||||
use bevy_camera::visibility::Visibility;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::Image;
|
||||
use bevy_math::Quat;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::components::Transform;
|
||||
|
||||
/// A marker component for a light probe, which is a cuboid region that provides
|
||||
/// global illumination to all fragments inside it.
|
||||
///
|
||||
/// Note that a light probe will have no effect unless the entity contains some
|
||||
/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
|
||||
/// `IrradianceVolume`.
|
||||
///
|
||||
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
|
||||
/// origin. The [`Transform`] applied to this entity can scale, rotate, or translate
|
||||
/// that cube so that it contains all fragments that should take this light probe into account.
|
||||
///
|
||||
/// When multiple sources of indirect illumination can be applied to a fragment,
|
||||
/// the highest-quality one is chosen. Diffuse and specular illumination are
|
||||
/// considered separately, so, for example, Bevy may decide to sample the
|
||||
/// diffuse illumination from an irradiance volume and the specular illumination
|
||||
/// from a reflection probe. From highest priority to lowest priority, the
|
||||
/// ranking is as follows:
|
||||
///
|
||||
/// | Rank | Diffuse | Specular |
|
||||
/// | ---- | -------------------- | -------------------- |
|
||||
/// | 1 | Lightmap | Lightmap |
|
||||
/// | 2 | Irradiance volume | Reflection probe |
|
||||
/// | 3 | Reflection probe | View environment map |
|
||||
/// | 4 | View environment map | |
|
||||
///
|
||||
/// Note that ambient light is always added to the diffuse component and does
|
||||
/// not participate in the ranking. That is, ambient light is applied in
|
||||
/// addition to, not instead of, the light sources above.
|
||||
///
|
||||
/// A terminology note: Unfortunately, there is little agreement across game and
|
||||
/// graphics engines as to what to call the various techniques that Bevy groups
|
||||
/// under the term *light probe*. In Bevy, a *light probe* is the generic term
|
||||
/// that encompasses both *reflection probes* and *irradiance volumes*. In
|
||||
/// object-oriented terms, *light probe* is the superclass, and *reflection
|
||||
/// probe* and *irradiance volume* are subclasses. In other engines, you may see
|
||||
/// the term *light probe* refer to an irradiance volume with a single voxel, or
|
||||
/// perhaps some other technique, while in Bevy *light probe* refers not to a
|
||||
/// specific technique but rather to a class of techniques. Developers familiar
|
||||
/// with other engines should be aware of this terminology difference.
|
||||
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct LightProbe;
|
||||
|
||||
impl LightProbe {
|
||||
/// Creates a new light probe component.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// A pair of cubemap textures that represent the surroundings of a specific
|
||||
/// area in space.
|
||||
///
|
||||
/// See `bevy_pbr::environment_map` for detailed information.
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
pub struct EnvironmentMapLight {
|
||||
/// The blurry image that represents diffuse radiance surrounding a region.
|
||||
pub diffuse_map: Handle<Image>,
|
||||
|
||||
/// The typically-sharper, mipmapped image that represents specular radiance
|
||||
/// surrounding a region.
|
||||
pub specular_map: Handle<Image>,
|
||||
|
||||
/// Scale factor applied to the diffuse and specular light generated by this component.
|
||||
///
|
||||
/// After applying this multiplier, the resulting values should
|
||||
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
|
||||
///
|
||||
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
|
||||
pub intensity: f32,
|
||||
|
||||
/// World space rotation applied to the environment light cubemaps.
|
||||
/// This is useful for users who require a different axis, such as the Z-axis, to serve
|
||||
/// as the vertical axis.
|
||||
pub rotation: Quat,
|
||||
|
||||
/// Whether the light from this environment map contributes diffuse lighting
|
||||
/// to meshes with lightmaps.
|
||||
///
|
||||
/// Set this to false if your lightmap baking tool bakes the diffuse light
|
||||
/// from this environment light into the lightmaps in order to avoid
|
||||
/// counting the radiance from this environment map twice.
|
||||
///
|
||||
/// By default, this is set to true.
|
||||
pub affects_lightmapped_mesh_diffuse: bool,
|
||||
}
|
||||
|
||||
impl Default for EnvironmentMapLight {
|
||||
fn default() -> Self {
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: Handle::default(),
|
||||
specular_map: Handle::default(),
|
||||
intensity: 0.0,
|
||||
rotation: Quat::IDENTITY,
|
||||
affects_lightmapped_mesh_diffuse: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The component that defines an irradiance volume.
|
||||
///
|
||||
/// See `bevy_pbr::irradiance_volume` for detailed information.
|
||||
///
|
||||
/// This component requires the [`LightProbe`] component, and is typically used with
|
||||
/// [`bevy_transform::components::Transform`] to place the volume appropriately.
|
||||
#[derive(Clone, Reflect, Component, Debug)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(LightProbe)]
|
||||
pub struct IrradianceVolume {
|
||||
/// The 3D texture that represents the ambient cubes, encoded in the format
|
||||
/// described in `bevy_pbr::irradiance_volume`.
|
||||
pub voxels: Handle<Image>,
|
||||
|
||||
/// Scale factor applied to the diffuse and specular light generated by this component.
|
||||
///
|
||||
/// After applying this multiplier, the resulting values should
|
||||
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
|
||||
///
|
||||
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
|
||||
pub intensity: f32,
|
||||
|
||||
/// Whether the light from this irradiance volume has an effect on meshes
|
||||
/// with lightmaps.
|
||||
///
|
||||
/// Set this to false if your lightmap baking tool bakes the light from this
|
||||
/// irradiance volume into the lightmaps in order to avoid counting the
|
||||
/// irradiance twice. Frequently, applications use irradiance volumes as a
|
||||
/// lower-quality alternative to lightmaps for capturing indirect
|
||||
/// illumination on dynamic objects, and such applications will want to set
|
||||
/// this value to false.
|
||||
///
|
||||
/// By default, this is set to true.
|
||||
pub affects_lightmapped_meshes: bool,
|
||||
}
|
||||
|
||||
impl Default for IrradianceVolume {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
IrradianceVolume {
|
||||
voxels: Handle::default(),
|
||||
intensity: 0.0,
|
||||
affects_lightmapped_meshes: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,13 @@
|
||||
use bevy_asset::Handle;
|
||||
use bevy_camera::{
|
||||
primitives::Frustum,
|
||||
visibility::{self, Visibility, VisibilityClass},
|
||||
visibility::{self, Visibility, VisibilityClass, VisibleMeshEntities},
|
||||
};
|
||||
use bevy_color::Color;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::Image;
|
||||
use bevy_math::{Mat4, Vec4};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::view::VisibleMeshEntities;
|
||||
use bevy_transform::components::{GlobalTransform, Transform};
|
||||
|
||||
use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects};
|
||||
157
crates/bevy_light/src/volumetric.rs
Normal file
157
crates/bevy_light/src/volumetric.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use bevy_asset::Handle;
|
||||
use bevy_camera::visibility::Visibility;
|
||||
use bevy_color::Color;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::Image;
|
||||
use bevy_math::Vec3;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::components::Transform;
|
||||
|
||||
/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
|
||||
/// (`shadows_enabled: true`) to make volumetric fog interact with it.
|
||||
///
|
||||
/// This allows the light to generate light shafts/god rays.
|
||||
#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
pub struct VolumetricLight;
|
||||
|
||||
/// When placed on a [`bevy_camera::Camera3d`], enables
|
||||
/// volumetric fog and volumetric lighting, also known as light shafts or god
|
||||
/// rays.
|
||||
#[derive(Clone, Copy, Component, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
pub struct VolumetricFog {
|
||||
/// Color of the ambient light.
|
||||
///
|
||||
/// This is separate from Bevy's [`AmbientLight`](crate::AmbientLight) because an
|
||||
/// [`EnvironmentMapLight`](crate::EnvironmentMapLight) is
|
||||
/// still considered an ambient light for the purposes of volumetric fog. If you're using a
|
||||
/// [`EnvironmentMapLight`](crate::EnvironmentMapLight), for best results,
|
||||
/// this should be a good approximation of the average color of the environment map.
|
||||
///
|
||||
/// Defaults to white.
|
||||
pub ambient_color: Color,
|
||||
|
||||
/// The brightness of the ambient light.
|
||||
///
|
||||
/// If there's no [`EnvironmentMapLight`](crate::EnvironmentMapLight),
|
||||
/// set this to 0.
|
||||
///
|
||||
/// Defaults to 0.1.
|
||||
pub ambient_intensity: f32,
|
||||
|
||||
/// The maximum distance to offset the ray origin randomly by, in meters.
|
||||
///
|
||||
/// This is intended for use with temporal antialiasing. It helps fog look
|
||||
/// less blocky by varying the start position of the ray, using interleaved
|
||||
/// gradient noise.
|
||||
pub jitter: f32,
|
||||
|
||||
/// The number of raymarching steps to perform.
|
||||
///
|
||||
/// Higher values produce higher-quality results with less banding, but
|
||||
/// reduce performance.
|
||||
///
|
||||
/// The default value is 64.
|
||||
pub step_count: u32,
|
||||
}
|
||||
|
||||
impl Default for VolumetricFog {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
step_count: 64,
|
||||
// Matches `AmbientLight` defaults.
|
||||
ambient_color: Color::WHITE,
|
||||
ambient_intensity: 0.1,
|
||||
jitter: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct FogVolume {
|
||||
/// The color of the fog.
|
||||
///
|
||||
/// Note that the fog must be lit by a [`VolumetricLight`] or ambient light
|
||||
/// in order for this color to appear.
|
||||
///
|
||||
/// Defaults to white.
|
||||
pub fog_color: Color,
|
||||
|
||||
/// The density of fog, which measures how dark the fog is.
|
||||
///
|
||||
/// The default value is 0.1.
|
||||
pub density_factor: f32,
|
||||
|
||||
/// Optional 3D voxel density texture for the fog.
|
||||
pub density_texture: Option<Handle<Image>>,
|
||||
|
||||
/// Configurable offset of the density texture in UVW coordinates.
|
||||
///
|
||||
/// This can be used to scroll a repeating density texture in a direction over time
|
||||
/// to create effects like fog moving in the wind. Make sure to configure the texture
|
||||
/// to use `ImageAddressMode::Repeat` if this is your intention.
|
||||
///
|
||||
/// Has no effect when no density texture is present.
|
||||
///
|
||||
/// The default value is (0, 0, 0).
|
||||
pub density_texture_offset: Vec3,
|
||||
|
||||
/// The absorption coefficient, which measures what fraction of light is
|
||||
/// absorbed by the fog at each step.
|
||||
///
|
||||
/// Increasing this value makes the fog darker.
|
||||
///
|
||||
/// The default value is 0.3.
|
||||
pub absorption: f32,
|
||||
|
||||
/// The scattering coefficient, which measures the fraction of light that's
|
||||
/// scattered toward, and away from, the viewer.
|
||||
///
|
||||
/// The default value is 0.3.
|
||||
pub scattering: f32,
|
||||
|
||||
/// Measures the fraction of light that's scattered *toward* the camera, as
|
||||
/// opposed to *away* from the camera.
|
||||
///
|
||||
/// Increasing this value makes light shafts become more prominent when the
|
||||
/// camera is facing toward their source and less prominent when the camera
|
||||
/// is facing away. Essentially, a high value here means the light shafts
|
||||
/// will fade into view as the camera focuses on them and fade away when the
|
||||
/// camera is pointing away.
|
||||
///
|
||||
/// The default value is 0.8.
|
||||
pub scattering_asymmetry: f32,
|
||||
|
||||
/// Applies a nonphysical color to the light.
|
||||
///
|
||||
/// This can be useful for artistic purposes but is nonphysical.
|
||||
///
|
||||
/// The default value is white.
|
||||
pub light_tint: Color,
|
||||
|
||||
/// Scales the light by a fixed fraction.
|
||||
///
|
||||
/// This can be useful for artistic purposes but is nonphysical.
|
||||
///
|
||||
/// The default value is 1.0, which results in no adjustment.
|
||||
pub light_intensity: f32,
|
||||
}
|
||||
|
||||
impl Default for FogVolume {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
absorption: 0.3,
|
||||
scattering: 0.3,
|
||||
density_factor: 0.1,
|
||||
density_texture: None,
|
||||
density_texture_offset: Vec3::ZERO,
|
||||
scattering_asymmetry: 0.5,
|
||||
fog_color: Color::WHITE,
|
||||
light_tint: Color::WHITE,
|
||||
light_intensity: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use core::f32::consts::FRAC_1_SQRT_2;
|
||||
use core::fmt;
|
||||
use derive_more::derive::Into;
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
@ -325,6 +326,12 @@ impl core::ops::Mul<Dir2> for Rot2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dir2 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "approx", test))]
|
||||
impl approx::AbsDiffEq for Dir2 {
|
||||
type Epsilon = f32;
|
||||
@ -587,6 +594,12 @@ impl core::ops::Mul<Dir3> for Quat {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dir3 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "approx")]
|
||||
impl approx::AbsDiffEq for Dir3 {
|
||||
type Epsilon = f32;
|
||||
@ -834,6 +847,12 @@ impl core::ops::Mul<Dir3A> for Quat {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dir3A {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "approx")]
|
||||
impl approx::AbsDiffEq for Dir3A {
|
||||
type Epsilon = f32;
|
||||
@ -1022,6 +1041,12 @@ impl core::ops::Mul<Dir4> for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dir4 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "approx")]
|
||||
impl approx::AbsDiffEq for Dir4 {
|
||||
type Epsilon = f32;
|
||||
|
||||
@ -18,7 +18,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_mikktspace = { path = "../bevy_mikktspace", 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_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
|
||||
@ -9,19 +9,19 @@ license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
webgl = []
|
||||
webgpu = []
|
||||
webgl = ["bevy_light/webgl"]
|
||||
webgpu = ["bevy_light/webgpu"]
|
||||
pbr_transmission_textures = []
|
||||
pbr_multi_layer_material_textures = []
|
||||
pbr_anisotropy_texture = []
|
||||
experimental_pbr_pcss = []
|
||||
experimental_pbr_pcss = ["bevy_light/experimental_pbr_pcss"]
|
||||
pbr_specular_textures = []
|
||||
pbr_clustered_decals = []
|
||||
pbr_light_textures = []
|
||||
shader_format_glsl = ["bevy_render/shader_format_glsl"]
|
||||
trace = ["bevy_render/trace"]
|
||||
# Enables the meshlet renderer for dense high-poly scenes (experimental)
|
||||
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"]
|
||||
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"]
|
||||
# Enables processing meshes into meshlet meshes
|
||||
meshlet_processor = [
|
||||
"meshlet",
|
||||
@ -40,15 +40,17 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_light = { path = "../bevy_light", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", 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_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", features = [
|
||||
"bevy_light",
|
||||
], version = "0.17.0-dev" }
|
||||
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
@ -63,14 +65,12 @@ lz4_flex = { version = "0.11", default-features = false, features = [
|
||||
"frame",
|
||||
], optional = true }
|
||||
range-alloc = { version = "0.1.3", optional = true }
|
||||
half = { version = "2", features = ["bytemuck"], optional = true }
|
||||
meshopt = { version = "0.4.1", optional = true }
|
||||
metis = { version = "0.2", optional = true }
|
||||
itertools = { version = "0.14", optional = true }
|
||||
bitvec = { version = "1", optional = true }
|
||||
# direct dependency required for derive macro
|
||||
bytemuck = { version = "1", features = ["derive", "must_cast"] }
|
||||
radsort = "0.1"
|
||||
smallvec = { version = "1", default-features = false }
|
||||
nonmax = "0.5"
|
||||
static_assertions = "1"
|
||||
|
||||
@ -2,6 +2,7 @@ use core::num::NonZero;
|
||||
|
||||
use bevy_camera::Camera;
|
||||
use bevy_ecs::{entity::EntityHashMap, prelude::*};
|
||||
use bevy_light::cluster::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
|
||||
use bevy_math::{uvec4, UVec3, UVec4, Vec4};
|
||||
use bevy_render::{
|
||||
render_resource::{
|
||||
@ -13,7 +14,6 @@ use bevy_render::{
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
use super::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
|
||||
use crate::MeshPipeline;
|
||||
|
||||
// NOTE: this must be kept in sync with the same constants in
|
||||
@ -27,6 +27,8 @@ use bevy_ecs::{
|
||||
system::{Query, Res, ResMut},
|
||||
};
|
||||
use bevy_image::Image;
|
||||
pub use bevy_light::cluster::ClusteredDecal;
|
||||
use bevy_light::{DirectionalLightTexture, PointLightTexture, SpotLightTexture};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_platform::collections::HashMap;
|
||||
pub use bevy_render::primitives::CubemapLayout;
|
||||
@ -47,9 +49,7 @@ use bevy_render::{
|
||||
use bevy_transform::components::GlobalTransform;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
pub use crate::ClusteredDecal;
|
||||
use crate::{binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta};
|
||||
pub use crate::{DirectionalLightTexture, PointLightTexture, SpotLightTexture};
|
||||
|
||||
/// The maximum number of decals that can be present in a view.
|
||||
///
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
use crate::{
|
||||
graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight,
|
||||
MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion,
|
||||
ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
|
||||
graph::NodePbr, irradiance_volume::IrradianceVolume, MeshPipeline, MeshViewBindGroup,
|
||||
RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform,
|
||||
ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
|
||||
ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
|
||||
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
|
||||
};
|
||||
use crate::{
|
||||
DistanceFog, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
|
||||
ViewLightsUniformOffset,
|
||||
};
|
||||
use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
@ -21,6 +18,7 @@ use bevy_core_pipeline::{
|
||||
};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_light::{EnvironmentMapLight, ShadowFilteringMethod};
|
||||
use bevy_render::{
|
||||
extract_component::{
|
||||
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
|
||||
@ -31,7 +31,6 @@ pub mod decal;
|
||||
pub mod deferred;
|
||||
mod extended_material;
|
||||
mod fog;
|
||||
mod light;
|
||||
mod light_probe;
|
||||
mod lightmap;
|
||||
mod material;
|
||||
@ -48,12 +47,19 @@ mod volumetric_fog;
|
||||
use bevy_color::{Color, LinearRgba};
|
||||
|
||||
pub use atmosphere::*;
|
||||
use bevy_light::SimulationLightSystems;
|
||||
pub use bevy_light::{
|
||||
light_consts, AmbientLight, CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades,
|
||||
ClusteredDecal, DirectionalLight, DirectionalLightShadowMap, DirectionalLightTexture,
|
||||
FogVolume, IrradianceVolume, LightPlugin, LightProbe, NotShadowCaster, NotShadowReceiver,
|
||||
PointLight, PointLightShadowMap, PointLightTexture, ShadowFilteringMethod, SpotLight,
|
||||
SpotLightTexture, TransmittedShadowReceiver, VolumetricFog, VolumetricLight,
|
||||
};
|
||||
pub use cluster::*;
|
||||
pub use components::*;
|
||||
pub use decal::clustered::ClusteredDecalPlugin;
|
||||
pub use extended_material::*;
|
||||
pub use fog::*;
|
||||
pub use light::*;
|
||||
pub use light_probe::*;
|
||||
pub use lightmap::*;
|
||||
pub use material::*;
|
||||
@ -65,7 +71,7 @@ pub use prepass::*;
|
||||
pub use render::*;
|
||||
pub use ssao::*;
|
||||
pub use ssr::*;
|
||||
pub use volumetric_fog::{FogVolume, VolumetricFog, VolumetricFogPlugin, VolumetricLight};
|
||||
pub use volumetric_fog::VolumetricFogPlugin;
|
||||
|
||||
/// The PBR prelude.
|
||||
///
|
||||
@ -74,14 +80,17 @@ pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
fog::{DistanceFog, FogFalloff},
|
||||
light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight},
|
||||
light_probe::{environment_map::EnvironmentMapLight, LightProbe},
|
||||
material::{Material, MaterialPlugin},
|
||||
mesh_material::MeshMaterial3d,
|
||||
parallax::ParallaxMappingMethod,
|
||||
pbr_material::StandardMaterial,
|
||||
ssao::ScreenSpaceAmbientOcclusionPlugin,
|
||||
};
|
||||
#[doc(hidden)]
|
||||
pub use bevy_light::{
|
||||
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight, LightProbe, PointLight,
|
||||
SpotLight,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod graph {
|
||||
@ -122,7 +131,6 @@ pub mod graph {
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
|
||||
use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{AssetApp, AssetPath, Assets, Handle};
|
||||
@ -203,8 +211,6 @@ impl Plugin for PbrPlugin {
|
||||
load_shader_library!(app, "meshlet/dummy_visibility_buffer_resolve.wgsl");
|
||||
|
||||
app.register_asset_reflect::<StandardMaterial>()
|
||||
.register_type::<ClusterConfig>()
|
||||
.init_resource::<GlobalVisibleClusterableObjects>()
|
||||
.register_type::<DefaultOpaqueRendererMethod>()
|
||||
.init_resource::<DefaultOpaqueRendererMethod>()
|
||||
.add_plugins((
|
||||
|
||||
@ -44,13 +44,10 @@
|
||||
//!
|
||||
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
|
||||
|
||||
use bevy_asset::{AssetId, Handle};
|
||||
use bevy_ecs::{
|
||||
component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read,
|
||||
};
|
||||
use bevy_asset::AssetId;
|
||||
use bevy_ecs::{query::QueryItem, system::lifetimeless::Read};
|
||||
use bevy_image::Image;
|
||||
use bevy_math::Quat;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_light::EnvironmentMapLight;
|
||||
use bevy_render::{
|
||||
extract_instances::ExtractInstance,
|
||||
render_asset::RenderAssets,
|
||||
@ -72,56 +69,6 @@ use crate::{
|
||||
|
||||
use super::{LightProbeComponent, RenderViewLightProbes};
|
||||
|
||||
/// A pair of cubemap textures that represent the surroundings of a specific
|
||||
/// area in space.
|
||||
///
|
||||
/// See [`crate::environment_map`] for detailed information.
|
||||
#[derive(Clone, Component, Reflect)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
pub struct EnvironmentMapLight {
|
||||
/// The blurry image that represents diffuse radiance surrounding a region.
|
||||
pub diffuse_map: Handle<Image>,
|
||||
|
||||
/// The typically-sharper, mipmapped image that represents specular radiance
|
||||
/// surrounding a region.
|
||||
pub specular_map: Handle<Image>,
|
||||
|
||||
/// Scale factor applied to the diffuse and specular light generated by this component.
|
||||
///
|
||||
/// After applying this multiplier, the resulting values should
|
||||
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
|
||||
///
|
||||
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
|
||||
pub intensity: f32,
|
||||
|
||||
/// World space rotation applied to the environment light cubemaps.
|
||||
/// This is useful for users who require a different axis, such as the Z-axis, to serve
|
||||
/// as the vertical axis.
|
||||
pub rotation: Quat,
|
||||
|
||||
/// Whether the light from this environment map contributes diffuse lighting
|
||||
/// to meshes with lightmaps.
|
||||
///
|
||||
/// Set this to false if your lightmap baking tool bakes the diffuse light
|
||||
/// from this environment light into the lightmaps in order to avoid
|
||||
/// counting the radiance from this environment map twice.
|
||||
///
|
||||
/// By default, this is set to true.
|
||||
pub affects_lightmapped_mesh_diffuse: bool,
|
||||
}
|
||||
|
||||
impl Default for EnvironmentMapLight {
|
||||
fn default() -> Self {
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: Handle::default(),
|
||||
specular_map: Handle::default(),
|
||||
intensity: 0.0,
|
||||
rotation: Quat::IDENTITY,
|
||||
affects_lightmapped_mesh_diffuse: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.
|
||||
///
|
||||
/// This is for use in the render app.
|
||||
|
||||
@ -133,8 +133,8 @@
|
||||
//!
|
||||
//! [Why ambient cubes?]: #why-ambient-cubes
|
||||
|
||||
use bevy_ecs::{component::Component, reflect::ReflectComponent};
|
||||
use bevy_image::Image;
|
||||
pub use bevy_light::IrradianceVolume;
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssets,
|
||||
render_resource::{
|
||||
@ -144,71 +144,22 @@ use bevy_render::{
|
||||
renderer::{RenderAdapter, RenderDevice},
|
||||
texture::{FallbackImage, GpuImage},
|
||||
};
|
||||
use bevy_utils::default;
|
||||
use core::{num::NonZero, ops::Deref};
|
||||
|
||||
use bevy_asset::{AssetId, Handle};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_asset::AssetId;
|
||||
|
||||
use crate::{
|
||||
add_cubemap_texture_view, binding_arrays_are_usable, RenderViewLightProbes,
|
||||
MAX_VIEW_LIGHT_PROBES,
|
||||
};
|
||||
|
||||
use super::{LightProbe, LightProbeComponent};
|
||||
use super::LightProbeComponent;
|
||||
|
||||
/// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can
|
||||
/// overflow the number of texture bindings when deferred rendering is in use
|
||||
/// (see issue #11885).
|
||||
pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "wasm32"));
|
||||
|
||||
/// The component that defines an irradiance volume.
|
||||
///
|
||||
/// See [`crate::irradiance_volume`] for detailed information.
|
||||
///
|
||||
/// This component requires the [`LightProbe`] component, and is typically used with
|
||||
/// [`bevy_transform::components::Transform`] to place the volume appropriately.
|
||||
#[derive(Clone, Reflect, Component, Debug)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(LightProbe)]
|
||||
pub struct IrradianceVolume {
|
||||
/// The 3D texture that represents the ambient cubes, encoded in the format
|
||||
/// described in [`crate::irradiance_volume`].
|
||||
pub voxels: Handle<Image>,
|
||||
|
||||
/// Scale factor applied to the diffuse and specular light generated by this component.
|
||||
///
|
||||
/// After applying this multiplier, the resulting values should
|
||||
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
|
||||
///
|
||||
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
|
||||
pub intensity: f32,
|
||||
|
||||
/// Whether the light from this irradiance volume has an effect on meshes
|
||||
/// with lightmaps.
|
||||
///
|
||||
/// Set this to false if your lightmap baking tool bakes the light from this
|
||||
/// irradiance volume into the lightmaps in order to avoid counting the
|
||||
/// irradiance twice. Frequently, applications use irradiance volumes as a
|
||||
/// lower-quality alternative to lightmaps for capturing indirect
|
||||
/// illumination on dynamic objects, and such applications will want to set
|
||||
/// this value to false.
|
||||
///
|
||||
/// By default, this is set to true.
|
||||
pub affects_lightmapped_meshes: bool,
|
||||
}
|
||||
|
||||
impl Default for IrradianceVolume {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
IrradianceVolume {
|
||||
voxels: default(),
|
||||
intensity: 0.0,
|
||||
affects_lightmapped_meshes: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// All the bind group entries necessary for PBR shaders to access the
|
||||
/// irradiance volumes exposed to a view.
|
||||
pub(crate) enum RenderViewIrradianceVolumeBindGroupEntries<'a> {
|
||||
|
||||
@ -8,15 +8,14 @@ use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
query::With,
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
system::{Commands, Local, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_image::Image;
|
||||
use bevy_light::{EnvironmentMapLight, LightProbe};
|
||||
use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
extract_instances::ExtractInstancesPlugin,
|
||||
load_shader_library,
|
||||
@ -27,7 +26,7 @@ use bevy_render::{
|
||||
settings::WgpuFeatures,
|
||||
sync_world::RenderEntity,
|
||||
texture::{FallbackImage, GpuImage},
|
||||
view::{ExtractedView, Visibility},
|
||||
view::ExtractedView,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_transform::{components::Transform, prelude::GlobalTransform};
|
||||
@ -35,7 +34,7 @@ use tracing::error;
|
||||
|
||||
use core::{hash::Hash, ops::Deref};
|
||||
|
||||
use crate::light_probe::environment_map::{EnvironmentMapIds, EnvironmentMapLight};
|
||||
use crate::light_probe::environment_map::EnvironmentMapIds;
|
||||
|
||||
use self::irradiance_volume::IrradianceVolume;
|
||||
|
||||
@ -59,50 +58,6 @@ const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
|
||||
/// cubemaps applied to all objects that a view renders.
|
||||
pub struct LightProbePlugin;
|
||||
|
||||
/// A marker component for a light probe, which is a cuboid region that provides
|
||||
/// global illumination to all fragments inside it.
|
||||
///
|
||||
/// Note that a light probe will have no effect unless the entity contains some
|
||||
/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
|
||||
/// [`IrradianceVolume`].
|
||||
///
|
||||
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
|
||||
/// origin. The [`Transform`] applied to this entity can scale, rotate, or translate
|
||||
/// that cube so that it contains all fragments that should take this light probe into account.
|
||||
///
|
||||
/// When multiple sources of indirect illumination can be applied to a fragment,
|
||||
/// the highest-quality one is chosen. Diffuse and specular illumination are
|
||||
/// considered separately, so, for example, Bevy may decide to sample the
|
||||
/// diffuse illumination from an irradiance volume and the specular illumination
|
||||
/// from a reflection probe. From highest priority to lowest priority, the
|
||||
/// ranking is as follows:
|
||||
///
|
||||
/// | Rank | Diffuse | Specular |
|
||||
/// | ---- | -------------------- | -------------------- |
|
||||
/// | 1 | Lightmap | Lightmap |
|
||||
/// | 2 | Irradiance volume | Reflection probe |
|
||||
/// | 3 | Reflection probe | View environment map |
|
||||
/// | 4 | View environment map | |
|
||||
///
|
||||
/// Note that ambient light is always added to the diffuse component and does
|
||||
/// not participate in the ranking. That is, ambient light is applied in
|
||||
/// addition to, not instead of, the light sources above.
|
||||
///
|
||||
/// A terminology note: Unfortunately, there is little agreement across game and
|
||||
/// graphics engines as to what to call the various techniques that Bevy groups
|
||||
/// under the term *light probe*. In Bevy, a *light probe* is the generic term
|
||||
/// that encompasses both *reflection probes* and *irradiance volumes*. In
|
||||
/// object-oriented terms, *light probe* is the superclass, and *reflection
|
||||
/// probe* and *irradiance volume* are subclasses. In other engines, you may see
|
||||
/// the term *light probe* refer to an irradiance volume with a single voxel, or
|
||||
/// perhaps some other technique, while in Bevy *light probe* refers not to a
|
||||
/// specific technique but rather to a class of techniques. Developers familiar
|
||||
/// with other engines should be aware of this terminology difference.
|
||||
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct LightProbe;
|
||||
|
||||
/// A GPU type that stores information about a light probe.
|
||||
#[derive(Clone, Copy, ShaderType, Default)]
|
||||
struct RenderLightProbe {
|
||||
@ -302,14 +257,6 @@ pub trait LightProbeComponent: Send + Sync + Component + Sized {
|
||||
) -> RenderViewLightProbes<Self>;
|
||||
}
|
||||
|
||||
impl LightProbe {
|
||||
/// Creates a new light probe component.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// The uniform struct extracted from [`EnvironmentMapLight`].
|
||||
/// Will be available for use in the Environment Map shader.
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
@ -341,10 +288,7 @@ impl Plugin for LightProbePlugin {
|
||||
load_shader_library!(app, "environment_map.wgsl");
|
||||
load_shader_library!(app, "irradiance_volume.wgsl");
|
||||
|
||||
app.register_type::<LightProbe>()
|
||||
.register_type::<EnvironmentMapLight>()
|
||||
.register_type::<IrradianceVolume>()
|
||||
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
|
||||
app.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
|
||||
@ -2,13 +2,14 @@ use super::{
|
||||
instance_manager::InstanceManager, pipelines::MeshletPipelines,
|
||||
resource_manager::ResourceManager,
|
||||
};
|
||||
use crate::{environment_map::EnvironmentMapLight, irradiance_volume::IrradianceVolume, *};
|
||||
use crate::{irradiance_volume::IrradianceVolume, *};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::Camera3d,
|
||||
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
|
||||
tonemapping::{DebandDither, Tonemapping},
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_light::EnvironmentMapLight;
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_render::erased_render_asset::ErasedRenderAssets;
|
||||
use bevy_render::{
|
||||
|
||||
@ -15,7 +15,6 @@ fn vertex(@builtin(vertex_index) vertex_input: u32) -> @builtin(position) vec4<f
|
||||
return vec4(uv_to_ndc(uv), material_depth, 1.0);
|
||||
}
|
||||
|
||||
#ifdef PREPASS_FRAGMENT
|
||||
@fragment
|
||||
fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
|
||||
let vertex_output = resolve_vertex_output(frag_coord);
|
||||
@ -23,7 +22,6 @@ fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32>
|
||||
let color = vec3(rand_f(&rng), rand_f(&rng), rand_f(&rng));
|
||||
return vec4(color, 1.0);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PREPASS_FRAGMENT
|
||||
@fragment
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
use self::assign::ClusterableObjectType;
|
||||
use crate::assign::calculate_cluster_factors;
|
||||
use crate::cascade::{Cascade, CascadeShadowConfig, Cascades};
|
||||
use crate::*;
|
||||
use bevy_asset::UntypedAssetId;
|
||||
pub use bevy_camera::primitives::{face_index_to_name, CubeMapFace, CUBE_MAP_FACES};
|
||||
@ -14,6 +11,13 @@ use bevy_ecs::{
|
||||
prelude::*,
|
||||
system::lifetimeless::Read,
|
||||
};
|
||||
use bevy_light::cascade::Cascade;
|
||||
use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
|
||||
use bevy_light::cluster::GlobalVisibleClusterableObjects;
|
||||
use bevy_light::{
|
||||
spot_light_clip_from_view, spot_light_world_from_view, DirectionalLightShadowMap,
|
||||
NotShadowCaster, PointLightShadowMap,
|
||||
};
|
||||
use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_platform::hash::FixedHasher;
|
||||
@ -798,7 +802,7 @@ pub fn prepare_lights(
|
||||
// - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
|
||||
point_lights.sort_by_cached_key(|(entity, _, light, _)| {
|
||||
(
|
||||
ClusterableObjectType::from_point_or_spot_light(light).ordering(),
|
||||
point_or_spot_light_to_clusterable(light).ordering(),
|
||||
*entity,
|
||||
)
|
||||
});
|
||||
@ -2265,3 +2269,18 @@ impl ShadowPassNode {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the [`ClusterableObjectType`] data for a point or spot light.
|
||||
fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
|
||||
match point_light.spot_light_angles {
|
||||
Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
|
||||
outer_angle,
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
volumetric: point_light.volumetric,
|
||||
},
|
||||
None => ClusterableObjectType::PointLight {
|
||||
shadows_enabled: point_light.shadows_enabled,
|
||||
volumetric: point_light.volumetric,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,9 @@ use bevy_ecs::{
|
||||
system::{lifetimeless::*, SystemParamItem, SystemState},
|
||||
};
|
||||
use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo};
|
||||
use bevy_light::{
|
||||
EnvironmentMapLight, NotShadowCaster, NotShadowReceiver, TransmittedShadowReceiver,
|
||||
};
|
||||
use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4};
|
||||
use bevy_platform::collections::{hash_map::Entry, HashMap};
|
||||
use bevy_render::{
|
||||
@ -52,7 +55,6 @@ use material_bind_groups::MaterialBindingId;
|
||||
use tracing::{error, warn};
|
||||
|
||||
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
|
||||
use crate::environment_map::EnvironmentMapLight;
|
||||
use crate::irradiance_volume::IrradianceVolume;
|
||||
use crate::{
|
||||
render::{
|
||||
|
||||
@ -17,6 +17,7 @@ use bevy_ecs::{
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_light::EnvironmentMapLight;
|
||||
use bevy_math::Vec4;
|
||||
use bevy_render::{
|
||||
globals::{GlobalsBuffer, GlobalsUniform},
|
||||
@ -30,7 +31,6 @@ use bevy_render::{
|
||||
},
|
||||
};
|
||||
use core::{array, num::NonZero};
|
||||
use environment_map::EnvironmentMapLight;
|
||||
|
||||
use crate::{
|
||||
decal::{
|
||||
|
||||
@ -6,14 +6,14 @@
|
||||
//! for light beams from directional lights to shine through, creating what is
|
||||
//! known as *light shafts* or *god rays*.
|
||||
//!
|
||||
//! To add volumetric fog to a scene, add [`VolumetricFog`] to the
|
||||
//! camera, and add [`VolumetricLight`] to directional lights that you wish to
|
||||
//! be volumetric. [`VolumetricFog`] feature numerous settings that
|
||||
//! To add volumetric fog to a scene, add [`crate::VolumetricFog`] to the
|
||||
//! camera, and add [`crate::VolumetricLight`] to directional lights that you wish to
|
||||
//! be volumetric. [`crate::VolumetricFog`] feature numerous settings that
|
||||
//! allow you to define the accuracy of the simulation, as well as the look of
|
||||
//! the fog. Currently, only interaction with directional lights that have
|
||||
//! shadow maps is supported. Note that the overhead of the effect scales
|
||||
//! directly with the number of directional lights in use, so apply
|
||||
//! [`VolumetricLight`] sparingly for the best results.
|
||||
//! [`crate::VolumetricLight`] sparingly for the best results.
|
||||
//!
|
||||
//! The overall algorithm, which is implemented as a postprocessing effect, is a
|
||||
//! combination of the techniques described in [Scratchapixel] and [this blog
|
||||
@ -30,30 +30,24 @@
|
||||
//! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{embedded_asset, Assets, Handle};
|
||||
use bevy_color::Color;
|
||||
use bevy_asset::{embedded_asset, Assets};
|
||||
use bevy_core_pipeline::core_3d::{
|
||||
graph::{Core3d, Node3d},
|
||||
prepare_core_3d_depth_textures,
|
||||
};
|
||||
use bevy_ecs::{
|
||||
component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs as _,
|
||||
};
|
||||
use bevy_image::Image;
|
||||
use bevy_ecs::schedule::IntoScheduleConfigs as _;
|
||||
use bevy_light::FogVolume;
|
||||
use bevy_math::{
|
||||
primitives::{Cuboid, Plane3d},
|
||||
Vec2, Vec3,
|
||||
};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
mesh::{Mesh, Meshable},
|
||||
render_graph::{RenderGraphExt, ViewNodeRunner},
|
||||
render_resource::SpecializedRenderPipelines,
|
||||
sync_component::SyncComponentPlugin,
|
||||
view::Visibility,
|
||||
ExtractSchedule, Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_transform::components::Transform;
|
||||
use render::{
|
||||
VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH,
|
||||
};
|
||||
@ -65,127 +59,6 @@ pub mod render;
|
||||
/// A plugin that implements volumetric fog.
|
||||
pub struct VolumetricFogPlugin;
|
||||
|
||||
/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
|
||||
/// (`shadows_enabled: true`) to make volumetric fog interact with it.
|
||||
///
|
||||
/// This allows the light to generate light shafts/god rays.
|
||||
#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
pub struct VolumetricLight;
|
||||
|
||||
/// When placed on a [`bevy_core_pipeline::core_3d::Camera3d`], enables
|
||||
/// volumetric fog and volumetric lighting, also known as light shafts or god
|
||||
/// rays.
|
||||
#[derive(Clone, Copy, Component, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
pub struct VolumetricFog {
|
||||
/// Color of the ambient light.
|
||||
///
|
||||
/// This is separate from Bevy's [`AmbientLight`](crate::light::AmbientLight) because an
|
||||
/// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight) is
|
||||
/// still considered an ambient light for the purposes of volumetric fog. If you're using a
|
||||
/// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight), for best results,
|
||||
/// this should be a good approximation of the average color of the environment map.
|
||||
///
|
||||
/// Defaults to white.
|
||||
pub ambient_color: Color,
|
||||
|
||||
/// The brightness of the ambient light.
|
||||
///
|
||||
/// If there's no [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight),
|
||||
/// set this to 0.
|
||||
///
|
||||
/// Defaults to 0.1.
|
||||
pub ambient_intensity: f32,
|
||||
|
||||
/// The maximum distance to offset the ray origin randomly by, in meters.
|
||||
///
|
||||
/// This is intended for use with temporal antialiasing. It helps fog look
|
||||
/// less blocky by varying the start position of the ray, using interleaved
|
||||
/// gradient noise.
|
||||
pub jitter: f32,
|
||||
|
||||
/// The number of raymarching steps to perform.
|
||||
///
|
||||
/// Higher values produce higher-quality results with less banding, but
|
||||
/// reduce performance.
|
||||
///
|
||||
/// The default value is 64.
|
||||
pub step_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Component, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[require(Transform, Visibility)]
|
||||
pub struct FogVolume {
|
||||
/// The color of the fog.
|
||||
///
|
||||
/// Note that the fog must be lit by a [`VolumetricLight`] or ambient light
|
||||
/// in order for this color to appear.
|
||||
///
|
||||
/// Defaults to white.
|
||||
pub fog_color: Color,
|
||||
|
||||
/// The density of fog, which measures how dark the fog is.
|
||||
///
|
||||
/// The default value is 0.1.
|
||||
pub density_factor: f32,
|
||||
|
||||
/// Optional 3D voxel density texture for the fog.
|
||||
pub density_texture: Option<Handle<Image>>,
|
||||
|
||||
/// Configurable offset of the density texture in UVW coordinates.
|
||||
///
|
||||
/// This can be used to scroll a repeating density texture in a direction over time
|
||||
/// to create effects like fog moving in the wind. Make sure to configure the texture
|
||||
/// to use `ImageAddressMode::Repeat` if this is your intention.
|
||||
///
|
||||
/// Has no effect when no density texture is present.
|
||||
///
|
||||
/// The default value is (0, 0, 0).
|
||||
pub density_texture_offset: Vec3,
|
||||
|
||||
/// The absorption coefficient, which measures what fraction of light is
|
||||
/// absorbed by the fog at each step.
|
||||
///
|
||||
/// Increasing this value makes the fog darker.
|
||||
///
|
||||
/// The default value is 0.3.
|
||||
pub absorption: f32,
|
||||
|
||||
/// The scattering coefficient, which measures the fraction of light that's
|
||||
/// scattered toward, and away from, the viewer.
|
||||
///
|
||||
/// The default value is 0.3.
|
||||
pub scattering: f32,
|
||||
|
||||
/// Measures the fraction of light that's scattered *toward* the camera, as
|
||||
/// opposed to *away* from the camera.
|
||||
///
|
||||
/// Increasing this value makes light shafts become more prominent when the
|
||||
/// camera is facing toward their source and less prominent when the camera
|
||||
/// is facing away. Essentially, a high value here means the light shafts
|
||||
/// will fade into view as the camera focuses on them and fade away when the
|
||||
/// camera is pointing away.
|
||||
///
|
||||
/// The default value is 0.8.
|
||||
pub scattering_asymmetry: f32,
|
||||
|
||||
/// Applies a nonphysical color to the light.
|
||||
///
|
||||
/// This can be useful for artistic purposes but is nonphysical.
|
||||
///
|
||||
/// The default value is white.
|
||||
pub light_tint: Color,
|
||||
|
||||
/// Scales the light by a fixed fraction.
|
||||
///
|
||||
/// This can be useful for artistic purposes but is nonphysical.
|
||||
///
|
||||
/// The default value is 1.0, which results in no adjustment.
|
||||
pub light_intensity: f32,
|
||||
}
|
||||
|
||||
impl Plugin for VolumetricFogPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
embedded_asset!(app, "volumetric_fog.wgsl");
|
||||
@ -194,9 +67,6 @@ impl Plugin for VolumetricFogPlugin {
|
||||
meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into());
|
||||
meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into());
|
||||
|
||||
app.register_type::<VolumetricFog>()
|
||||
.register_type::<VolumetricLight>();
|
||||
|
||||
app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
@ -238,31 +108,3 @@ impl Plugin for VolumetricFogPlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VolumetricFog {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
step_count: 64,
|
||||
// Matches `AmbientLight` defaults.
|
||||
ambient_color: Color::WHITE,
|
||||
ambient_intensity: 0.1,
|
||||
jitter: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FogVolume {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
absorption: 0.3,
|
||||
scattering: 0.3,
|
||||
density_factor: 0.1,
|
||||
density_texture: None,
|
||||
density_texture_offset: Vec3::ZERO,
|
||||
scattering_asymmetry: 0.5,
|
||||
fog_color: Color::WHITE,
|
||||
light_tint: Color::WHITE,
|
||||
light_intensity: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
|
||||
@ -344,7 +344,7 @@ pub fn update_is_hovered(
|
||||
}
|
||||
|
||||
// Algorithm: for each entity having a `Hovered` component, we want to know if the current
|
||||
// entry in the hover map is "within" (that is, in the set of descenants of) that entity. Rather
|
||||
// entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather
|
||||
// than doing an expensive breadth-first traversal of children, instead start with the hovermap
|
||||
// entry and search upwards. We can make this even cheaper by building a set of ancestors for
|
||||
// the hovermap entry, and then testing each `Hovered` entity against that set.
|
||||
|
||||
@ -39,24 +39,30 @@ pub mod prelude {
|
||||
pub use crate::input::PointerInputPlugin;
|
||||
}
|
||||
|
||||
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
|
||||
/// that you can replace with your own plugin as needed.
|
||||
///
|
||||
/// [`crate::PickingPlugin::is_input_enabled`] can be used to toggle whether
|
||||
/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
|
||||
///
|
||||
/// This plugin contains several settings, and is added to the world as a resource after initialization.
|
||||
/// You can configure pointer input settings at runtime by accessing the resource.
|
||||
#[derive(Copy, Clone, Resource, Debug, Reflect)]
|
||||
#[reflect(Resource, Default, Clone)]
|
||||
pub struct PointerInputPlugin {
|
||||
/// Settings for enabling and disabling updating mouse and touch inputs for picking
|
||||
///
|
||||
/// ## Custom initialization
|
||||
/// ```
|
||||
/// # use bevy_app::App;
|
||||
/// # use bevy_picking::input::{PointerInputSettings,PointerInputPlugin};
|
||||
/// App::new()
|
||||
/// .insert_resource(PointerInputSettings {
|
||||
/// is_touch_enabled: false,
|
||||
/// is_mouse_enabled: true,
|
||||
/// })
|
||||
/// // or DefaultPlugins
|
||||
/// .add_plugins(PointerInputPlugin);
|
||||
/// ```
|
||||
pub struct PointerInputSettings {
|
||||
/// Should touch inputs be updated?
|
||||
pub is_touch_enabled: bool,
|
||||
/// Should mouse inputs be updated?
|
||||
pub is_mouse_enabled: bool,
|
||||
}
|
||||
|
||||
impl PointerInputPlugin {
|
||||
impl PointerInputSettings {
|
||||
fn is_mouse_enabled(state: Res<Self>) -> bool {
|
||||
state.is_mouse_enabled
|
||||
}
|
||||
@ -66,7 +72,7 @@ impl PointerInputPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PointerInputPlugin {
|
||||
impl Default for PointerInputSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_touch_enabled: true,
|
||||
@ -75,25 +81,35 @@ impl Default for PointerInputPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
|
||||
/// that you can replace with your own plugin as needed.
|
||||
///
|
||||
/// Toggling mouse input or touch input can be done at runtime by modifying
|
||||
/// [`PointerInputSettings`] resource.
|
||||
///
|
||||
/// [`PointerInputSettings`] can be initialized with custom values, but will be
|
||||
/// initialized with default values if it is not present at the moment this is
|
||||
/// added to the app.
|
||||
pub struct PointerInputPlugin;
|
||||
|
||||
impl Plugin for PointerInputPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(*self)
|
||||
app.init_resource::<PointerInputSettings>()
|
||||
.register_type::<PointerInputSettings>()
|
||||
.add_systems(Startup, spawn_mouse_pointer)
|
||||
.add_systems(
|
||||
First,
|
||||
(
|
||||
mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
|
||||
touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
|
||||
mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
|
||||
touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
|
||||
)
|
||||
.chain()
|
||||
.in_set(PickingSystems::Input),
|
||||
)
|
||||
.add_systems(
|
||||
Last,
|
||||
deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
|
||||
)
|
||||
.register_type::<Self>()
|
||||
.register_type::<PointerInputPlugin>();
|
||||
deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -293,20 +293,31 @@ pub struct DefaultPickingPlugins;
|
||||
impl PluginGroup for DefaultPickingPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(input::PointerInputPlugin::default())
|
||||
.add(PickingPlugin::default())
|
||||
.add(input::PointerInputPlugin)
|
||||
.add(PickingPlugin)
|
||||
.add(InteractionPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
|
||||
/// types used by other picking plugins.
|
||||
///
|
||||
/// This plugin contains several settings, and is added to the world as a resource after initialization. You
|
||||
/// can configure picking settings at runtime through the resource.
|
||||
#[derive(Copy, Clone, Debug, Resource, Reflect)]
|
||||
#[reflect(Resource, Default, Debug, Clone)]
|
||||
pub struct PickingPlugin {
|
||||
/// Controls the behavior of picking
|
||||
///
|
||||
/// ## Custom initialization
|
||||
/// ```
|
||||
/// # use bevy_app::App;
|
||||
/// # use bevy_picking::{PickingSettings, PickingPlugin};
|
||||
/// App::new()
|
||||
/// .insert_resource(PickingSettings {
|
||||
/// is_enabled: true,
|
||||
/// is_input_enabled: false,
|
||||
/// is_hover_enabled: true,
|
||||
/// is_window_picking_enabled: false,
|
||||
/// })
|
||||
/// // or DefaultPlugins
|
||||
/// .add_plugins(PickingPlugin);
|
||||
/// ```
|
||||
pub struct PickingSettings {
|
||||
/// Enables and disables all picking features.
|
||||
pub is_enabled: bool,
|
||||
/// Enables and disables input collection.
|
||||
@ -317,7 +328,7 @@ pub struct PickingPlugin {
|
||||
pub is_window_picking_enabled: bool,
|
||||
}
|
||||
|
||||
impl PickingPlugin {
|
||||
impl PickingSettings {
|
||||
/// Whether or not input collection systems should be running.
|
||||
pub fn input_should_run(state: Res<Self>) -> bool {
|
||||
state.is_input_enabled && state.is_enabled
|
||||
@ -335,7 +346,7 @@ impl PickingPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PickingPlugin {
|
||||
impl Default for PickingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_enabled: true,
|
||||
@ -346,9 +357,18 @@ impl Default for PickingPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
|
||||
/// types used by other picking plugins.
|
||||
///
|
||||
/// Behavior of picking can be controlled by modifying [`PickingSettings`].
|
||||
///
|
||||
/// [`PickingSettings`] will be initialized with default values if it
|
||||
/// is not present at the moment this is added to the app.
|
||||
pub struct PickingPlugin;
|
||||
|
||||
impl Plugin for PickingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(*self)
|
||||
app.init_resource::<PickingSettings>()
|
||||
.init_resource::<pointer::PointerMap>()
|
||||
.init_resource::<backend::ray::RayMap>()
|
||||
.add_event::<pointer::PointerInput>()
|
||||
@ -369,7 +389,7 @@ impl Plugin for PickingPlugin {
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
window::update_window_hits
|
||||
.run_if(Self::window_picking_should_run)
|
||||
.run_if(PickingSettings::window_picking_should_run)
|
||||
.in_set(PickingSystems::Backend),
|
||||
)
|
||||
.configure_sets(
|
||||
@ -382,15 +402,15 @@ impl Plugin for PickingPlugin {
|
||||
.configure_sets(
|
||||
PreUpdate,
|
||||
(
|
||||
PickingSystems::ProcessInput.run_if(Self::input_should_run),
|
||||
PickingSystems::ProcessInput.run_if(PickingSettings::input_should_run),
|
||||
PickingSystems::Backend,
|
||||
PickingSystems::Hover.run_if(Self::hover_should_run),
|
||||
PickingSystems::Hover.run_if(PickingSettings::hover_should_run),
|
||||
PickingSystems::PostHover,
|
||||
PickingSystems::Last,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.register_type::<Self>()
|
||||
.register_type::<PickingSettings>()
|
||||
.register_type::<Pickable>()
|
||||
.register_type::<hover::PickingInteraction>()
|
||||
.register_type::<hover::Hovered>()
|
||||
|
||||
@ -9,6 +9,7 @@ mod structs {
|
||||
|
||||
#[reflect_remote(external_crate::TheirStruct)]
|
||||
//~^ ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: mismatched types
|
||||
struct MyStruct {
|
||||
// Reason: Should be `u32`
|
||||
pub value: bool,
|
||||
@ -25,6 +26,7 @@ mod tuple_structs {
|
||||
|
||||
#[reflect_remote(external_crate::TheirStruct)]
|
||||
//~^ ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: mismatched types
|
||||
struct MyStruct(
|
||||
// Reason: Should be `u32`
|
||||
pub bool,
|
||||
@ -48,6 +50,7 @@ mod enums {
|
||||
//~| ERROR: variant `enums::external_crate::TheirStruct::Unit` has no field named `0`
|
||||
//~| ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: mismatched types
|
||||
enum MyStruct {
|
||||
// Reason: Should be unit variant
|
||||
Unit(i32),
|
||||
@ -57,6 +60,7 @@ mod enums {
|
||||
// Reason: Should be `usize`
|
||||
Struct { value: String },
|
||||
//~^ ERROR: mismatched types
|
||||
//~| ERROR: mismatched types
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -26,8 +26,8 @@ mod incorrect_inner_type {
|
||||
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
|
||||
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
|
||||
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
|
||||
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
|
||||
//~| ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: mismatched types
|
||||
struct MyOuter<T: FromReflect + GetTypeRegistration> {
|
||||
// Reason: Should not use `MyInner<T>` directly
|
||||
pub inner: MyInner<T>,
|
||||
|
||||
@ -77,6 +77,7 @@ mod enums {
|
||||
|
||||
#[reflect_remote(external_crate::TheirBar)]
|
||||
//~^ ERROR: `?` operator has incompatible types
|
||||
//~| ERROR: mismatched types
|
||||
enum MyBar {
|
||||
// Reason: Should use `i32`
|
||||
Value(u32),
|
||||
|
||||
@ -24,7 +24,6 @@ indexmap = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full", "extra-traits"] }
|
||||
uuid = { version = "1.13.1", features = ["v4"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
|
||||
@ -722,18 +722,7 @@ impl<'a> ReflectStruct<'a> {
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#bevy_reflect_path::PartialReflect::reflect_clone(#accessor)?
|
||||
.take()
|
||||
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
|
||||
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
|
||||
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
|
||||
),
|
||||
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
|
||||
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
|
||||
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
|
||||
)
|
||||
),
|
||||
})?
|
||||
<#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#accessor)?
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -316,9 +316,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
|
||||
|
||||
fn construct_field(&self, field: VariantField) -> TokenStream {
|
||||
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
|
||||
|
||||
let field_ty = field.field.reflected_type();
|
||||
|
||||
let alias = field.alias;
|
||||
let alias = match &field.field.attrs.remote {
|
||||
Some(wrapper_ty) => {
|
||||
@ -332,18 +330,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
|
||||
match &field.field.attrs.clone {
|
||||
CloneBehavior::Default => {
|
||||
quote! {
|
||||
#bevy_reflect_path::PartialReflect::reflect_clone(#alias)?
|
||||
.take()
|
||||
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
|
||||
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
|
||||
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
|
||||
),
|
||||
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
|
||||
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
|
||||
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
|
||||
)
|
||||
),
|
||||
})?
|
||||
<#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#alias)?
|
||||
}
|
||||
}
|
||||
CloneBehavior::Trait => {
|
||||
|
||||
@ -231,11 +231,11 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
|
||||
/// // Generates a where clause like:
|
||||
/// // impl bevy_reflect::Reflect for Foo
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // Vec<Foo>: FromReflect + TypePath,
|
||||
/// // Foo: Any + Send + Sync,
|
||||
/// // Vec<Foo>: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||
/// ```
|
||||
///
|
||||
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + TypePath`,
|
||||
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + ...`,
|
||||
/// which requires that `Foo` implements `FromReflect`,
|
||||
/// which requires that `Vec<Foo>` implements `FromReflect`,
|
||||
/// and so on, resulting in the error.
|
||||
@ -283,10 +283,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
|
||||
/// //
|
||||
/// // impl<T: Trait> bevy_reflect::Reflect for Foo<T>
|
||||
/// // where
|
||||
/// // Self: Any + Send + Sync,
|
||||
/// // Foo<T>: Any + Send + Sync,
|
||||
/// // T::Assoc: Default,
|
||||
/// // T: TypePath,
|
||||
/// // T::Assoc: FromReflect + TypePath,
|
||||
/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||
/// // T::Assoc: List,
|
||||
/// // {/* ... */}
|
||||
/// ```
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use crate::derive_data::ReflectMeta;
|
||||
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{TokenStream, TokenTree};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{punctuated::Punctuated, Token, Type, WhereClause};
|
||||
use syn::{punctuated::Punctuated, Ident, Token, Type, WhereClause};
|
||||
|
||||
/// Options defining how to extend the `where` clause for reflection.
|
||||
pub(crate) struct WhereClauseOptions<'a, 'b> {
|
||||
@ -29,15 +29,24 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
self.meta
|
||||
}
|
||||
|
||||
/// Extends the `where` clause for a type with additional bounds needed for the reflection impls.
|
||||
/// Extends the `where` clause for a type with additional bounds needed for the reflection
|
||||
/// impls.
|
||||
///
|
||||
/// The default bounds added are as follows:
|
||||
/// - `Self` has the bounds `Any + Send + Sync`
|
||||
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present
|
||||
/// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present
|
||||
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present)
|
||||
/// - `Self` has:
|
||||
/// - `Any + Send + Sync` bounds, if generic over types
|
||||
/// - An `Any` bound, if generic over lifetimes but not types
|
||||
/// - No bounds, if generic over neither types nor lifetimes
|
||||
/// - Any given bounds in a `where` clause on the type
|
||||
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is
|
||||
/// present
|
||||
/// - Active fields with non-generic types have the bounds `TypePath`, either `PartialReflect`
|
||||
/// if `#[reflect(from_reflect = false)]` is present or `FromReflect` otherwise,
|
||||
/// `MaybeTyped`, and `RegisterForReflection` (or no bounds at all if
|
||||
/// `#[reflect(no_field_bounds)]` is present)
|
||||
///
|
||||
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well.
|
||||
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are
|
||||
/// added as well.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@ -55,57 +64,69 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// Foo<T, U>: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Field bounds
|
||||
/// T: FromReflect + TypePath,
|
||||
/// // Active non-generic field bounds
|
||||
/// T: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate:
|
||||
/// If we add various things to the type:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// #[derive(Reflect)]
|
||||
/// #[reflect(where T: MyTrait)]
|
||||
/// #[reflect(no_field_bounds)]
|
||||
/// struct Foo<T, U>
|
||||
/// where T: Clone
|
||||
/// {
|
||||
/// a: T,
|
||||
/// #[reflect(ignore)]
|
||||
/// b: U
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// It will instead generate the following where clause:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // Field bounds
|
||||
/// T: FromReflect + TypePath,
|
||||
/// // Custom bounds
|
||||
/// T: MyTrait,
|
||||
/// ```
|
||||
///
|
||||
/// And if we also added `#[reflect(no_field_bounds)]` to the type, it would instead generate:
|
||||
///
|
||||
/// ```ignore (bevy_reflect is not accessible from this crate)
|
||||
/// where
|
||||
/// // `Self` bounds:
|
||||
/// Self: Any + Send + Sync,
|
||||
/// Foo<T, U>: Any + Send + Sync,
|
||||
/// // Given bounds:
|
||||
/// T: Clone,
|
||||
/// // Type parameter bounds:
|
||||
/// T: TypePath,
|
||||
/// U: TypePath,
|
||||
/// // No active non-generic field bounds
|
||||
/// // Custom bounds
|
||||
/// T: MyTrait,
|
||||
/// ```
|
||||
pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream {
|
||||
// We would normally just use `Self`, but that won't work for generating things like assertion functions
|
||||
// and trait impls for a type's reference (e.g. `impl FromArg for &MyType`)
|
||||
let this = self.meta.type_path().true_type();
|
||||
let mut generic_where_clause = quote! { where };
|
||||
|
||||
let required_bounds = self.required_bounds();
|
||||
// Bounds on `Self`. We would normally just use `Self`, but that won't work for generating
|
||||
// things like assertion functions and trait impls for a type's reference (e.g. `impl
|
||||
// FromArg for &MyType`).
|
||||
let generics = self.meta.type_path().generics();
|
||||
if generics.type_params().next().is_some() {
|
||||
// Generic over types? We need `Any + Send + Sync`.
|
||||
let this = self.meta.type_path().true_type();
|
||||
generic_where_clause.extend(quote! { #this: #FQAny + #FQSend + #FQSync, });
|
||||
} else if generics.lifetimes().next().is_some() {
|
||||
// Generic only over lifetimes? We need `'static`.
|
||||
let this = self.meta.type_path().true_type();
|
||||
generic_where_clause.extend(quote! { #this: 'static, });
|
||||
}
|
||||
|
||||
// Maintain existing where clause, if any.
|
||||
let mut generic_where_clause = if let Some(where_clause) = where_clause {
|
||||
// Maintain existing where clause bounds, if any.
|
||||
if let Some(where_clause) = where_clause {
|
||||
let predicates = where_clause.predicates.iter();
|
||||
quote! {where #this: #required_bounds, #(#predicates,)*}
|
||||
} else {
|
||||
quote!(where #this: #required_bounds,)
|
||||
};
|
||||
generic_where_clause.extend(quote! { #(#predicates,)* });
|
||||
}
|
||||
|
||||
// Add additional reflection trait bounds
|
||||
// Add additional reflection trait bounds.
|
||||
let predicates = self.predicates();
|
||||
generic_where_clause.extend(quote! {
|
||||
#predicates
|
||||
@ -157,19 +178,57 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
let bevy_reflect_path = self.meta.bevy_reflect_path();
|
||||
let reflect_bound = self.reflect_bound();
|
||||
|
||||
// `TypePath` is always required for active fields since they are used to
|
||||
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl.
|
||||
// Likewise, `GetTypeRegistration` is always required for active fields since
|
||||
// they are used to register the type's dependencies.
|
||||
Some(self.active_fields.iter().map(move |ty| {
|
||||
quote!(
|
||||
#ty : #reflect_bound
|
||||
+ #bevy_reflect_path::TypePath
|
||||
// Needed for `Typed` impls
|
||||
+ #bevy_reflect_path::MaybeTyped
|
||||
// Needed for `GetTypeRegistration` impls
|
||||
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
|
||||
)
|
||||
// Get the identifiers of all type parameters.
|
||||
let type_param_idents = self
|
||||
.meta
|
||||
.type_path()
|
||||
.generics()
|
||||
.type_params()
|
||||
.map(|type_param| type_param.ident.clone())
|
||||
.collect::<Vec<Ident>>();
|
||||
|
||||
// Do any of the identifiers in `idents` appear in `token_stream`?
|
||||
fn is_any_ident_in_token_stream(idents: &[Ident], token_stream: TokenStream) -> bool {
|
||||
for token_tree in token_stream {
|
||||
match token_tree {
|
||||
TokenTree::Ident(ident) => {
|
||||
if idents.contains(&ident) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TokenTree::Group(group) => {
|
||||
if is_any_ident_in_token_stream(idents, group.stream()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
Some(self.active_fields.iter().filter_map(move |ty| {
|
||||
// Field type bounds are only required if `ty` is generic. How to determine that?
|
||||
// Search `ty`s token stream for identifiers that match the identifiers from the
|
||||
// function's type params. E.g. if `T` and `U` are the type param identifiers and
|
||||
// `ty` is `Vec<[T; 4]>` then the `T` identifiers match. This is a bit hacky, but
|
||||
// it works.
|
||||
let is_generic =
|
||||
is_any_ident_in_token_stream(&type_param_idents, ty.to_token_stream());
|
||||
|
||||
is_generic.then(|| {
|
||||
quote!(
|
||||
#ty: #reflect_bound
|
||||
// Needed to construct `NamedField` and `UnnamedField` instances for
|
||||
// the `Typed` impl.
|
||||
+ #bevy_reflect_path::TypePath
|
||||
// Needed for `Typed` impls
|
||||
+ #bevy_reflect_path::MaybeTyped
|
||||
// Needed for registering type dependencies in the
|
||||
// `GetTypeRegistration` impl.
|
||||
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
|
||||
)
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -194,9 +253,4 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum required bounds for a type to be reflected.
|
||||
fn required_bounds(&self) -> TokenStream {
|
||||
quote!(#FQAny + #FQSend + #FQSync)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ use crate::{
|
||||
type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration},
|
||||
utility::GenericTypeInfoCell,
|
||||
};
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::vec::Vec;
|
||||
use bevy_platform::prelude::*;
|
||||
use bevy_reflect_derive::impl_type_path;
|
||||
@ -144,21 +143,8 @@ where
|
||||
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
|
||||
let mut map = Self::new();
|
||||
for (key, value) in self.iter() {
|
||||
let key =
|
||||
key.reflect_clone()?
|
||||
.take()
|
||||
.map_err(|_| ReflectCloneError::FailedDowncast {
|
||||
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
|
||||
received: Cow::Owned(key.reflect_type_path().to_string()),
|
||||
})?;
|
||||
let value =
|
||||
value
|
||||
.reflect_clone()?
|
||||
.take()
|
||||
.map_err(|_| ReflectCloneError::FailedDowncast {
|
||||
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
|
||||
received: Cow::Owned(value.reflect_type_path().to_string()),
|
||||
})?;
|
||||
let key = key.reflect_clone_and_take()?;
|
||||
let value = value.reflect_clone_and_take()?;
|
||||
map.insert(key, value);
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@ use crate::{
|
||||
};
|
||||
use bevy_platform::prelude::*;
|
||||
use bevy_reflect_derive::impl_type_path;
|
||||
use core::any::Any;
|
||||
use core::fmt;
|
||||
|
||||
macro_rules! impl_reflect_for_atomic {
|
||||
@ -21,10 +20,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
#[cfg(feature = "functions")]
|
||||
crate::func::macros::impl_function_traits!($ty);
|
||||
|
||||
impl GetTypeRegistration for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl GetTypeRegistration for $ty {
|
||||
fn get_type_registration() -> TypeRegistration {
|
||||
let mut registration = TypeRegistration::of::<Self>();
|
||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
||||
@ -42,10 +38,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl Typed for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl Typed for $ty {
|
||||
fn type_info() -> &'static TypeInfo {
|
||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
||||
CELL.get_or_set(|| {
|
||||
@ -55,10 +48,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialReflect for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl PartialReflect for $ty {
|
||||
#[inline]
|
||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
||||
Some(<Self as Typed>::type_info())
|
||||
@ -128,10 +118,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromReflect for $ty
|
||||
where
|
||||
$ty: Any + Send + Sync,
|
||||
{
|
||||
impl FromReflect for $ty {
|
||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
||||
Some(<$ty>::new(
|
||||
reflect.try_downcast_ref::<$ty>()?.load($ordering),
|
||||
@ -140,7 +127,7 @@ macro_rules! impl_reflect_for_atomic {
|
||||
}
|
||||
};
|
||||
|
||||
impl_full_reflect!(for $ty where $ty: Any + Send + Sync);
|
||||
impl_full_reflect!(for $ty);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -113,14 +113,7 @@ macro_rules! impl_reflect_for_veclike {
|
||||
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
|
||||
Ok(bevy_platform::prelude::Box::new(
|
||||
self.iter()
|
||||
.map(|value| {
|
||||
value.reflect_clone()?.take().map_err(|_| {
|
||||
$crate::error::ReflectCloneError::FailedDowncast {
|
||||
expected: alloc::borrow::Cow::Borrowed(<T as $crate::type_path::TypePath>::type_path()),
|
||||
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
|
||||
}
|
||||
})
|
||||
})
|
||||
.map(|value| value.reflect_clone_and_take())
|
||||
.collect::<Result<Self, $crate::error::ReflectCloneError>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
@ -146,18 +146,8 @@ macro_rules! impl_reflect_for_hashmap {
|
||||
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
|
||||
let mut map = Self::with_capacity_and_hasher(self.len(), S::default());
|
||||
for (key, value) in self.iter() {
|
||||
let key = key.reflect_clone()?.take().map_err(|_| {
|
||||
$crate::error::ReflectCloneError::FailedDowncast {
|
||||
expected: alloc::borrow::Cow::Borrowed(<K as $crate::type_path::TypePath>::type_path()),
|
||||
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(key.reflect_type_path())),
|
||||
}
|
||||
})?;
|
||||
let value = value.reflect_clone()?.take().map_err(|_| {
|
||||
$crate::error::ReflectCloneError::FailedDowncast {
|
||||
expected: alloc::borrow::Cow::Borrowed(<V as $crate::type_path::TypePath>::type_path()),
|
||||
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
|
||||
}
|
||||
})?;
|
||||
let key = key.reflect_clone_and_take()?;
|
||||
let value = value.reflect_clone_and_take()?;
|
||||
map.insert(key, value);
|
||||
}
|
||||
|
||||
|
||||
@ -129,12 +129,7 @@ macro_rules! impl_reflect_for_hashset {
|
||||
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
|
||||
let mut set = Self::with_capacity_and_hasher(self.len(), S::default());
|
||||
for value in self.iter() {
|
||||
let value = value.reflect_clone()?.take().map_err(|_| {
|
||||
$crate::error::ReflectCloneError::FailedDowncast {
|
||||
expected: alloc::borrow::Cow::Borrowed(<V as $crate::type_path::TypePath>::type_path()),
|
||||
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
|
||||
}
|
||||
})?;
|
||||
let value = value.reflect_clone_and_take()?;
|
||||
set.insert(value);
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::{
|
||||
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, TypePath, TypeRegistration,
|
||||
Typed,
|
||||
};
|
||||
use alloc::{borrow::Cow, boxed::Box, string::ToString, vec::Vec};
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use bevy_reflect::ReflectCloneError;
|
||||
use bevy_reflect_derive::impl_type_path;
|
||||
use core::any::Any;
|
||||
@ -137,16 +137,11 @@ where
|
||||
|
||||
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
|
||||
Ok(Box::new(
|
||||
self.iter()
|
||||
.map(|value| {
|
||||
value
|
||||
.reflect_clone()?
|
||||
.take()
|
||||
.map_err(|_| ReflectCloneError::FailedDowncast {
|
||||
expected: Cow::Borrowed(<T::Item as TypePath>::type_path()),
|
||||
received: Cow::Owned(value.reflect_type_path().to_string()),
|
||||
})
|
||||
})
|
||||
// `(**self)` avoids getting `SmallVec<T> as List::iter`, which
|
||||
// would give us the wrong item type.
|
||||
(**self)
|
||||
.iter()
|
||||
.map(PartialReflect::reflect_clone_and_take)
|
||||
.collect::<Result<Self, ReflectCloneError>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
@ -215,7 +215,7 @@ pub enum ReflectRef<'a> {
|
||||
/// [function-like]: Function
|
||||
#[cfg(feature = "functions")]
|
||||
Function(&'a dyn Function),
|
||||
/// An immutable refeence to an [opaque] type.
|
||||
/// An immutable reference to an [opaque] type.
|
||||
///
|
||||
/// [opaque]: ReflectKind::Opaque
|
||||
Opaque(&'a dyn PartialReflect),
|
||||
@ -281,7 +281,7 @@ pub enum ReflectMut<'a> {
|
||||
///
|
||||
/// [function-like]: Function
|
||||
Function(&'a mut dyn Function),
|
||||
/// A mutable refeence to an [opaque] type.
|
||||
/// A mutable reference to an [opaque] type.
|
||||
///
|
||||
/// [opaque]: ReflectKind::Opaque
|
||||
Opaque(&'a mut dyn PartialReflect),
|
||||
|
||||
@ -2615,9 +2615,11 @@ bevy_reflect::tests::Test {
|
||||
#[reflect(where T: Default)]
|
||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(Default, TypePath)]
|
||||
struct Bar;
|
||||
|
||||
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Baz;
|
||||
|
||||
@ -2631,6 +2633,7 @@ bevy_reflect::tests::Test {
|
||||
#[reflect(where)]
|
||||
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Bar;
|
||||
|
||||
@ -2665,6 +2668,7 @@ bevy_reflect::tests::Test {
|
||||
#[reflect(where T::Assoc: core::fmt::Display)]
|
||||
struct Foo<T: Trait>(T::Assoc);
|
||||
|
||||
#[expect(dead_code, reason = "Bar is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Bar;
|
||||
|
||||
@ -2672,6 +2676,7 @@ bevy_reflect::tests::Test {
|
||||
type Assoc = usize;
|
||||
}
|
||||
|
||||
#[expect(dead_code, reason = "Baz is never constructed")]
|
||||
#[derive(TypePath)]
|
||||
struct Baz;
|
||||
|
||||
|
||||
@ -313,6 +313,24 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// For a type implementing [`PartialReflect`], combines `reflect_clone` and
|
||||
/// `take` in a useful fashion, automatically constructing an appropriate
|
||||
/// [`ReflectCloneError`] if the downcast fails.
|
||||
///
|
||||
/// This is an associated function, rather than a method, because methods
|
||||
/// with generic types prevent dyn-compatibility.
|
||||
fn reflect_clone_and_take<T: 'static>(&self) -> Result<T, ReflectCloneError>
|
||||
where
|
||||
Self: TypePath + Sized,
|
||||
{
|
||||
self.reflect_clone()?
|
||||
.take()
|
||||
.map_err(|_| ReflectCloneError::FailedDowncast {
|
||||
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
|
||||
received: Cow::Owned(self.reflect_type_path().to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a hash of the value (which includes the type).
|
||||
///
|
||||
/// If the underlying type does not support hashing, returns `None`.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user