Merge commit '2d91a6fc39ffef7554aa7f989c024338a366e08d' into native-depth-clamp

This commit is contained in:
JMS55 2024-11-14 22:29:06 -08:00
commit 8d210215ce
295 changed files with 4085 additions and 2635 deletions

View file

@ -2,7 +2,7 @@
name: Performance Regression
about: Bevy running slowly after upgrading? Report a performance regression.
title: ''
labels: C-Bug, C-Performance, C-Regression, S-Needs-Triage
labels: C-Bug, C-Performance, P-Regression, S-Needs-Triage
assignees: ''
---

View file

@ -72,8 +72,7 @@ jobs:
run: cargo run -p ci -- lints
miri:
# Explicitly use macOS 14 to take advantage of M1 chip.
runs-on: macos-14
runs-on: macos-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
@ -127,7 +126,7 @@ jobs:
- name: Check Compile
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- compile
check-compiles-no-std:
runs-on: ubuntu-latest
timeout-minutes: 30
@ -231,10 +230,10 @@ jobs:
- name: Taplo info
if: failure()
run: |
echo 'To fix toml fmt, please run `taplo fmt`'
echo 'To check for a diff, run `taplo fmt --check --diff'
echo 'To fix toml fmt, please run `taplo fmt`.'
echo 'To check for a diff, run `taplo fmt --check --diff`.'
echo 'You can find taplo here: https://taplo.tamasfe.dev/'
echo 'Or if you use VSCode, use the `Even Better Toml` extension with 2 spaces'
echo 'Or if you use VSCode, use the `Even Better Toml` extension.'
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tamasfe.even-better-toml'
typos:
@ -243,7 +242,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.26.0
uses: crate-ci/typos@v1.27.3
- name: Typos info
if: failure()
run: |
@ -254,8 +253,7 @@ jobs:
echo 'You can find the extension here: https://marketplace.visualstudio.com/items?itemName=tekumara.typos-vscode'
run-examples-macos-metal:
# Explicitly use macOS 14 to take advantage of M1 chip.
runs-on: macos-14
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
@ -301,7 +299,7 @@ jobs:
with:
name: example-run-macos
path: example-run/
check-doc:
runs-on: ubuntu-latest
timeout-minutes: 30
@ -316,7 +314,9 @@ jobs:
~/.cargo/git/db/
target/
key: ${{ runner.os }}-check-doc-${{ hashFiles('**/Cargo.toml') }}
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
with:
@ -327,7 +327,7 @@ jobs:
run: cargo run -p ci -- doc
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
RUSTFLAGS: "-C debuginfo=0 --cfg docsrs_dep"
# This currently report a lot of false positives
# Enable it again once it's fixed - https://github.com/bevyengine/bevy/issues/1983
# - name: Installs cargo-deadlinks

View file

@ -31,7 +31,7 @@ jobs:
with:
path: |
target
key: ${{ runner.os }}-ios-install-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-ios-install-${{ hashFiles('**/Cargo.lock') }}
# TODO: remove x86 target once it always run on arm GitHub runners
- name: Add iOS targets
@ -244,7 +244,7 @@ jobs:
- name: First Wasm build
run: |
cargo build --release --example ui --target wasm32-unknown-unknown
cargo build --release --example testbed_ui --target wasm32-unknown-unknown
- name: Run examples
shell: bash

View file

@ -59,31 +59,13 @@ jobs:
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- compile
check-doc:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@beta
- name: Install Linux dependencies
uses: ./.github/actions/install-linux-deps
with:
wayland: true
xkb: true
- name: Build and check docs
# See tools/ci/src/main.rs for the commands this runs
run: cargo run -p ci -- doc
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-C debuginfo=0"
open-issue:
name: Warn that weekly CI fails
runs-on: ubuntu-latest
needs: [test, lint, check-compiles, check-doc]
needs: [test, lint, check-compiles]
permissions:
issues: write
# Use always() so the job doesn't get canceled if any other jobs fail
# Use always() so the job doesn't get canceled if any other jobs fail
if: ${{ always() && contains(needs.*.result, 'failure') }}
steps:
- name: Create issue
@ -94,7 +76,7 @@ jobs:
--jq '.[0].number')
if [[ -n $previous_issue_number ]]; then
gh issue comment $previous_issue_number \
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
--body "Weekly pipeline still fails: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
else
gh issue create \
--title "$TITLE" \
@ -106,6 +88,6 @@ jobs:
GH_REPO: ${{ github.repository }}
TITLE: Main branch fails to compile on Rust beta.
LABELS: C-Bug,S-Needs-Triage
BODY: |
BODY: |
## Weekly CI run has failed.
[The offending run.](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})

12
.gitignore vendored
View file

@ -1,9 +1,9 @@
# Rust build artifacts
/target
crates/*/target
target
crates/**/target
benches/**/target
tools/**/target
**/*.rs.bk
/benches/target
/tools/compile_fail_utils/target
# Cargo
Cargo.lock
@ -11,8 +11,8 @@ Cargo.lock
.cargo/config.toml
# IDE files
/.idea
/.vscode
.idea
.vscode
.zed
dxcompiler.dll
dxil.dll

View file

@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
documentation = "https://docs.rs/bevy"
rust-version = "1.81.0"
rust-version = "1.82.0"
[workspace]
exclude = [
@ -40,6 +40,7 @@ semicolon_if_nothing_returned = "warn"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
needless_lifetimes = "allow"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
@ -80,6 +81,7 @@ semicolon_if_nothing_returned = "warn"
type_complexity = "allow"
undocumented_unsafe_blocks = "warn"
unwrap_or_default = "warn"
needless_lifetimes = "allow"
ptr_as_ptr = "warn"
ptr_cast_constness = "warn"
@ -403,7 +405,7 @@ pbr_multi_layer_material_textures = [
pbr_anisotropy_texture = ["bevy_internal/pbr_anisotropy_texture"]
# Enable support for PCSS, at the risk of blowing past the global, per-shader sampler limit on older/lower-end GPUs
pbr_pcss = ["bevy_internal/pbr_pcss"]
experimental_pbr_pcss = ["bevy_internal/experimental_pbr_pcss"]
# Enable some limitations to be able to use WebGL2. Please refer to the [WebGL2 and WebGPU](https://github.com/bevyengine/bevy/tree/latest/examples#webgl2-and-webgpu) section of the examples README for more information on how to run Wasm builds with WebGPU.
webgl2 = ["bevy_internal/webgl"]
@ -474,6 +476,7 @@ hyper = { version = "1", features = ["server", "http1"] }
http-body-util = "0.1"
anyhow = "1"
macro_rules_attribute = "0.2"
accesskit = "0.17"
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
smol = "2"
@ -1216,7 +1219,7 @@ setup = [
"curl",
"-o",
"assets/models/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/8483db58832542383820c3f44e4730e566910be7/bunny.meshlet_mesh",
"https://raw.githubusercontent.com/JMS55/bevy_meshlet_asset/defbd9b32072624d40d57de7d345c66a9edf5d0b/bunny.meshlet_mesh",
],
]
@ -2703,6 +2706,17 @@ description = "Test rendering of many UI elements"
category = "Stress Tests"
wasm = true
[[example]]
name = "many_cameras_lights"
path = "examples/stress_tests/many_cameras_lights.rs"
doc-scrape-examples = true
[package.metadata.example.many_cameras_lights]
name = "Many Cameras & Lights"
description = "Test rendering of many cameras and lights"
category = "Stress Tests"
wasm = true
[[example]]
name = "many_cubes"
path = "examples/stress_tests/many_cubes.rs"
@ -3136,17 +3150,6 @@ description = "Demonstrates how to control the relative depth (z-position) of UI
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui"
path = "examples/ui/ui.rs"
doc-scrape-examples = true
[package.metadata.example.ui]
name = "UI"
description = "Illustrates various features of Bevy UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui_scaling"
path = "examples/ui/ui_scaling.rs"
@ -3789,7 +3792,7 @@ wasm = true
name = "pcss"
path = "examples/3d/pcss.rs"
doc-scrape-examples = true
required-features = ["pbr_pcss"]
required-features = ["experimental_pbr_pcss"]
[package.metadata.example.pcss]
name = "Percentage-closer soft shadows"
@ -3856,3 +3859,19 @@ doc-scrape-examples = true
[package.metadata.example.testbed_3d]
hidden = true
[[example]]
name = "testbed_ui"
path = "examples/testbed/ui.rs"
doc-scrape-examples = true
[package.metadata.example.testbed_ui]
hidden = true
[[example]]
name = "testbed_ui_layout_rounding"
path = "examples/testbed/ui_layout_rounding.rs"
doc-scrape-examples = true
[package.metadata.example.testbed_ui_layout_rounding]
hidden = true

View file

@ -1,3 +1,5 @@
#![expect(dead_code, reason = "Many fields are unused/unread as they are just for benchmarking purposes.")]
use criterion::criterion_main;
mod components;

View file

@ -1,6 +1,6 @@
use bevy::prelude::*;
use bevy_ecs::prelude::*;
#[derive(Component)]
#[derive(Component, Clone)]
struct A(f32);
#[derive(Component)]
struct B(f32);
@ -12,19 +12,18 @@ impl Benchmark {
let mut world = World::default();
let entities = world
.spawn_batch((0..10000).map(|_| A(0.0)))
.collect::<Vec<_>>();
.spawn_batch(core::iter::repeat(A(0.)).take(10000))
.collect();
Self(world, entities)
}
pub fn run(&mut self) {
for entity in &self.1 {
self.0.insert_one(*entity, B(0.0)).unwrap();
self.0.entity_mut(*entity).insert(B(0.));
}
for entity in &self.1 {
self.0.remove_one::<B>(*entity).unwrap();
self.0.entity_mut(*entity).remove::<B>();
}
}
}

View file

@ -5,6 +5,7 @@ mod add_remove_big_table;
mod add_remove_sparse_set;
mod add_remove_table;
mod add_remove_very_big_table;
mod add_remove;
mod archetype_updates;
mod insert_simple;
mod insert_simple_unbatched;

View file

@ -1,6 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use bevy_math::{prelude::*, *};
use bevy_math::prelude::*;
fn easing(c: &mut Criterion) {
let cubic_bezier = CubicSegment::new_bezier(vec2(0.25, 0.1), vec2(0.25, 1.0));

View file

@ -1,5 +1,5 @@
use bevy_math::{Dir3, Mat4, Ray3d, Vec3};
use bevy_picking::{mesh_picking::ray_cast, prelude::*};
use bevy_picking::mesh_picking::ray_cast;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn ptoxznorm(p: u32, size: u32) -> (f32, f32) {

View file

@ -15,7 +15,7 @@ bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev" }
accesskit = "0.16"
accesskit = "0.17"
[lints]
workspace = true

View file

@ -6,14 +6,19 @@
)]
//! Accessibility for Bevy
//!
//! As of Bevy version 0.15 `accesskit` is no longer re-exported from this crate.
//!
//! If you need to use `accesskit`, you will need to add it as a separate dependency in your `Cargo.toml`.
//!
//! Make sure to use the same version of `accesskit` as Bevy.
extern crate alloc;
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
pub use accesskit;
use accesskit::NodeBuilder;
use accesskit::Node;
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
@ -84,10 +89,10 @@ impl ManageAccessibilityUpdates {
/// If the entity doesn't have a parent, or if the immediate parent doesn't have
/// an `AccessibilityNode`, its node will be an immediate child of the primary window.
#[derive(Component, Clone, Deref, DerefMut)]
pub struct AccessibilityNode(pub NodeBuilder);
pub struct AccessibilityNode(pub Node);
impl From<NodeBuilder> for AccessibilityNode {
fn from(node: NodeBuilder) -> Self {
impl From<Node> for AccessibilityNode {
fn from(node: Node) -> Self {
Self(node)
}
}

View file

@ -7,9 +7,9 @@
//! `Curve<Vec3>` that we want to use to animate something. That could be defined in
//! a number of different ways, but let's imagine that we've defined it [using a function]:
//!
//! # use bevy_math::curve::{Curve, Interval, function_curve};
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_math::vec3;
//! let wobble_curve = function_curve(
//! let wobble_curve = FunctionCurve::new(
//! Interval::UNIT,
//! |t| { vec3(t.cos(), 0.0, 0.0) },
//! );
@ -25,10 +25,10 @@
//! the adaptor [`TranslationCurve`], which wraps any `Curve<Vec3>` and turns it into an
//! [`AnimationCurve`] that will use the given curve to animate the entity's translation:
//!
//! # use bevy_math::curve::{Curve, Interval, function_curve};
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_math::vec3;
//! # use bevy_animation::animation_curves::*;
//! # let wobble_curve = function_curve(
//! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT,
//! # |t| vec3(t.cos(), 0.0, 0.0)
//! # );
@ -37,11 +37,11 @@
//! And finally, this `AnimationCurve` needs to be added to an [`AnimationClip`] in order to
//! actually animate something. This is what that looks like:
//!
//! # use bevy_math::curve::{Curve, Interval, function_curve};
//! # use bevy_math::curve::{Curve, Interval, FunctionCurve};
//! # use bevy_animation::{AnimationClip, AnimationTargetId, animation_curves::*};
//! # use bevy_core::Name;
//! # use bevy_math::vec3;
//! # let wobble_curve = function_curve(
//! # let wobble_curve = FunctionCurve::new(
//! # Interval::UNIT,
//! # |t| { vec3(t.cos(), 0.0, 0.0) },
//! # );
@ -71,7 +71,7 @@
//! Animation of arbitrary components can be accomplished using [`AnimatableProperty`] in
//! conjunction with [`AnimatableCurve`]. See the documentation [there] for details.
//!
//! [using a function]: bevy_math::curve::function_curve
//! [using a function]: bevy_math::curve::FunctionCurve
//! [translation component of a `Transform`]: bevy_transform::prelude::Transform::translation
//! [`AnimationClip`]: crate::AnimationClip
//! [there]: AnimatableProperty
@ -746,7 +746,15 @@ impl WeightsCurveEvaluator {
None => {
self.blend_register_blend_weight = Some(weight_to_blend);
self.blend_register_morph_target_weights.clear();
self.blend_register_morph_target_weights.extend(stack_iter);
// In the additive case, the values pushed onto the blend register need
// to be scaled by the weight.
if additive {
self.blend_register_morph_target_weights
.extend(stack_iter.map(|m| m * weight_to_blend));
} else {
self.blend_register_morph_target_weights.extend(stack_iter);
}
}
Some(ref mut current_weight) => {
@ -877,7 +885,9 @@ where
} = self.stack.pop().unwrap();
match self.blend_register.take() {
None => self.blend_register = Some((value_to_blend, weight_to_blend)),
None => {
self.initialize_blend_register(value_to_blend, weight_to_blend, additive);
}
Some((mut current_value, mut current_weight)) => {
current_weight += weight_to_blend;
@ -912,6 +922,22 @@ where
Ok(())
}
fn initialize_blend_register(&mut self, value: A, weight: f32, additive: bool) {
if additive {
let scaled_value = A::blend(
[BlendInput {
weight,
value,
additive: true,
}]
.into_iter(),
);
self.blend_register = Some((scaled_value, weight));
} else {
self.blend_register = Some((value, weight));
}
}
fn push_blend_register(
&mut self,
weight: f32,

View file

@ -217,9 +217,8 @@ pub enum AnimationNodeType {
/// additively.
///
/// The weights of all the children of this node are *not* normalized to
/// 1.0. Rather, the first child is used as a base, ignoring its weight,
/// while the others are multiplied by their respective weights and then
/// added in sequence to the base.
/// 1.0. Rather, each child is multiplied by its respective weight and
/// added in sequence.
///
/// Add nodes are primarily useful for superimposing an animation for a
/// portion of a rig on top of the main animation. For example, an add node

5
crates/bevy_animation/src/lib.rs Executable file → Normal file
View file

@ -856,8 +856,8 @@ impl AnimationPlayer {
self.active_animations.iter_mut()
}
#[deprecated = "Use `animation_is_playing` instead"]
/// Check if the given animation node is being played.
/// Returns true if the animation is currently playing or paused, or false
/// if the animation is stopped.
pub fn is_playing_animation(&self, animation: AnimationNodeIndex) -> bool {
self.active_animations.contains_key(&animation)
}
@ -944,6 +944,7 @@ impl AnimationPlayer {
self.active_animations.get_mut(&animation)
}
#[deprecated = "Use `is_playing_animation` instead"]
/// Returns true if the animation is currently playing or paused, or false
/// if the animation is stopped.
pub fn animation_is_playing(&self, animation: AnimationNodeIndex) -> bool {

View file

@ -48,6 +48,17 @@ use core::any::TypeId;
/// # impl Plugin for WebCompatibilityPlugin { fn build(&self, _: &mut App) {} }
/// # }
/// #
/// # mod audio {
/// # use bevy_app::*;
/// # #[derive(Default)]
/// # pub struct AudioPlugins;
/// # impl PluginGroup for AudioPlugins {
/// # fn build(self) -> PluginGroupBuilder {
/// # PluginGroupBuilder::start::<Self>()
/// # }
/// # }
/// # }
/// #
/// # mod internal {
/// # use bevy_app::*;
/// # #[derive(Default)]
@ -75,6 +86,10 @@ use core::any::TypeId;
/// // generation, in which case you must wrap it in `#[custom()]`.
/// #[custom(cfg(target_arch = "wasm32"))]
/// web:::WebCompatibilityPlugin,
/// // You can nest `PluginGroup`s within other `PluginGroup`s, you just need the
/// // `#[plugin_group]` attribute.
/// #[plugin_group]
/// audio:::AudioPlugins,
/// // You can hide plugins from documentation. Due to macro limitations, hidden plugins
/// // must be last.
/// #[doc(hidden)]
@ -94,6 +109,14 @@ macro_rules! plugin_group {
$(#[custom($plugin_meta:meta)])*
$($plugin_path:ident::)* : $plugin_name:ident
),*
$(
$(,)?$(
#[plugin_group]
$(#[cfg(feature = $plugin_group_feature:literal)])?
$(#[custom($plugin_group_meta:meta)])*
$($plugin_group_path:ident::)* : $plugin_group_name:ident
),+
)?
$(
$(,)?$(
#[doc(hidden)]
@ -113,6 +136,10 @@ macro_rules! plugin_group {
" - [`", stringify!($plugin_name), "`](" $(, stringify!($plugin_path), "::")*, stringify!($plugin_name), ")"
$(, " - with feature `", $plugin_feature, "`")?
)])*
$($(#[doc = concat!(
" - [`", stringify!($plugin_group_name), "`](" $(, stringify!($plugin_group_path), "::")*, stringify!($plugin_group_name), ")"
$(, " - with feature `", $plugin_group_feature, "`")?
)]),+)?
$(
///
$(#[doc = $post_doc])+
@ -135,6 +162,18 @@ macro_rules! plugin_group {
group = group.add(<$($plugin_path::)*$plugin_name>::default());
}
)*
$($(
$(#[cfg(feature = $plugin_group_feature)])?
$(#[$plugin_group_meta])*
{
const _: () = {
const fn check_default<T: Default>() {}
check_default::<$($plugin_group_path::)*$plugin_group_name>();
};
group = group.add_group(<$($plugin_group_path::)*$plugin_group_name>::default());
}
)+)?
$($(
$(#[cfg(feature = $hidden_plugin_feature)])?
$(#[$hidden_plugin_meta])*

View file

@ -37,6 +37,7 @@ type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
///
/// // Create a sub-app with the same resource and a single schedule.
/// let mut sub_app = SubApp::new();
/// sub_app.update_schedule = Some(Main.intern());
/// sub_app.insert_resource(Val(100));
///
/// // Setup an extract function to copy the resource's value in the main world.

View file

@ -65,7 +65,7 @@ wasm-bindgen-futures = "0.4"
js-sys = "0.3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
notify-debouncer-full = { version = "0.3.1", optional = true }
notify-debouncer-full = { version = "0.4.0", optional = true }
[dev-dependencies]
bevy_core = { path = "../bevy_core", version = "0.15.0-dev" }

View file

@ -5,7 +5,7 @@ use crate::io::{
};
use alloc::sync::Arc;
use bevy_utils::{tracing::warn, Duration, HashMap};
use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, FileIdMap};
use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache};
use parking_lot::RwLock;
use std::{
fs::File,
@ -18,7 +18,7 @@ use std::{
/// This watcher will watch for changes to the "source files", read the contents of changed files from the file system
/// and overwrite the initial static bytes of the file embedded in the binary with the new dynamically loaded bytes.
pub struct EmbeddedWatcher {
_watcher: Debouncer<RecommendedWatcher, FileIdMap>,
_watcher: Debouncer<RecommendedWatcher, RecommendedCache>,
}
impl EmbeddedWatcher {

View file

@ -164,6 +164,12 @@ impl AssetWriter for FileAssetWriter {
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::create_dir_all(full_path).await?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_dir_all(full_path).await?;

View file

@ -9,9 +9,9 @@ use notify_debouncer_full::{
notify::{
self,
event::{AccessKind, AccessMode, CreateKind, ModifyKind, RemoveKind, RenameMode},
RecommendedWatcher, RecursiveMode, Watcher,
RecommendedWatcher, RecursiveMode,
},
DebounceEventResult, Debouncer, FileIdMap,
DebounceEventResult, Debouncer, RecommendedCache,
};
use std::path::{Path, PathBuf};
@ -21,7 +21,7 @@ use std::path::{Path, PathBuf};
/// This introduces a small delay in processing events, but it helps reduce event duplicates. A small delay is also necessary
/// on some systems to avoid processing a change event before it has actually been applied.
pub struct FileWatcher {
_watcher: Debouncer<RecommendedWatcher, FileIdMap>,
_watcher: Debouncer<RecommendedWatcher, RecommendedCache>,
}
impl FileWatcher {
@ -73,7 +73,7 @@ pub(crate) fn new_asset_event_debouncer(
root: PathBuf,
debounce_wait_time: Duration,
mut handler: impl FilesystemEventHandler,
) -> Result<Debouncer<RecommendedWatcher, FileIdMap>, notify::Error> {
) -> Result<Debouncer<RecommendedWatcher, RecommendedCache>, notify::Error> {
let root = super::get_base_path().join(root);
let mut debouncer = new_debouncer(
debounce_wait_time,
@ -245,8 +245,7 @@ pub(crate) fn new_asset_event_debouncer(
}
},
)?;
debouncer.watcher().watch(&root, RecursiveMode::Recursive)?;
debouncer.cache().add_root(&root, RecursiveMode::Recursive);
debouncer.watch(&root, RecursiveMode::Recursive)?;
Ok(debouncer)
}

View file

@ -205,6 +205,12 @@ impl AssetWriter for FileAssetWriter {
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::create_dir_all(full_path)?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_dir_all(full_path)?;

View file

@ -23,6 +23,7 @@ pub use source::*;
use alloc::sync::Arc;
use bevy_utils::{BoxedFuture, ConditionalSendFuture};
use core::future::Future;
use core::{
mem::size_of,
pin::Pin,
@ -120,6 +121,40 @@ impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
}
}
/// Extension trait for [`AsyncSeekForward`].
pub trait AsyncSeekForwardExt: AsyncSeekForward {
/// Seek by the provided `offset` in the forwards direction, using the [`AsyncSeekForward`] trait.
fn seek_forward(&mut self, offset: u64) -> SeekForwardFuture<'_, Self>
where
Self: Unpin,
{
SeekForwardFuture {
seeker: self,
offset,
}
}
}
impl<R: AsyncSeekForward + ?Sized> AsyncSeekForwardExt for R {}
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SeekForwardFuture<'a, S: Unpin + ?Sized> {
seeker: &'a mut S,
offset: u64,
}
impl<S: Unpin + ?Sized> Unpin for SeekForwardFuture<'_, S> {}
impl<S: AsyncSeekForward + Unpin + ?Sized> Future for SeekForwardFuture<'_, S> {
type Output = futures_lite::io::Result<u64>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let offset = self.offset;
Pin::new(&mut *self.seeker).poll_seek_forward(cx, offset)
}
}
/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
/// (or virtual file) corresponding to an asset.
///
@ -349,6 +384,12 @@ pub trait AssetWriter: Send + Sync + 'static {
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
@ -425,6 +466,12 @@ pub trait ErasedAssetWriter: Send + Sync + 'static {
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
@ -488,6 +535,12 @@ impl<T: AssetWriter> ErasedAssetWriter for T {
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::rename_meta(self, old_path, new_path))
}
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::create_directory(self, path))
}
fn remove_directory<'a>(
&'a self,
path: &'a Path,

View file

@ -264,6 +264,17 @@ where
}
}
impl AudioPlayer<AudioSource> {
/// Creates a new [`AudioPlayer`] with the given [`Handle<AudioSource>`].
///
/// For convenience reasons, this hard-codes the [`AudioSource`] type. If you want to
/// initialize an [`AudioPlayer`] with a different type, just initialize it directly using normal
/// tuple struct syntax.
pub fn new(source: Handle<AudioSource>) -> Self {
Self(source)
}
}
/// Bundle for playing a sound.
///
/// Insert this bundle onto an entity to trigger a sound source to begin playing.

View file

@ -21,7 +21,7 @@
//!
//! fn play_background_audio(asset_server: Res<AssetServer>, mut commands: Commands) {
//! commands.spawn((
//! AudioPlayer::<AudioSource>(asset_server.load("background_audio.ogg")),
//! AudioPlayer::new(asset_server.load("background_audio.ogg")),
//! PlaybackSettings::LOOP,
//! ));
//! }

View file

@ -23,7 +23,7 @@ derive_more = { version = "1", default-features = false, features = [
"from",
"display",
] }
wgpu-types = { version = "22", default-features = false, optional = true }
wgpu-types = { version = "23", default-features = false, optional = true }
encase = { version = "0.10", default-features = false }
[features]

View file

@ -123,7 +123,7 @@ impl Color {
}
/// Reads an array of floats to creates a new [`Color`] object storing a [`Srgba`] color with an alpha of 1.0.
pub fn srgb_from_array(array: [f32; 3]) -> Self {
pub const fn srgb_from_array(array: [f32; 3]) -> Self {
Self::Srgba(Srgba {
red: array[0],
green: array[1],
@ -143,7 +143,7 @@ impl Color {
/// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values.
///
/// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0.
pub fn srgba_u8(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
pub const fn srgba_u8(red: u8, green: u8, blue: u8, alpha: u8) -> Self {
Self::Srgba(Srgba {
red: red as f32 / 255.0,
green: green as f32 / 255.0,
@ -163,7 +163,7 @@ impl Color {
/// Creates a new [`Color`] object storing a [`Srgba`] color from [`u8`] values with an alpha of 1.0.
///
/// A value of 0 is interpreted as 0.0, and a value of 255 is interpreted as 1.0.
pub fn srgb_u8(red: u8, green: u8, blue: u8) -> Self {
pub const fn srgb_u8(red: u8, green: u8, blue: u8) -> Self {
Self::Srgba(Srgba {
red: red as f32 / 255.0,
green: green as f32 / 255.0,

View file

@ -20,7 +20,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
# other
serde = { version = "1.0", optional = true }
uuid = "1.0"
[features]
default = ["bevy_reflect"]

View file

@ -1,9 +1,12 @@
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
use bevy_utils::tracing::trace;
use alloc::sync::Arc;
use core::fmt::Debug;
/// Defines a simple way to determine how many threads to use given the number of remaining cores
/// and number of total cores
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct TaskPoolThreadAssignmentPolicy {
/// Force using at least this many threads
pub min_threads: usize,
@ -12,6 +15,22 @@ pub struct TaskPoolThreadAssignmentPolicy {
/// Target using this percentage of total cores, clamped by `min_threads` and `max_threads`. It is
/// permitted to use 1.0 to try to use all remaining threads
pub percent: f32,
/// Callback that is invoked once for every created thread as it starts.
/// This configuration will be ignored under wasm platform.
pub on_thread_spawn: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
/// Callback that is invoked once for every created thread as it terminates
/// This configuration will be ignored under wasm platform.
pub on_thread_destroy: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
}
impl Debug for TaskPoolThreadAssignmentPolicy {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TaskPoolThreadAssignmentPolicy")
.field("min_threads", &self.min_threads)
.field("max_threads", &self.max_threads)
.field("percent", &self.percent)
.finish()
}
}
impl TaskPoolThreadAssignmentPolicy {
@ -61,6 +80,8 @@ impl Default for TaskPoolOptions {
min_threads: 1,
max_threads: 4,
percent: 0.25,
on_thread_spawn: None,
on_thread_destroy: None,
},
// Use 25% of cores for async compute, at least 1, no more than 4
@ -68,6 +89,8 @@ impl Default for TaskPoolOptions {
min_threads: 1,
max_threads: 4,
percent: 0.25,
on_thread_spawn: None,
on_thread_destroy: None,
},
// Use all remaining cores for compute (at least 1)
@ -75,6 +98,8 @@ impl Default for TaskPoolOptions {
min_threads: 1,
max_threads: usize::MAX,
percent: 1.0, // This 1.0 here means "whatever is left over"
on_thread_spawn: None,
on_thread_destroy: None,
},
}
}
@ -108,10 +133,21 @@ impl TaskPoolOptions {
remaining_threads = remaining_threads.saturating_sub(io_threads);
IoTaskPool::get_or_init(|| {
TaskPoolBuilder::default()
let mut builder = TaskPoolBuilder::default()
.num_threads(io_threads)
.thread_name("IO Task Pool".to_string())
.build()
.thread_name("IO Task Pool".to_string());
#[cfg(not(target_arch = "wasm32"))]
{
if let Some(f) = self.io.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.io.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
}
builder.build()
});
}
@ -125,10 +161,21 @@ impl TaskPoolOptions {
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
AsyncComputeTaskPool::get_or_init(|| {
TaskPoolBuilder::default()
let mut builder = TaskPoolBuilder::default()
.num_threads(async_compute_threads)
.thread_name("Async Compute Task Pool".to_string())
.build()
.thread_name("Async Compute Task Pool".to_string());
#[cfg(not(target_arch = "wasm32"))]
{
if let Some(f) = self.async_compute.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.async_compute.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
}
builder.build()
});
}
@ -142,10 +189,21 @@ impl TaskPoolOptions {
trace!("Compute Threads: {}", compute_threads);
ComputeTaskPool::get_or_init(|| {
TaskPoolBuilder::default()
let mut builder = TaskPoolBuilder::default()
.num_threads(compute_threads)
.thread_name("Compute Task Pool".to_string())
.build()
.thread_name("Compute Task Pool".to_string());
#[cfg(not(target_arch = "wasm32"))]
{
if let Some(f) = self.compute.on_thread_spawn.clone() {
builder = builder.on_thread_spawn(move || f());
}
if let Some(f) = self.compute.on_thread_destroy.clone() {
builder = builder.on_thread_destroy(move || f());
}
}
builder.build()
});
}
}

View file

@ -3,11 +3,11 @@ use super::compensation_curve::{
};
use bevy_asset::prelude::*;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_render::{
globals::GlobalsUniform,
render_resource::{binding_types::*, *},
renderer::RenderDevice,
texture::Image,
view::ViewUniform,
};
use core::num::NonZero;
@ -89,6 +89,7 @@ impl SpecializedComputePipeline for AutoExposurePipeline {
AutoExposurePass::Average => "compute_average".into(),
},
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -3,8 +3,9 @@ use core::ops::RangeInclusive;
use super::compensation_curve::AutoExposureCompensationCurve;
use bevy_asset::Handle;
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_image::Image;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{extract_component::ExtractComponent, texture::Image};
use bevy_render::extract_component::ExtractComponent;
use bevy_utils::default;
/// Component that enables auto exposure for an HDR-enabled 2d or 3d camera.

View file

@ -98,6 +98,7 @@ impl SpecializedRenderPipeline for BlitPipeline {
..Default::default()
},
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -127,6 +127,7 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -39,7 +39,7 @@ use upsampling_pipeline::{
const BLOOM_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(929599476923908);
const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Float;
const BLOOM_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rg11b10Ufloat;
pub struct BloomPlugin;

View file

@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -6,6 +6,7 @@ use crate::{
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_image::BevyDefault as _;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
@ -16,7 +17,6 @@ use bevy_render::{
*,
},
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
@ -233,6 +233,7 @@ impl SpecializedRenderPipeline for CasPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -65,14 +65,15 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true;
use core::ops::Range;
use bevy_asset::{AssetId, UntypedAssetId};
use bevy_color::LinearRgba;
pub use camera_3d::*;
pub use main_opaque_pass_3d_node::*;
pub use main_transparent_pass_3d_node::*;
use bevy_app::{App, Plugin, PostUpdate};
use bevy_asset::{AssetId, UntypedAssetId};
use bevy_color::LinearRgba;
use bevy_ecs::{entity::EntityHashSet, prelude::*};
use bevy_image::{BevyDefault, Image};
use bevy_math::FloatOrd;
use bevy_render::sync_world::MainEntity;
use bevy_render::{
@ -91,7 +92,7 @@ use bevy_render::{
},
renderer::RenderDevice,
sync_world::RenderEntity,
texture::{BevyDefault, ColorAttachment, Image, TextureCache},
texture::{ColorAttachment, TextureCache},
view::{ExtractedView, ViewDepthTexture, ViewTarget},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
};

View file

@ -160,6 +160,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
});
Self {

View file

@ -26,6 +26,7 @@ use bevy_ecs::{
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::ops;
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_render::{
@ -48,7 +49,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
sync_component::SyncComponentPlugin,
sync_world::RenderEntity,
texture::{BevyDefault, CachedTexture, TextureCache},
texture::{CachedTexture, TextureCache},
view::{
prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
ViewUniformOffset, ViewUniforms,
@ -806,6 +807,7 @@ impl SpecializedRenderPipeline for DepthOfFieldPipeline {
},
targets,
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -6,6 +6,7 @@ use crate::{
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::prelude::*;
use bevy_image::BevyDefault as _;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_component::{ExtractComponent, ExtractComponentPlugin},
@ -16,7 +17,6 @@ use bevy_render::{
*,
},
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
@ -196,6 +196,7 @@ impl SpecializedRenderPipeline for FxaaPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -5,6 +5,7 @@ use bevy_ecs::{
system::{Commands, Query, Res, ResMut, Resource},
world::FromWorld,
};
use bevy_image::BevyDefault as _;
use bevy_render::{
globals::GlobalsUniform,
render_resource::{
@ -19,7 +20,6 @@ use bevy_render::{
TextureFormat, TextureSampleType,
},
renderer::RenderDevice,
texture::BevyDefault,
view::{ExtractedView, Msaa, ViewTarget},
};
@ -141,6 +141,7 @@ impl SpecializedRenderPipeline for MotionBlurPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -2,18 +2,24 @@
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::prelude::*;
use bevy_ecs::{component::*, prelude::*};
use bevy_math::UVec2;
use bevy_reflect::Reflect;
use bevy_render::{
camera::{Camera, ExtractedCamera},
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::{BufferUsages, BufferVec, DynamicUniformBuffer, Shader, TextureUsages},
render_resource::{
BufferUsages, BufferVec, DynamicUniformBuffer, Shader, ShaderType, TextureUsages,
},
renderer::{RenderDevice, RenderQueue},
view::Msaa,
Render, RenderApp, RenderSet,
};
use bevy_utils::{tracing::trace, HashSet, Instant};
use bevy_utils::{
tracing::{trace, warn},
HashSet, Instant,
};
use bevy_window::PrimaryWindow;
use resolve::{
node::{OitResolveNode, OitResolvePass},
@ -36,17 +42,41 @@ pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(404252
// TODO consider supporting multiple OIT techniques like WBOIT, Moment Based OIT,
// depth peeling, stochastic transparency, ray tracing etc.
// This should probably be done by adding an enum to this component.
#[derive(Component, Clone, Copy, ExtractComponent)]
// We use the same struct to pass on the settings to the drawing shader.
#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
pub struct OrderIndependentTransparencySettings {
/// Controls how many layers will be used to compute the blending.
/// The more layers you use the more memory it will use but it will also give better results.
/// 8 is generally recommended, going above 16 is probably not worth it in the vast majority of cases
pub layer_count: u8,
/// 8 is generally recommended, going above 32 is probably not worth it in the vast majority of cases
pub layer_count: i32,
/// Threshold for which fragments will be added to the blending layers.
/// This can be tweaked to optimize quality / layers count. Higher values will
/// allow lower number of layers and a better performance, compromising quality.
pub alpha_threshold: f32,
}
impl Default for OrderIndependentTransparencySettings {
fn default() -> Self {
Self { layer_count: 8 }
Self {
layer_count: 8,
alpha_threshold: 0.0,
}
}
}
// OrderIndependentTransparencySettings is also a Component. We explicitly implement the trait so
// we can hook on_add to issue a warning in case `layer_count` is seemingly too high.
impl Component for OrderIndependentTransparencySettings {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|world, entity, _| {
if let Some(value) = world.get::<OrderIndependentTransparencySettings>(entity) {
if value.layer_count > 32 {
warn!("OrderIndependentTransparencySettings layer_count set to {} might be too high.", value.layer_count);
}
}
});
}
}
@ -82,7 +112,8 @@ impl Plugin for OrderIndependentTransparencyPlugin {
OitResolvePlugin,
))
.add_systems(Update, check_msaa)
.add_systems(Last, configure_depth_texture_usages);
.add_systems(Last, configure_depth_texture_usages)
.register_type::<OrderIndependentTransparencySettings>();
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
@ -164,7 +195,7 @@ pub struct OitBuffers {
pub layers: BufferVec<UVec2>,
/// Buffer containing the index of the last layer that was written for each fragment.
pub layer_ids: BufferVec<i32>,
pub layers_count_uniforms: DynamicUniformBuffer<i32>,
pub settings: DynamicUniformBuffer<OrderIndependentTransparencySettings>,
}
impl FromWorld for OitBuffers {
@ -184,19 +215,19 @@ impl FromWorld for OitBuffers {
layer_ids.reserve(1, render_device);
layer_ids.write_buffer(render_device, render_queue);
let mut layers_count_uniforms = DynamicUniformBuffer::default();
layers_count_uniforms.set_label(Some("oit_layers_count"));
let mut settings = DynamicUniformBuffer::default();
settings.set_label(Some("oit_settings"));
Self {
layers,
layer_ids,
layers_count_uniforms,
settings,
}
}
}
#[derive(Component)]
pub struct OitLayersCountOffset {
pub struct OrderIndependentTransparencySettingsOffset {
pub offset: u32,
}
@ -268,16 +299,16 @@ pub fn prepare_oit_buffers(
);
}
if let Some(mut writer) = buffers.layers_count_uniforms.get_writer(
if let Some(mut writer) = buffers.settings.get_writer(
camera_oit_uniforms.iter().len(),
&render_device,
&render_queue,
) {
for (entity, settings) in &camera_oit_uniforms {
let offset = writer.write(&(settings.layer_count as i32));
let offset = writer.write(settings);
commands
.entity(entity)
.insert(OitLayersCountOffset { offset });
.insert(OrderIndependentTransparencySettingsOffset { offset });
}
}
}

View file

@ -1,14 +1,13 @@
#define_import_path bevy_core_pipeline::oit
#import bevy_pbr::mesh_view_bindings::{view, oit_layers, oit_layer_ids, oit_layers_count}
#import bevy_pbr::mesh_view_bindings::{view, oit_layers, oit_layer_ids, oit_settings}
#ifdef OIT_ENABLED
// Add the fragment to the oit buffer
fn oit_draw(position: vec4f, color: vec4f) {
// Don't add fully transparent fragments to the list
// because we don't want to have to sort them in the resolve pass
// TODO should this be comparing with < espilon ?
if color.a == 0.0 {
if color.a < oit_settings.alpha_threshold {
return;
}
// get the index of the current fragment relative to the screen size
@ -20,10 +19,10 @@ fn oit_draw(position: vec4f, color: vec4f) {
// gets the layer index of the current fragment
var layer_id = atomicAdd(&oit_layer_ids[screen_index], 1);
// exit early if we've reached the maximum amount of fragments per layer
if layer_id >= oit_layers_count {
if layer_id >= oit_settings.layers_count {
// force to store the oit_layers_count to make sure we don't
// accidentally increase the index above the maximum value
atomicStore(&oit_layer_ids[screen_index], oit_layers_count);
atomicStore(&oit_layer_ids[screen_index], oit_settings.layers_count);
// TODO for tail blending we should return the color here
return;
}

View file

@ -9,6 +9,7 @@ use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
prelude::*,
};
use bevy_image::BevyDefault as _;
use bevy_render::{
render_resource::{
binding_types::{storage_buffer_sized, texture_depth_2d, uniform_buffer},
@ -18,7 +19,6 @@ use bevy_render::{
Shader, ShaderDefVal, ShaderStages, TextureFormat,
},
renderer::{RenderAdapter, RenderDevice},
texture::BevyDefault,
view::{ExtractedView, ViewTarget, ViewUniform, ViewUniforms},
Render, RenderApp, RenderSet,
};
@ -121,7 +121,7 @@ pub struct OitResolvePipelineId(pub CachedRenderPipelineId);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct OitResolvePipelineKey {
hdr: bool,
layer_count: u8,
layer_count: i32,
}
#[allow(clippy::too_many_arguments)]
@ -208,6 +208,7 @@ fn specialize_oit_resolve_pipeline(
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}

View file

@ -14,6 +14,7 @@ use bevy_ecs::{
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::{BevyDefault, Image};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
camera::Camera,
@ -32,7 +33,7 @@ use bevy_render::{
TextureDimension, TextureFormat, TextureSampleType,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage, Image},
texture::GpuImage,
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
@ -344,6 +345,7 @@ impl SpecializedRenderPipeline for PostProcessingPipeline {
depth_stencil: None,
multisample: default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -6,6 +6,7 @@ use bevy_ecs::{
schedule::IntoSystemConfigs,
system::{Commands, Query, Res, ResMut, Resource},
};
use bevy_image::{BevyDefault, Image};
use bevy_math::{Mat4, Quat};
use bevy_render::{
camera::Exposure,
@ -19,7 +20,7 @@ use bevy_render::{
*,
},
renderer::RenderDevice,
texture::{BevyDefault, GpuImage, Image},
texture::GpuImage,
view::{ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniforms},
Render, RenderApp, RenderSet,
};
@ -233,6 +234,7 @@ impl SpecializedRenderPipeline for SkyboxPipeline {
write_mask: ColorWrites::ALL,
})],
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -105,6 +105,7 @@ impl SpecializedRenderPipeline for SkyboxPrepassPipeline {
entry_point: "fragment".into(),
targets: prepass_target_descriptors(key.normal_prepass, true, false),
}),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -29,7 +29,12 @@
//! * Compatibility with SSAA and MSAA.
//!
//! [SMAA]: https://www.iryoku.com/smaa/
#[cfg(not(feature = "smaa_luts"))]
use crate::tonemapping::lut_placeholder;
use crate::{
core_2d::graph::{Core2d, Node2d},
core_3d::graph::{Core3d, Node3d},
};
use bevy_app::{App, Plugin};
#[cfg(feature = "smaa_luts")]
use bevy_asset::load_internal_binary_asset;
@ -44,6 +49,7 @@ use bevy_ecs::{
system::{lifetimeless::Read, Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::{BevyDefault, Image};
use bevy_math::{vec4, Vec4};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
@ -67,24 +73,12 @@ use bevy_render::{
VertexState,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::{BevyDefault, CachedTexture, GpuImage, Image, TextureCache},
texture::{CachedTexture, GpuImage, TextureCache},
view::{ExtractedView, ViewTarget},
Render, RenderApp, RenderSet,
};
#[cfg(feature = "smaa_luts")]
use bevy_render::{
render_asset::RenderAssetUsages,
texture::{CompressedImageFormats, ImageFormat, ImageSampler, ImageType},
};
use bevy_utils::prelude::default;
#[cfg(not(feature = "smaa_luts"))]
use crate::tonemapping::lut_placeholder;
use crate::{
core_2d::graph::{Core2d, Node2d},
core_3d::graph::{Core3d, Node3d},
};
/// The handle of the `smaa.wgsl` shader.
const SMAA_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(12247928498010601081);
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
@ -306,11 +300,11 @@ impl Plugin for SmaaPlugin {
#[cfg(all(debug_assertions, feature = "dds"))]
"SMAAAreaLUT".to_owned(),
bytes,
ImageType::Format(ImageFormat::Ktx2),
CompressedImageFormats::NONE,
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
bevy_image::CompressedImageFormats::NONE,
false,
ImageSampler::Default,
RenderAssetUsages::RENDER_WORLD,
bevy_image::ImageSampler::Default,
bevy_asset::RenderAssetUsages::RENDER_WORLD,
)
.expect("Failed to load SMAA area LUT")
);
@ -324,11 +318,11 @@ impl Plugin for SmaaPlugin {
#[cfg(all(debug_assertions, feature = "dds"))]
"SMAASearchLUT".to_owned(),
bytes,
ImageType::Format(ImageFormat::Ktx2),
CompressedImageFormats::NONE,
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
bevy_image::CompressedImageFormats::NONE,
false,
ImageSampler::Default,
RenderAssetUsages::RENDER_WORLD,
bevy_image::ImageSampler::Default,
bevy_asset::RenderAssetUsages::RENDER_WORLD,
)
.expect("Failed to load SMAA search LUT")
);
@ -512,6 +506,7 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
bias: default(),
}),
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}
@ -571,6 +566,7 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
bias: default(),
}),
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}
@ -607,6 +603,7 @@ impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline {
primitive: PrimitiveState::default(),
depth_stencil: None,
multisample: MultisampleState::default(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -16,6 +16,7 @@ use bevy_ecs::{
system::{Commands, Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
@ -34,7 +35,7 @@ use bevy_render::{
renderer::{RenderContext, RenderDevice},
sync_component::SyncComponentPlugin,
sync_world::RenderEntity,
texture::{BevyDefault, CachedTexture, TextureCache},
texture::{CachedTexture, TextureCache},
view::{ExtractedView, Msaa, ViewTarget},
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
};
@ -355,6 +356,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -2,6 +2,7 @@ use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Assets, Handle};
use bevy_ecs::prelude::*;
use bevy_image::{CompressedImageFormats, Image, ImageSampler, ImageType};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
camera::Camera,
@ -13,7 +14,7 @@ use bevy_render::{
*,
},
renderer::RenderDevice,
texture::{CompressedImageFormats, FallbackImage, GpuImage, Image, ImageSampler, ImageType},
texture::{FallbackImage, GpuImage},
view::{ExtractedView, ViewTarget, ViewUniform},
Render, RenderApp, RenderSet,
};
@ -307,6 +308,7 @@ impl SpecializedRenderPipeline for TonemappingPipeline {
depth_stencil: None,
multisample: MultisampleState::default(),
push_constant_ranges: Vec::new(),
zero_initialize_workgroup_memory: false,
}
}
}
@ -432,14 +434,14 @@ pub fn get_lut_bind_group_layout_entries() -> [BindGroupLayoutEntryBuilder; 2] {
// allow(dead_code) so it doesn't complain when the tonemapping_luts feature is disabled
#[allow(dead_code)]
fn setup_tonemapping_lut_image(bytes: &[u8], image_type: ImageType) -> Image {
let image_sampler = ImageSampler::Descriptor(bevy_render::texture::ImageSamplerDescriptor {
let image_sampler = ImageSampler::Descriptor(bevy_image::ImageSamplerDescriptor {
label: Some("Tonemapping LUT sampler".to_string()),
address_mode_u: bevy_render::texture::ImageAddressMode::ClampToEdge,
address_mode_v: bevy_render::texture::ImageAddressMode::ClampToEdge,
address_mode_w: bevy_render::texture::ImageAddressMode::ClampToEdge,
mag_filter: bevy_render::texture::ImageFilterMode::Linear,
min_filter: bevy_render::texture::ImageFilterMode::Linear,
mipmap_filter: bevy_render::texture::ImageFilterMode::Linear,
address_mode_u: bevy_image::ImageAddressMode::ClampToEdge,
address_mode_v: bevy_image::ImageAddressMode::ClampToEdge,
address_mode_w: bevy_image::ImageAddressMode::ClampToEdge,
mag_filter: bevy_image::ImageFilterMode::Linear,
min_filter: bevy_image::ImageFilterMode::Linear,
mipmap_filter: bevy_image::ImageFilterMode::Linear,
..default()
});
Image::from_buffer(

View file

@ -46,28 +46,32 @@ fn prepare_view_upscaling_pipelines(
let mut output_textures = HashSet::new();
for (entity, view_target, camera) in view_targets.iter() {
let out_texture_id = view_target.out_texture().id();
let blend_state = if let Some(ExtractedCamera {
output_mode: CameraOutputMode::Write { blend_state, .. },
..
}) = camera
{
match *blend_state {
None => {
// If we've already seen this output for a camera and it doesn't have a output blend
// mode configured, default to alpha blend so that we don't accidentally overwrite
// the output texture
if output_textures.contains(&out_texture_id) {
Some(BlendState::ALPHA_BLENDING)
} else {
None
let blend_state = if let Some(extracted_camera) = camera {
match extracted_camera.output_mode {
CameraOutputMode::Skip => None,
CameraOutputMode::Write { blend_state, .. } => {
let already_seen = output_textures.contains(&out_texture_id);
output_textures.insert(out_texture_id);
match blend_state {
None => {
// If we've already seen this output for a camera and it doesn't have a output blend
// mode configured, default to alpha blend so that we don't accidentally overwrite
// the output texture
if already_seen {
Some(BlendState::ALPHA_BLENDING)
} else {
None
}
}
_ => blend_state,
}
}
_ => *blend_state,
}
} else {
output_textures.insert(out_texture_id);
None
};
output_textures.insert(out_texture_id);
let key = BlitPipelineKey {
texture_format: view_target.out_texture_format(),

View file

@ -82,7 +82,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
for require in requires {
let ident = &require.path;
register_recursive_requires.push(quote! {
<#ident as Component>::register_required_components(
<#ident as #bevy_ecs_path::component::Component>::register_required_components(
requiree,
components,
storages,

View file

@ -487,7 +487,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component id: {:?}, already has an on_add hook")
.expect("Component already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
@ -505,7 +505,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component id: {:?}, already has an on_insert hook")
.expect("Component already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
@ -527,7 +527,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_replace` hook
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_replace(hook)
.expect("Component id: {:?}, already has an on_replace hook")
.expect("Component already has an on_replace hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
@ -538,7 +538,7 @@ impl ComponentHooks {
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component id: {:?}, already has an on_remove hook")
.expect("Component already has an on_remove hook")
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
@ -1512,8 +1512,11 @@ impl<'a> TickCells<'a> {
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
pub struct ComponentTicks {
pub(crate) added: Tick,
pub(crate) changed: Tick,
/// Tick recording the time this component or resource was added.
pub added: Tick,
/// Tick recording the time this component or resource was most recently changed.
pub changed: Tick,
}
impl ComponentTicks {
@ -1531,19 +1534,8 @@ impl ComponentTicks {
self.changed.is_newer_than(last_run, this_run)
}
/// Returns the tick recording the time this component or resource was most recently changed.
#[inline]
pub fn last_changed_tick(&self) -> Tick {
self.changed
}
/// Returns the tick recording the time this component or resource was added.
#[inline]
pub fn added_tick(&self) -> Tick {
self.added
}
pub(crate) fn new(change_tick: Tick) -> Self {
/// Creates a new instance with the same change tick for `added` and `changed`.
pub fn new(change_tick: Tick) -> Self {
Self {
added: change_tick,
changed: change_tick,

View file

@ -772,7 +772,7 @@ impl<T: SparseSetIndex> Access<T> {
/// `Access`, it's not recommended. Prefer to manage your own lists of
/// accessible components if your application needs to do that.
#[doc(hidden)]
#[deprecated]
// TODO: this should be deprecated and removed, see https://github.com/bevyengine/bevy/issues/16339
pub fn component_reads_and_writes(&self) -> (impl Iterator<Item = T> + '_, bool) {
(
self.component_read_and_writes

View file

@ -2030,8 +2030,8 @@ macro_rules! impl_tuple_query_data {
}
macro_rules! impl_anytuple_fetch {
($(($name: ident, $state: ident)),*) => {
($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => {
$(#[$meta])*
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
/// SAFETY:
@ -2153,6 +2153,7 @@ macro_rules! impl_anytuple_fetch {
}
}
$(#[$meta])*
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
// SAFETY: defers to soundness of `$name: WorldQuery` impl
@ -2160,6 +2161,7 @@ macro_rules! impl_anytuple_fetch {
type ReadOnly = AnyOf<($($name::ReadOnly,)*)>;
}
$(#[$meta])*
/// SAFETY: each item in the tuple is read only
unsafe impl<$($name: ReadOnlyQueryData),*> ReadOnlyQueryData for AnyOf<($($name,)*)> {}
};
@ -2173,7 +2175,14 @@ all_tuples!(
F,
S
);
all_tuples!(impl_anytuple_fetch, 0, 15, F, S);
all_tuples!(
#[doc(fake_variadic)]
impl_anytuple_fetch,
0,
15,
F,
S
);
/// [`WorldQuery`] used to nullify queries by turning `Query<D>` into `Query<NopWorldQuery<D>>`
///

View file

@ -379,7 +379,8 @@ impl<T: WorldQuery> Clone for OrFetch<'_, T> {
}
macro_rules! impl_or_query_filter {
($(($filter: ident, $state: ident)),*) => {
($(#[$meta:meta])* $(($filter: ident, $state: ident)),*) => {
$(#[$meta])*
#[allow(unused_variables)]
#[allow(non_snake_case)]
#[allow(clippy::unused_unit)]
@ -497,6 +498,7 @@ macro_rules! impl_or_query_filter {
}
}
$(#[$meta])*
// SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access.
unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> {
const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*;
@ -546,7 +548,14 @@ all_tuples!(
15,
F
);
all_tuples!(impl_or_query_filter, 0, 15, F, S);
all_tuples!(
#[doc(fake_variadic)]
impl_or_query_filter,
0,
15,
F,
S
);
/// A filter on a component that only retains results the first time after they have been added.
///
@ -1044,7 +1053,8 @@ macro_rules! impl_archetype_filter_tuple {
}
macro_rules! impl_archetype_or_filter_tuple {
($($filter: ident),*) => {
($(#[$meta:meta])* $($filter: ident),*) => {
$(#[$meta])*
impl<$($filter: ArchetypeFilter),*> ArchetypeFilter for Or<($($filter,)*)> {}
};
}
@ -1057,4 +1067,10 @@ all_tuples!(
F
);
all_tuples!(impl_archetype_or_filter_tuple, 0, 15, F);
all_tuples!(
#[doc(fake_variadic)]
impl_archetype_or_filter_tuple,
0,
15,
F
);

View file

@ -28,14 +28,16 @@ pub struct ReflectBundle(ReflectBundleFns);
/// The also [`super::component::ReflectComponentFns`].
#[derive(Clone)]
pub struct ReflectBundleFns {
/// Function pointer implementing [`ReflectBundle::insert()`].
/// Function pointer implementing [`ReflectBundle::insert`].
pub insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry),
/// Function pointer implementing [`ReflectBundle::apply()`].
/// Function pointer implementing [`ReflectBundle::apply`].
pub apply: fn(EntityMut, &dyn PartialReflect, &TypeRegistry),
/// Function pointer implementing [`ReflectBundle::apply_or_insert()`].
/// Function pointer implementing [`ReflectBundle::apply_or_insert`].
pub apply_or_insert: fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry),
/// Function pointer implementing [`ReflectBundle::remove()`].
/// Function pointer implementing [`ReflectBundle::remove`].
pub remove: fn(&mut EntityWorldMut),
/// Function pointer implementing [`ReflectBundle::take`].
pub take: fn(&mut EntityWorldMut) -> Option<Box<dyn Reflect>>,
}
impl ReflectBundleFns {
@ -85,8 +87,17 @@ impl ReflectBundle {
}
/// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist.
pub fn remove(&self, entity: &mut EntityWorldMut) {
pub fn remove(&self, entity: &mut EntityWorldMut) -> &ReflectBundle {
(self.0.remove)(entity);
self
}
/// Removes all components in the [`Bundle`] from the entity and returns their previous values.
///
/// **Note:** If the entity does not have every component in the bundle, this method will not remove any of them.
#[must_use]
pub fn take(&self, entity: &mut EntityWorldMut) -> Option<Box<dyn Reflect>> {
(self.0.take)(entity)
}
/// Create a custom implementation of [`ReflectBundle`].
@ -168,7 +179,7 @@ impl<B: Bundle + Reflect + TypePath> FromType<B> for ReflectBundle {
.iter_fields()
.for_each(|field| apply_or_insert_field(entity, field, registry)),
_ => panic!(
"expected bundle `{}` to be named struct or tuple",
"expected bundle `{}` to be a named struct or tuple",
// FIXME: once we have unique reflect, use `TypePath`.
core::any::type_name::<B>(),
),
@ -178,6 +189,11 @@ impl<B: Bundle + Reflect + TypePath> FromType<B> for ReflectBundle {
remove: |entity| {
entity.remove::<B>();
},
take: |entity| {
entity
.take::<B>()
.map(|bundle| Box::new(bundle).into_reflect())
},
})
}
}

View file

@ -650,6 +650,11 @@ impl ScheduleGraph {
.and_then(|system| system.inner.as_deref())
}
/// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`.
pub fn contains_set(&self, set: impl SystemSet) -> bool {
self.system_set_ids.contains_key(&set.intern())
}
/// Returns the system at the given [`NodeId`].
///
/// Panics if it doesn't exist.
@ -1956,13 +1961,13 @@ pub enum ScheduleBuildError {
#[display("System dependencies contain cycle(s).\n{_0}")]
DependencyCycle(String),
/// Tried to order a system (set) relative to a system set it belongs to.
#[display("`{0}` and `{_1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
#[display("`{_0}` and `{_1}` have both `in_set` and `before`-`after` relationships (these might be transitive). This combination is unsolvable as a system cannot run before or after a set it belongs to.")]
CrossDependency(String, String),
/// Tried to order system sets that share systems.
#[display("`{0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
#[display("`{_0}` and `{_1}` have a `before`-`after` relationship (which may be transitive) but share systems.")]
SetsHaveOrderButIntersect(String, String),
/// Tried to order a system (set) relative to all instances of some system function.
#[display("Tried to order against `{0}` in a schedule that has more than one `{0}` instance. `{_0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
#[display("Tried to order against `{_0}` in a schedule that has more than one `{_0}` instance. `{_0}` is a `SystemTypeSet` and cannot be used for ordering if ambiguous. Use a different set without this restriction.")]
SystemTypeSetAmbiguity(String),
/// Systems with conflicting access have indeterminate run order.
///

View file

@ -4,6 +4,7 @@ use core::{marker::PhantomData, panic::Location};
use super::{
Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource, RunSystemCachedWith,
UnregisterSystem,
};
use crate::{
self as bevy_ecs,
@ -890,6 +891,17 @@ impl<'w, 's> Commands<'w, 's> {
SystemId::from_entity(entity)
}
/// Removes a system previously registered with [`Commands::register_system`] or [`World::register_system`].
///
/// See [`World::unregister_system`] for more information.
pub fn unregister_system<I, O>(&mut self, system_id: SystemId<I, O>)
where
I: SystemInput + Send + 'static,
O: Send + 'static,
{
self.queue(UnregisterSystem::new(system_id));
}
/// Similar to [`Self::run_system`], but caching the [`SystemId`] in a
/// [`CachedSystemId`](crate::system::CachedSystemId) resource.
///

View file

@ -306,7 +306,8 @@ pub struct SystemState<Param: SystemParam + 'static> {
// So, generate a function for each arity with an explicit `FnMut` constraint to enable higher-order lifetimes,
// along with a regular `SystemParamFunction` constraint to allow the system to be built.
macro_rules! impl_build_system {
($($param: ident),*) => {
($(#[$meta:meta])* $($param: ident),*) => {
$(#[$meta])*
impl<$($param: SystemParam),*> SystemState<($($param,)*)> {
/// Create a [`FunctionSystem`] from a [`SystemState`].
/// This method signature allows type inference of closure parameters for a system with no input.
@ -344,7 +345,13 @@ macro_rules! impl_build_system {
}
}
all_tuples!(impl_build_system, 0, 16, P);
all_tuples!(
#[doc(fake_variadic)]
impl_build_system,
0,
16,
P
);
impl<Param: SystemParam> SystemState<Param> {
/// Creates a new [`SystemState`] with default state.

View file

@ -1651,6 +1651,8 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>>
/// Use [`Option<Single<D, F>>`] instead if zero or one matching entities can exist.
///
/// See [`Query`] for more details.
///
/// [System parameter]: crate::system::SystemParam
pub struct Single<'w, D: QueryData, F: QueryFilter = ()> {
pub(crate) item: D::Item<'w>,
pub(crate) _filter: PhantomData<F>,
@ -1687,6 +1689,8 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> {
/// which must individually check each query result for a match.
///
/// See [`Query`] for more details.
///
/// [System parameter]: crate::system::SystemParam
pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>);
impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> {

View file

@ -30,7 +30,7 @@ pub struct SystemIdMarker;
/// A system that has been removed from the registry.
/// It contains the system and whether or not it has been initialized.
///
/// This struct is returned by [`World::remove_system`].
/// This struct is returned by [`World::unregister_system`].
pub struct RemovedSystem<I = (), O = ()> {
initialized: bool,
system: BoxedSystem<I, O>,
@ -172,7 +172,7 @@ impl World {
///
/// If no system corresponds to the given [`SystemId`], this method returns an error.
/// Systems are also not allowed to remove themselves, this returns an error too.
pub fn remove_system<I, O>(
pub fn unregister_system<I, O>(
&mut self,
id: SystemId<I, O>,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
@ -412,7 +412,7 @@ impl World {
/// Removes a cached system and its [`CachedSystemId`] resource.
///
/// See [`World::register_system_cached`] for more information.
pub fn remove_system_cached<I, O, M, S>(
pub fn unregister_system_cached<I, O, M, S>(
&mut self,
_system: S,
) -> Result<RemovedSystem<I, O>, RegisteredSystemError<I, O>>
@ -424,7 +424,7 @@ impl World {
let id = self
.remove_resource::<CachedSystemId<S::System>>()
.ok_or(RegisteredSystemError::SystemNotCached)?;
self.remove_system(id.0)
self.unregister_system(id.0)
}
/// Runs a cached system, registering it if necessary.
@ -544,6 +544,32 @@ where
}
}
/// The [`Command`] type for unregistering one-shot systems from [`Commands`](crate::system::Commands).
pub struct UnregisterSystem<I: SystemInput + 'static, O: 'static> {
system_id: SystemId<I, O>,
}
impl<I, O> UnregisterSystem<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
/// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands).
pub fn new(system_id: SystemId<I, O>) -> Self {
Self { system_id }
}
}
impl<I, O> Command for UnregisterSystem<I, O>
where
I: SystemInput + 'static,
O: 'static,
{
fn apply(self, world: &mut World) {
let _ = world.unregister_system(self.system_id);
}
}
/// The [`Command`] type for running a cached one-shot system from
/// [`Commands`](crate::system::Commands).
///
@ -834,7 +860,7 @@ mod tests {
let new = world.register_system_cached(four);
assert_eq!(old, new);
let result = world.remove_system_cached(four);
let result = world.unregister_system_cached(four);
assert!(result.is_ok());
let new = world.register_system_cached(four);
assert_ne!(old, new);

View file

@ -730,6 +730,34 @@ impl<'w> EntityMut<'w> {
unsafe { component_ids.fetch_mut(self.0) }
}
/// Returns [untyped mutable reference](MutUntyped) to component for
/// the current entity, based on the given [`ComponentId`].
///
/// Unlike [`EntityMut::get_mut_by_id`], this method borrows &self instead of
/// &mut self, allowing the caller to access multiple components simultaneously.
///
/// # Errors
///
/// - Returns [`EntityComponentError::MissingComponent`] if the entity does
/// not have a component.
/// - Returns [`EntityComponentError::AliasedMutability`] if a component
/// is requested multiple times.
///
/// # Safety
/// It is the callers responsibility to ensure that
/// - the [`UnsafeEntityCell`] has permission to access the component mutably
/// - no other references to the component exist at the same time
#[inline]
pub unsafe fn get_mut_by_id_unchecked<F: DynamicComponentFetch>(
&self,
component_ids: F,
) -> Result<F::Mut<'_>, EntityComponentError> {
// SAFETY:
// - The caller must ensure simultaneous access is limited
// - to components that are mutually independent.
unsafe { component_ids.fetch_mut(self.0) }
}
/// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped)
/// to component(s) with lifetime `'w` for the current entity, based on the
/// given [`ComponentId`]s.
@ -4036,7 +4064,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn ref_compatible_with_resource_mut() {
fn borrow_system(_: Query<EntityRef>, _: ResMut<R>) {}
@ -4067,7 +4094,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn mut_compatible_with_resource() {
fn borrow_mut_system(_: Res<R>, _: Query<EntityMut>) {}
@ -4075,7 +4101,6 @@ mod tests {
}
#[test]
#[ignore] // This should pass, but it currently fails due to limitations in our access model.
fn mut_compatible_with_resource_mut() {
fn borrow_mut_system(_: ResMut<R>, _: Query<EntityMut>) {}
@ -4428,4 +4453,27 @@ mod tests {
.map(|_| { unreachable!() })
);
}
#[test]
fn get_mut_by_id_unchecked() {
let mut world = World::default();
let e1 = world.spawn((X(7), Y(10))).id();
let x_id = world.register_component::<X>();
let y_id = world.register_component::<Y>();
let e1_mut = &world.get_entity_mut([e1]).unwrap()[0];
// SAFETY: The entity e1 contains component X.
let x_ptr = unsafe { e1_mut.get_mut_by_id_unchecked(x_id) }.unwrap();
// SAFETY: The entity e1 contains component Y, with components X and Y being mutually independent.
let y_ptr = unsafe { e1_mut.get_mut_by_id_unchecked(y_id) }.unwrap();
// SAFETY: components match the id they were fetched with
let x_component = unsafe { x_ptr.into_inner().deref_mut::<X>() };
x_component.0 += 1;
// SAFETY: components match the id they were fetched with
let y_component = unsafe { y_ptr.into_inner().deref_mut::<Y>() };
y_component.0 -= 1;
assert_eq!((&mut X(8), &mut Y(9)), (x_component, y_component));
}
}

View file

@ -8,7 +8,7 @@ use bevy_ecs::prelude::Commands;
use bevy_ecs::system::NonSendMut;
use bevy_ecs::system::ResMut;
use bevy_input::gamepad::{
GamepadConnection, GamepadConnectionEvent, GamepadInfo, RawGamepadAxisChangedEvent,
GamepadConnection, GamepadConnectionEvent, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use gilrs::{ev::filter::axis_dpad_to_button, EventType, Filter};
@ -26,15 +26,13 @@ pub fn gilrs_event_startup_system(
gamepads.id_to_entity.insert(id, entity);
gamepads.entity_to_id.insert(entity, id);
let info = GamepadInfo {
name: gamepad.name().into(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
};
events.send(GamepadConnectionEvent {
gamepad: entity,
connection: GamepadConnection::Connected(info),
connection: GamepadConnection::Connected {
name: gamepad.name().to_string(),
vendor_id: gamepad.vendor_id(),
product_id: gamepad.product_id(),
},
});
}
}
@ -62,20 +60,17 @@ pub fn gilrs_event_system(
entity
});
let info = GamepadInfo {
name: pad.name().into(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
};
events.send(
GamepadConnectionEvent::new(entity, GamepadConnection::Connected(info.clone()))
.into(),
);
connection_events.send(GamepadConnectionEvent::new(
let event = GamepadConnectionEvent::new(
entity,
GamepadConnection::Connected(info),
));
GamepadConnection::Connected {
name: pad.name().to_string(),
vendor_id: pad.vendor_id(),
product_id: pad.product_id(),
},
);
events.send(event.clone().into());
connection_events.send(event);
}
EventType::Disconnected => {
let gamepad = gamepads

View file

@ -20,6 +20,7 @@ bevy_sprite = { path = "../bevy_sprite", version = "0.15.0-dev", optional = true
bevy_app = { path = "../bevy_app", version = "0.15.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.15.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.15.0-dev", optional = true }

View file

@ -33,7 +33,7 @@ where
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos()));
/// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
@ -67,7 +67,7 @@ where
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| {
/// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });
@ -104,7 +104,7 @@ where
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos()));
/// let curve = FunctionCurve::new(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_gradient_2d(
/// curve,
/// (0..=100).map(|n| n as f32 / 100.0)
@ -147,7 +147,7 @@ where
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| {
/// let curve = FunctionCurve::new(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });

View file

@ -13,6 +13,7 @@ use bevy_ecs::{
system::{Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::FloatOrd;
use bevy_render::sync_world::MainEntity;
use bevy_render::{
@ -22,7 +23,6 @@ use bevy_render::{
ViewSortedRenderPhases,
},
render_resource::*,
texture::BevyDefault,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
@ -161,6 +161,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
},
label: Some("LineGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
@ -261,6 +262,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline {
},
label: Some("LineJointGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -17,6 +17,7 @@ use bevy_ecs::{
system::{Query, Res, ResMut, Resource},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup};
use bevy_render::sync_world::MainEntity;
use bevy_render::{
@ -26,7 +27,6 @@ use bevy_render::{
ViewSortedRenderPhases,
},
render_resource::*,
texture::BevyDefault,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
@ -158,6 +158,7 @@ impl SpecializedRenderPipeline for LineGizmoPipeline {
},
label: Some("LineGizmo Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
@ -256,6 +257,7 @@ impl SpecializedRenderPipeline for LineJointGizmoPipeline {
},
label: Some("LineJointGizmo Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

View file

@ -103,12 +103,12 @@ pub use loader::*;
use bevy_app::prelude::*;
use bevy_asset::{Asset, AssetApp, AssetPath, Handle};
use bevy_ecs::{prelude::Component, reflect::ReflectComponent};
use bevy_image::CompressedImageFormats;
use bevy_pbr::StandardMaterial;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_render::{
mesh::{skinning::SkinnedMeshInverseBindposes, Mesh, MeshVertexAttribute},
renderer::RenderDevice,
texture::CompressedImageFormats,
};
use bevy_scene::Scene;

View file

@ -15,6 +15,10 @@ use bevy_ecs::{
world::World,
};
use bevy_hierarchy::{BuildChildren, ChildBuild, WorldChildBuilder};
use bevy_image::{
CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings,
ImageSampler, ImageSamplerDescriptor, ImageType, TextureError,
};
use bevy_math::{Affine2, Mat4, Vec3};
use bevy_pbr::{
DirectionalLight, MeshMaterial3d, PointLight, SpotLight, StandardMaterial, UvChannel,
@ -31,10 +35,6 @@ use bevy_render::{
primitives::Aabb,
render_asset::RenderAssetUsages,
render_resource::{Face, PrimitiveTopology},
texture::{
CompressedImageFormats, Image, ImageAddressMode, ImageFilterMode, ImageLoaderSettings,
ImageSampler, ImageSamplerDescriptor, ImageType, TextureError,
},
view::Visibility,
};
use bevy_scene::Scene;
@ -275,7 +275,7 @@ async fn load_gltf<'a, 'b, 'c>(
#[cfg(feature = "bevy_animation")]
let (animations, named_animations, animation_roots) = {
use bevy_animation::{animation_curves::*, gltf_curves::*, VariableCurve};
use bevy_math::curve::{constant_curve, Interval, UnevenSampleAutoCurve};
use bevy_math::curve::{ConstantCurve, Interval, UnevenSampleAutoCurve};
use bevy_math::{Quat, Vec4};
use gltf::animation::util::ReadOutputs;
let mut animations = vec![];
@ -313,7 +313,7 @@ async fn load_gltf<'a, 'b, 'c>(
let translations: Vec<Vec3> = tr.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, translations[0]))
Some(ConstantCurve::new(Interval::EVERYWHERE, translations[0]))
.map(TranslationCurve)
.map(VariableCurve::new)
} else {
@ -348,7 +348,7 @@ async fn load_gltf<'a, 'b, 'c>(
rots.into_f32().map(Quat::from_array).collect();
if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, rotations[0]))
Some(ConstantCurve::new(Interval::EVERYWHERE, rotations[0]))
.map(RotationCurve)
.map(VariableCurve::new)
} else {
@ -385,7 +385,7 @@ async fn load_gltf<'a, 'b, 'c>(
let scales: Vec<Vec3> = scale.map(Vec3::from).collect();
if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, scales[0]))
Some(ConstantCurve::new(Interval::EVERYWHERE, scales[0]))
.map(ScaleCurve)
.map(VariableCurve::new)
} else {
@ -419,7 +419,7 @@ async fn load_gltf<'a, 'b, 'c>(
let weights: Vec<f32> = weights.into_f32().collect();
if keyframe_timestamps.len() == 1 {
#[allow(clippy::unnecessary_map_on_constructor)]
Some(constant_curve(Interval::EVERYWHERE, weights))
Some(ConstantCurve::new(Interval::EVERYWHERE, weights))
.map(WeightsCurve)
.map(VariableCurve::new)
} else {

View file

@ -248,7 +248,7 @@ pub(crate) enum ConvertAttributeError {
"Vertex attribute {_0} has format {_1:?} but expected {_3:?} for target attribute {_2}"
)]
WrongFormat(String, VertexFormat, String, VertexFormat),
#[display("{0} in accessor {_1}")]
#[display("{_0} in accessor {_1}")]
AccessFailed(AccessFailed, usize),
#[display("Unknown vertex attribute {_0}")]
UnknownName(String),

0
crates/bevy_hierarchy/src/lib.rs Executable file → Normal file
View file

View file

@ -49,7 +49,7 @@ image = { version = "0.25.2", default-features = false }
# misc
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu = { version = "22", default-features = false }
wgpu = { version = "23", default-features = false }
serde = { version = "1", features = ["derive"] }
derive_more = { version = "1", default-features = false, features = [
"error",

View file

@ -1,3 +1,5 @@
//! [DirectDraw Surface](https://en.wikipedia.org/wiki/DirectDraw_Surface) functionality.
#[cfg(debug_assertions)]
use bevy_utils::warn_once;
use ddsfile::{Caps2, D3DFormat, Dds, DxgiFormat};
@ -16,7 +18,8 @@ pub fn dds_buffer_to_image(
is_srgb: bool,
) -> Result<Image, TextureError> {
let mut cursor = Cursor::new(buffer);
let dds = Dds::read(&mut cursor).expect("Failed to parse DDS file");
let dds = Dds::read(&mut cursor)
.map_err(|error| TextureError::InvalidData(format!("Failed to parse DDS file: {error}")))?;
let texture_format = dds_format_to_texture_format(&dds, is_srgb)?;
if !supported_compressed_formats.supports(texture_format) {
return Err(TextureError::UnsupportedTextureFormat(format!(
@ -182,7 +185,7 @@ pub fn dds_format_to_texture_format(
DxgiFormat::R10G10B10A2_Typeless | DxgiFormat::R10G10B10A2_UNorm => {
TextureFormat::Rgb10a2Unorm
}
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Float,
DxgiFormat::R11G11B10_Float => TextureFormat::Rg11b10Ufloat,
DxgiFormat::R8G8B8A8_Typeless
| DxgiFormat::R8G8B8A8_UNorm
| DxgiFormat::R8G8B8A8_UNorm_sRGB => {

View file

@ -14,6 +14,7 @@ use core::hash::Hash;
use derive_more::derive::{Display, Error, From};
use serde::{Deserialize, Serialize};
use wgpu::{Extent3d, TextureDimension, TextureFormat, TextureViewDescriptor};
pub trait BevyDefault {
fn bevy_default() -> Self;
}

View file

@ -641,7 +641,7 @@ pub fn ktx2_dfd_to_texture_format(
&& sample_information[2].channel_type == 2
&& sample_information[2].bit_length == 10
{
TextureFormat::Rg11b10Float
TextureFormat::Rg11b10Ufloat
} else if sample_information[0].channel_type == 0
&& sample_information[0].bit_length == 9
&& sample_information[1].channel_type == 1
@ -1276,7 +1276,7 @@ pub fn ktx2_format_to_texture_format(
ktx2::Format::R32G32B32A32_SINT => TextureFormat::Rgba32Sint,
ktx2::Format::R32G32B32A32_SFLOAT => TextureFormat::Rgba32Float,
ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Float,
ktx2::Format::B10G11R11_UFLOAT_PACK32 => TextureFormat::Rg11b10Ufloat,
ktx2::Format::E5B9G9R9_UFLOAT_PACK32 => TextureFormat::Rgb9e5Ufloat,
ktx2::Format::X8_D24_UNORM_PACK32 => TextureFormat::Depth24Plus,

View file

@ -2,6 +2,10 @@
#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![allow(unsafe_code)]
pub mod prelude {
pub use crate::{BevyDefault as _, Image, ImageFormat, TextureError};
}
mod image;
pub use self::image::*;
#[cfg(feature = "basis-universal")]

View file

@ -21,6 +21,7 @@ serialize = ["serde", "smol_str/serde"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.15.0-dev", default-features = false }
bevy_core = { path = "../bevy_core", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev", default-features = false, features = [
"serialize",
] }

View file

@ -45,16 +45,16 @@ where
/// If the `input_device`:
/// - was present before, the position data is updated, and the old value is returned.
/// - wasn't present before, `None` is returned.
pub fn set(&mut self, input_device: T, position_data: f32) -> Option<f32> {
self.axis_data.insert(input_device, position_data)
pub fn set(&mut self, input_device: impl Into<T>, position_data: f32) -> Option<f32> {
self.axis_data.insert(input_device.into(), position_data)
}
/// Returns the position data of the provided `input_device`.
///
/// This will be clamped between [`Axis::MIN`] and [`Axis::MAX`] inclusive.
pub fn get(&self, input_device: T) -> Option<f32> {
pub fn get(&self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data
.get(&input_device)
.get(&input_device.into())
.copied()
.map(|value| value.clamp(Self::MIN, Self::MAX))
}
@ -66,13 +66,13 @@ where
/// Use for things like camera zoom, where you want devices like mouse wheels to be able to
/// exceed the normal range. If being able to move faster on one input device
/// than another would give an unfair advantage, you should likely use [`Axis::get`] instead.
pub fn get_unclamped(&self, input_device: T) -> Option<f32> {
self.axis_data.get(&input_device).copied()
pub fn get_unclamped(&self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data.get(&input_device.into()).copied()
}
/// Removes the position data of the `input_device`, returning the position data if the input device was previously set.
pub fn remove(&mut self, input_device: T) -> Option<f32> {
self.axis_data.remove(&input_device)
pub fn remove(&mut self, input_device: impl Into<T>) -> Option<f32> {
self.axis_data.remove(&input_device.into())
}
/// Returns an iterator over all axes.

View file

@ -1,6 +1,7 @@
//! The gamepad input functionality.
use crate::{Axis, ButtonInput, ButtonState};
use bevy_core::Name;
use bevy_ecs::{
change_detection::DetectChangesMut,
component::Component,
@ -148,7 +149,7 @@ impl GamepadConnectionEvent {
/// Is the gamepad connected?
pub fn connected(&self) -> bool {
matches!(self.connection, GamepadConnection::Connected(_))
matches!(self.connection, GamepadConnection::Connected { .. })
}
/// Is the gamepad disconnected?
@ -306,29 +307,29 @@ pub enum ButtonSettingsError {
},
}
/// The [`Gamepad`] [`component`](Component) stores a connected gamepad's metadata such as the `name` and its [`GamepadButton`] and [`GamepadAxis`].
/// Stores a connected gamepad's state and any metadata such as the device name.
///
/// The [`entity`](Entity) representing a gamepad and its [`minimal components`](GamepadSettings) are automatically managed.
/// An entity with this component is spawned automatically after [`GamepadConnectionEvent`]
/// and updated by [`gamepad_event_processing_system`].
///
/// # Usage
///
/// The only way to obtain a [`Gamepad`] is by [`query`](Query).
/// See also [`GamepadSettings`] for configuration.
///
/// # Examples
///
/// ```
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
/// # use bevy_ecs::system::Query;
/// # use bevy_core::Name;
/// #
/// fn gamepad_usage_system(gamepads: Query<&Gamepad>) {
/// for gamepad in gamepads.iter() {
/// println!("{}", gamepad.name());
/// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) {
/// for (name, gamepad) in &gamepads {
/// println!("{name}");
///
/// if gamepad.just_pressed(GamepadButton::North) {
/// println!("{} just pressed North", gamepad.name())
/// if gamepad.digital.just_pressed(GamepadButton::North) {
/// println!("{name} just pressed North")
/// }
///
/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) {
/// if let Some(left_stick_x) = gamepad.analog.get(GamepadAxis::LeftStickX) {
/// println!("left stick X: {}", left_stick_x)
/// }
/// }
@ -338,206 +339,6 @@ pub enum ButtonSettingsError {
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug))]
#[require(GamepadSettings)]
pub struct Gamepad {
info: GamepadInfo,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub(crate) digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub(crate) analog: Axis<GamepadInput>,
}
impl Gamepad {
/// Creates a gamepad with the given metadata.
fn new(info: GamepadInfo) -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button.into(), 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type.into(), 0.0);
}
Self {
info,
analog,
digital: ButtonInput::default(),
}
}
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
pub fn name(&self) -> &str {
self.info.name.as_str()
}
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
pub fn vendor_id(&self) -> Option<u16> {
self.info.vendor_id
}
/// Returns the USB product ID as assigned by the [vendor], if available.
///
/// [vendor]: Self::vendor_id
pub fn product_id(&self) -> Option<u16> {
self.info.product_id
}
/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
///
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
pub fn get(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get(input.into())
}
/// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
///
/// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
pub fn get_unclamped(&self, input: impl Into<GamepadInput>) -> Option<f32> {
self.analog.get_unclamped(input.into())
}
/// Returns the left stick as a [`Vec2`]
pub fn left_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
}
}
/// Returns the right stick as a [`Vec2`]
pub fn right_stick(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
}
}
/// Returns the directional pad as a [`Vec2`]
pub fn dpad(&self) -> Vec2 {
Vec2 {
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
- self.get(GamepadButton::DPadLeft).unwrap_or(0.0),
y: self.get(GamepadButton::DPadUp).unwrap_or(0.0)
- self.get(GamepadButton::DPadDown).unwrap_or(0.0),
}
}
/// Returns `true` if the [`GamepadButton`] has been pressed.
pub fn pressed(&self, button_type: GamepadButton) -> bool {
self.digital.pressed(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has been pressed.
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.pressed(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have been pressed.
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.pressed(button_type))
}
/// Returns `true` if the [`GamepadButton`] has been pressed during the current frame.
///
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`].
pub fn just_pressed(&self, button_type: GamepadButton) -> bool {
self.digital.just_pressed(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has been pressed during the current frame.
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.just_pressed(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have been just pressed.
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.just_pressed(button_type))
}
/// Returns `true` if the [`GamepadButton`] has been released during the current frame.
///
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`].
pub fn just_released(&self, button_type: GamepadButton) -> bool {
self.digital.just_released(button_type)
}
/// Returns `true` if any item in [`GamepadButton`] has just been released.
pub fn any_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
button_inputs
.into_iter()
.any(|button_type| self.just_released(button_type))
}
/// Returns `true` if all items in [`GamepadButton`] have just been released.
pub fn all_just_released(
&self,
button_inputs: impl IntoIterator<Item = GamepadButton>,
) -> bool {
button_inputs
.into_iter()
.all(|button_type| self.just_released(button_type))
}
/// Returns an iterator over all digital [button]s that are pressed.
///
/// [button]: GamepadButton
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_pressed()
}
/// Returns an iterator over all digital [button]s that were just pressed.
///
/// [button]: GamepadButton
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_pressed()
}
/// Returns an iterator over all digital [button]s that were just released.
///
/// [button]: GamepadButton
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
self.digital.get_just_released()
}
/// Returns an iterator over all analog [axes].
///
/// [axes]: GamepadInput
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
self.analog.all_axes()
}
}
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// Metadata associated with a [`Gamepad`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct GamepadInfo {
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
pub name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
pub vendor_id: Option<u16>,
@ -545,6 +346,59 @@ pub struct GamepadInfo {
///
/// [vendor]: Self::vendor_id
pub product_id: Option<u16>,
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state
pub digital: ButtonInput<GamepadButton>,
/// [`Axis`] of [`GamepadButton`] representing their analog state.
pub analog: Axis<GamepadInput>,
}
impl Gamepad {
/// Returns the left stick as a [`Vec2`]
pub fn left_stick(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
y: self.analog.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
}
}
/// Returns the right stick as a [`Vec2`]
pub fn right_stick(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadAxis::RightStickX).unwrap_or(0.0),
y: self.analog.get(GamepadAxis::RightStickY).unwrap_or(0.0),
}
}
/// Returns the directional pad as a [`Vec2`]
pub fn dpad(&self) -> Vec2 {
Vec2 {
x: self.analog.get(GamepadButton::DPadRight).unwrap_or(0.0)
- self.analog.get(GamepadButton::DPadLeft).unwrap_or(0.0),
y: self.analog.get(GamepadButton::DPadUp).unwrap_or(0.0)
- self.analog.get(GamepadButton::DPadDown).unwrap_or(0.0),
}
}
}
impl Default for Gamepad {
fn default() -> Self {
let mut analog = Axis::default();
for button in GamepadButton::all().iter().copied() {
analog.set(button, 0.0);
}
for axis_type in GamepadAxis::all().iter().copied() {
analog.set(axis_type, 0.0);
}
Self {
vendor_id: None,
product_id: None,
digital: Default::default(),
analog,
}
}
}
/// Represents gamepad input types that are mapped in the range [0.0, 1.0].
@ -1356,12 +1210,23 @@ pub fn gamepad_connection_system(
for connection_event in connection_events.read() {
let id = connection_event.gamepad;
match &connection_event.connection {
GamepadConnection::Connected(info) => {
GamepadConnection::Connected {
name,
vendor_id,
product_id,
} => {
let Some(mut gamepad) = commands.get_entity(id) else {
warn!("Gamepad {:} removed before handling connection event.", id);
continue;
};
gamepad.insert(Gamepad::new(info.clone()));
gamepad.insert((
Name::new(name.clone()),
Gamepad {
vendor_id: *vendor_id,
product_id: *product_id,
..Default::default()
},
));
info!("Gamepad {:?} connected.", id);
}
GamepadConnection::Disconnected => {
@ -1379,6 +1244,9 @@ pub fn gamepad_connection_system(
}
}
// Note that we don't expose `gilrs::Gamepad::uuid` due to
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
//
/// The connection status of a gamepad.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -1389,7 +1257,20 @@ pub fn gamepad_connection_system(
)]
pub enum GamepadConnection {
/// The gamepad is connected.
Connected(GamepadInfo),
Connected {
/// The name of the gamepad.
///
/// This name is generally defined by the OS.
///
/// For example on Windows the name may be "HID-compliant game controller".
name: String,
/// The USB vendor ID as assigned by the USB-IF, if available.
vendor_id: Option<u16>,
/// The USB product ID as assigned by the vendor, if available.
product_id: Option<u16>,
},
/// The gamepad is disconnected.
Disconnected,
}
@ -1426,12 +1307,12 @@ pub fn gamepad_event_processing_system(
};
let Some(filtered_value) = gamepad_settings
.get_axis_settings(axis)
.filter(value, gamepad_axis.get(axis))
.filter(value, gamepad_axis.analog.get(axis))
else {
continue;
};
gamepad_axis.analog.set(axis.into(), filtered_value);
gamepad_axis.analog.set(axis, filtered_value);
let send_event = GamepadAxisChangedEvent::new(gamepad, axis, filtered_value);
processed_axis_events.send(send_event);
processed_events.send(GamepadEvent::from(send_event));
@ -1447,16 +1328,16 @@ pub fn gamepad_event_processing_system(
};
let Some(filtered_value) = settings
.get_button_axis_settings(button)
.filter(value, gamepad_buttons.get(button))
.filter(value, gamepad_buttons.analog.get(button))
else {
continue;
};
let button_settings = settings.get_button_settings(button);
gamepad_buttons.analog.set(button.into(), filtered_value);
gamepad_buttons.analog.set(button, filtered_value);
if button_settings.is_released(filtered_value) {
// Check if button was previously pressed
if gamepad_buttons.pressed(button) {
if gamepad_buttons.digital.pressed(button) {
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
gamepad,
button,
@ -1468,7 +1349,7 @@ pub fn gamepad_event_processing_system(
gamepad_buttons.digital.release(button);
} else if button_settings.is_pressed(filtered_value) {
// Check if button was previously not pressed
if !gamepad_buttons.pressed(button) {
if !gamepad_buttons.digital.pressed(button) {
processed_digital_events.send(GamepadButtonStateChangedEvent::new(
gamepad,
button,
@ -1626,8 +1507,8 @@ mod tests {
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent,
GamepadConnection::{Connected, Disconnected},
GamepadConnectionEvent, GamepadEvent, GamepadInfo, GamepadSettings,
RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
use crate::ButtonState;
use bevy_app::{App, PreUpdate};
@ -2000,11 +1881,11 @@ mod tests {
.resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new(
gamepad,
Connected(GamepadInfo {
name: String::from("Gamepad test"),
Connected {
name: "Test gamepad".to_string(),
vendor_id: None,
product_id: None,
}),
},
));
gamepad
}
@ -2522,13 +2403,8 @@ mod tests {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Pressed);
}
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
@ -2543,13 +2419,8 @@ mod tests {
.len(),
0
);
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.pressed(GamepadButton::DPadDown));
}
#[test]
@ -2568,23 +2439,13 @@ mod tests {
ctx.update();
// Check it is flagged for this frame
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.just_pressed(GamepadButton::DPadDown));
ctx.update();
//Check it clears next frame
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.just_pressed(GamepadButton::DPadDown));
}
#[test]
fn gamepad_buttons_released() {
@ -2627,13 +2488,8 @@ mod tests {
assert_eq!(event.button, GamepadButton::DPadDown);
assert_eq!(event.state, ButtonState::Released);
}
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.pressed(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.pressed(GamepadButton::DPadDown));
ctx.app
.world_mut()
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
@ -2672,23 +2528,13 @@ mod tests {
ctx.update();
// Check it is flagged for this frame
assert!(ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(gamepad.digital.just_released(GamepadButton::DPadDown));
ctx.update();
//Check it clears next frame
assert!(!ctx
.app
.world_mut()
.query::<&Gamepad>()
.get(ctx.app.world(), entity)
.unwrap()
.just_released(GamepadButton::DPadDown));
// Check it clears next frame
let gamepad = ctx.app.world_mut().get::<Gamepad>(entity).unwrap();
assert!(!gamepad.digital.just_released(GamepadButton::DPadDown));
}
#[test]

View file

@ -57,7 +57,7 @@ use gamepad::{
gamepad_connection_system, gamepad_event_processing_system, GamepadAxis,
GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
GamepadButtonStateChangedEvent, GamepadConnection, GamepadConnectionEvent, GamepadEvent,
GamepadInfo, GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
GamepadInput, GamepadRumbleRequest, GamepadSettings, RawGamepadAxisChangedEvent,
RawGamepadButtonChangedEvent, RawGamepadEvent,
};
@ -142,7 +142,6 @@ impl Plugin for InputPlugin {
.register_type::<GamepadButtonChangedEvent>()
.register_type::<GamepadAxisChangedEvent>()
.register_type::<GamepadButtonStateChangedEvent>()
.register_type::<GamepadInfo>()
.register_type::<GamepadConnection>()
.register_type::<GamepadSettings>()
.register_type::<GamepadAxis>()

View file

@ -135,7 +135,7 @@ pbr_anisotropy_texture = [
]
# Percentage-closer soft shadows
pbr_pcss = ["bevy_pbr?/pbr_pcss"]
experimental_pbr_pcss = ["bevy_pbr?/experimental_pbr_pcss"]
# Optimise for WebGL2
webgl = [
@ -173,7 +173,7 @@ android_shared_stdcxx = ["bevy_audio/android_shared_stdcxx"]
# screen readers and forks.)
accesskit_unix = ["bevy_winit/accesskit_unix"]
bevy_text = ["dep:bevy_text", "bevy_ui?/bevy_text"]
bevy_text = ["dep:bevy_text"]
bevy_render = [
"dep:bevy_render",

View file

@ -56,12 +56,13 @@ plugin_group! {
bevy_gizmos:::GizmoPlugin,
#[cfg(feature = "bevy_state")]
bevy_state::app:::StatesPlugin,
#[cfg(feature = "bevy_picking")]
bevy_picking:::DefaultPickingPlugins,
#[cfg(feature = "bevy_dev_tools")]
bevy_dev_tools:::DevToolsPlugin,
#[cfg(feature = "bevy_ci_testing")]
bevy_dev_tools::ci_testing:::CiTestingPlugin,
#[plugin_group]
#[cfg(feature = "bevy_picking")]
bevy_picking:::DefaultPickingPlugins,
#[doc(hidden)]
:IgnoreAmbiguitiesPlugin,
}

View file

@ -37,6 +37,8 @@ pub use bevy_gizmos as gizmos;
#[cfg(feature = "bevy_gltf")]
pub use bevy_gltf as gltf;
pub use bevy_hierarchy as hierarchy;
#[cfg(feature = "bevy_image")]
pub use bevy_image as image;
pub use bevy_input as input;
pub use bevy_log as log;
pub use bevy_math as math;

View file

@ -6,6 +6,10 @@ pub use crate::{
MinimalPlugins,
};
#[doc(hidden)]
#[cfg(feature = "bevy_image")]
pub use crate::image::prelude::*;
pub use bevy_derive::{bevy_main, Deref, DerefMut};
#[doc(hidden)]

View file

@ -1,7 +1,5 @@
//! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives).
use glam::Vec3A;
use crate::{
bounding::{Bounded2d, BoundingCircle},
ops,
@ -9,7 +7,7 @@ use crate::{
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d,
Line3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, Triangle3d,
},
Isometry2d, Isometry3d, Mat3, Vec2, Vec3,
Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A,
};
use super::{Aabb3d, Bounded3d, BoundingSphere};

View file

@ -180,7 +180,7 @@ impl NormedVectorSpace for f32 {
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparametrization of the original
/// interpolation curve between `p` and `q` must be the *linear* reparameterization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
@ -197,7 +197,7 @@ impl NormedVectorSpace for f32 {
/// / \
/// / \
/// / linear \
/// / reparametrization \
/// / reparameterization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|

View file

@ -362,7 +362,7 @@ where
}
}
/// A curve that has had its domain changed by a linear reparametrization (stretching and scaling).
/// A curve that has had its domain changed by a linear reparameterization (stretching and scaling).
/// Curves of this type are produced by [`Curve::reparametrize_linear`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]

View file

@ -3,9 +3,10 @@
//!
//! [easing functions]: EaseFunction
use crate::{Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace};
use super::{function_curve, Curve, Interval};
use crate::{
curve::{FunctionCurve, Interval},
Curve, Dir2, Dir3, Dir3A, Quat, Rot2, VectorSpace,
};
// TODO: Think about merging `Ease` with `StableInterpolate`
@ -28,13 +29,13 @@ pub trait Ease: Sized {
impl<V: VectorSpace> Ease for V {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
FunctionCurve::new(Interval::EVERYWHERE, move |t| V::lerp(start, end, t))
}
}
impl Ease for Rot2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
FunctionCurve::new(Interval::EVERYWHERE, move |t| Rot2::slerp(start, end, t))
}
}
@ -44,7 +45,7 @@ impl Ease for Quat {
let end_adjusted = if dot < 0.0 { -end } else { end };
let difference = end_adjusted * start.inverse();
let (axis, angle) = difference.to_axis_angle();
function_curve(Interval::EVERYWHERE, move |s| {
FunctionCurve::new(Interval::EVERYWHERE, move |s| {
Quat::from_axis_angle(axis, angle * s) * start
})
}
@ -52,7 +53,7 @@ impl Ease for Quat {
impl Ease for Dir2 {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
function_curve(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
FunctionCurve::new(Interval::EVERYWHERE, move |t| Dir2::slerp(start, end, t))
}
}
@ -71,20 +72,6 @@ impl Ease for Dir3A {
}
}
/// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
/// that connects them, using the given [ease function] to determine the form of the
/// curve in between.
///
/// [the unit interval]: Interval::UNIT
/// [ease function]: EaseFunction
pub fn easing_curve<T: Ease + Clone>(start: T, end: T, ease_fn: EaseFunction) -> EasingCurve<T> {
EasingCurve {
start,
end,
ease_fn,
}
}
/// A [`Curve`] that is defined by
///
/// - an initial `start` sample value at `t = 0`
@ -104,6 +91,22 @@ pub struct EasingCurve<T> {
ease_fn: EaseFunction,
}
impl<T> EasingCurve<T> {
/// Given a `start` and `end` value, create a curve parametrized over [the unit interval]
/// that connects them, using the given [ease function] to determine the form of the
/// curve in between.
///
/// [the unit interval]: Interval::UNIT
/// [ease function]: EaseFunction
pub fn new(start: T, end: T, ease_fn: EaseFunction) -> Self {
Self {
start,
end,
ease_fn,
}
}
}
impl<T> Curve<T> for EasingCurve<T>
where
T: Ease + Clone,

View file

@ -56,13 +56,13 @@
//! # use bevy_math::vec3;
//! # use bevy_math::curve::*;
//! // A sinusoid:
//! let sine_curve = function_curve(Interval::EVERYWHERE, f32::sin);
//! let sine_curve = FunctionCurve::new(Interval::EVERYWHERE, f32::sin);
//!
//! // A sawtooth wave:
//! let sawtooth_curve = function_curve(Interval::EVERYWHERE, |t| t % 1.0);
//! let sawtooth_curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t % 1.0);
//!
//! // A helix:
//! let helix_curve = function_curve(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos()));
//! let helix_curve = FunctionCurve::new(Interval::EVERYWHERE, |theta| vec3(theta.sin(), theta, theta.cos()));
//! ```
//!
//! Sample-interpolated curves commonly arises in both rasterization and in animation, and this library
@ -127,7 +127,7 @@
//! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::TAU;
//! // Our original curve, which may look something like this:
//! let ellipse_curve = function_curve(
//! let ellipse_curve = FunctionCurve::new(
//! interval(0.0, TAU).unwrap(),
//! |t| vec2(t.cos(), t.sin() * 2.0)
//! );
@ -141,7 +141,7 @@
//! ```rust
//! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::TAU;
//! # let ellipse_curve = function_curve(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0));
//! # let ellipse_curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), |t| vec2(t.cos(), t.sin() * 2.0));
//! # let ellipse_motion_curve = ellipse_curve.map(|pos| pos.extend(0.0));
//! // Change the domain to `[0, 1]` instead of `[0, TAU]`:
//! let final_curve = ellipse_motion_curve.reparametrize_linear(Interval::UNIT).unwrap();
@ -155,7 +155,7 @@
//! // A line segment curve connecting two points in the plane:
//! let start = vec2(-1.0, 1.0);
//! let end = vec2(1.0, 1.0);
//! let segment = function_curve(Interval::UNIT, |t| start.lerp(end, t));
//! let segment = FunctionCurve::new(Interval::UNIT, |t| start.lerp(end, t));
//!
//! // Let's make a curve that goes back and forth along this line segment forever.
//! //
@ -177,13 +177,13 @@
//! # use bevy_math::{vec2, prelude::*};
//! # use std::f32::consts::PI;
//! // A line segment connecting `(-1, 0)` to `(0, 0)`:
//! let line_curve = function_curve(
//! let line_curve = FunctionCurve::new(
//! Interval::UNIT,
//! |t| vec2(-1.0, 0.0).lerp(vec2(0.0, 0.0), t)
//! );
//!
//! // A half-circle curve starting at `(0, 0)`:
//! let half_circle_curve = function_curve(
//! let half_circle_curve = FunctionCurve::new(
//! interval(0.0, PI).unwrap(),
//! |t| vec2(t.cos() * -1.0 + 1.0, t.sin())
//! );
@ -198,10 +198,10 @@
//! ```rust
//! # use bevy_math::{vec2, prelude::*};
//! // Some entity's position in 2D:
//! let position_curve = function_curve(Interval::UNIT, |t| vec2(t.cos(), t.sin()));
//! let position_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t.cos(), t.sin()));
//!
//! // The same entity's orientation, described as a rotation. (In this case it will be spinning.)
//! let orientation_curve = function_curve(Interval::UNIT, |t| Rot2::radians(5.0 * t));
//! let orientation_curve = FunctionCurve::new(Interval::UNIT, |t| Rot2::radians(5.0 * t));
//!
//! // Both in one curve with `(Vec2, Rot2)` output:
//! let position_and_orientation = position_curve.zip(orientation_curve).unwrap();
@ -220,7 +220,7 @@
//! ```rust
//! # use bevy_math::{vec2, prelude::*};
//! // A curve that is not easily transported because it relies on evaluating a function:
//! let interesting_curve = function_curve(Interval::UNIT, |t| vec2(t * 3.0, t.exp()));
//! let interesting_curve = FunctionCurve::new(Interval::UNIT, |t| vec2(t * 3.0, t.exp()));
//!
//! // A rasterized form of the preceding curve which is just a `SampleAutoCurve`. Inside, this
//! // just stores an `Interval` along with a buffer of sample data, so it's easy to serialize
@ -250,7 +250,7 @@
//! Here is a demonstration:
//! ```rust
//! # use bevy_math::prelude::*;
//! # let some_magic_constructor = || easing_curve(0.0, 1.0, EaseFunction::ElasticInOut).graph();
//! # let some_magic_constructor = || EasingCurve::new(0.0, 1.0, EaseFunction::ElasticInOut).graph();
//! //`my_curve` is obtained somehow. It is a `Curve<(f32, f32)>`.
//! let my_curve = some_magic_constructor();
//!
@ -272,7 +272,7 @@
//! [changing parametrizations]: Curve::reparametrize
//! [mapping output]: Curve::map
//! [rasterization]: Curve::resample
//! [functions]: function_curve
//! [functions]: FunctionCurve
//! [sample interpolation]: SampleCurve
//! [splines]: crate::cubic_splines
//! [easings]: easing
@ -282,7 +282,7 @@
//! [`zip`]: Curve::zip
//! [`resample`]: Curve::resample
//!
//! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `function_curve
//! [^footnote]: In fact, universal as well, in some sense: if `curve` is any curve, then `FunctionCurve::new
//! (curve.domain(), |t| curve.sample_unchecked(t))` is an equivalent function curve.
pub mod adaptors;
@ -414,7 +414,7 @@ pub trait Curve<T> {
/// factor rather than multiplying:
/// ```
/// # use bevy_math::curve::*;
/// let my_curve = constant_curve(Interval::UNIT, 1.0);
/// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let scaled_curve = my_curve.reparametrize(interval(0.0, 2.0).unwrap(), |t| t / 2.0);
/// ```
/// This kind of linear remapping is provided by the convenience method
@ -425,12 +425,12 @@ pub trait Curve<T> {
/// // Reverse a curve:
/// # use bevy_math::curve::*;
/// # use bevy_math::vec2;
/// let my_curve = constant_curve(Interval::UNIT, 1.0);
/// let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let domain = my_curve.domain();
/// let reversed_curve = my_curve.reparametrize(domain, |t| domain.end() - (t - domain.start()));
///
/// // Take a segment of a curve:
/// # let my_curve = constant_curve(Interval::UNIT, 1.0);
/// # let my_curve = ConstantCurve::new(Interval::UNIT, 1.0);
/// let curve_segment = my_curve.reparametrize(interval(0.0, 0.5).unwrap(), |t| 0.5 + t);
/// ```
#[must_use]
@ -712,7 +712,7 @@ pub trait Curve<T> {
/// ```
/// # use bevy_math::*;
/// # use bevy_math::curve::*;
/// let quarter_rotation = function_curve(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t));
/// let quarter_rotation = FunctionCurve::new(interval(0.0, 90.0).unwrap(), |t| Rot2::degrees(t));
/// // A curve which only stores three data points and uses `nlerp` to interpolate them:
/// let resampled_rotation = quarter_rotation.resample(3, |x, y, t| x.nlerp(*y, t));
/// ```
@ -863,7 +863,7 @@ pub trait Curve<T> {
/// # Example
/// ```
/// # use bevy_math::curve::*;
/// let my_curve = function_curve(Interval::UNIT, |t| t * t + 1.0);
/// let my_curve = FunctionCurve::new(Interval::UNIT, |t| t * t + 1.0);
///
/// // Borrow `my_curve` long enough to resample a mapped version. Note that `map` takes
/// // ownership of its input.
@ -903,7 +903,7 @@ where
}
}
/// An error indicating that a linear reparametrization couldn't be performed because of
/// An error indicating that a linear reparameterization couldn't be performed because of
/// malformed inputs.
#[derive(Debug, Error, Display)]
#[display("Could not build a linear function to reparametrize this curve")]
@ -912,8 +912,8 @@ pub enum LinearReparamError {
#[display("This curve has unbounded domain")]
SourceCurveUnbounded,
/// The target interval for reparametrization was unbounded.
#[display("The target interval for reparametrization is unbounded")]
/// The target interval for reparameterization was unbounded.
#[display("The target interval for reparameterization is unbounded")]
TargetIntervalUnbounded,
}
@ -976,27 +976,8 @@ pub enum ResamplingError {
UnboundedDomain,
}
/// Create a [`Curve`] that constantly takes the given `value` over the given `domain`.
pub fn constant_curve<T: Clone>(domain: Interval, value: T) -> ConstantCurve<T> {
ConstantCurve { domain, value }
}
/// Convert the given function `f` into a [`Curve`] with the given `domain`, sampled by
/// evaluating the function.
pub fn function_curve<T, F>(domain: Interval, f: F) -> FunctionCurve<T, F>
where
F: Fn(f32) -> T,
{
FunctionCurve {
domain,
f,
_phantom: PhantomData,
}
}
#[cfg(test)]
mod tests {
use super::easing::*;
use super::*;
use crate::{ops, Quat};
use approx::{assert_abs_diff_eq, AbsDiffEq};
@ -1005,7 +986,7 @@ mod tests {
#[test]
fn curve_can_be_made_into_an_object() {
let curve = constant_curve(Interval::UNIT, 42.0);
let curve = ConstantCurve::new(Interval::UNIT, 42.0);
let curve: &dyn Curve<f64> = &curve;
assert_eq!(curve.sample(1.0), Some(42.0));
@ -1014,21 +995,21 @@ mod tests {
#[test]
fn constant_curves() {
let curve = constant_curve(Interval::EVERYWHERE, 5.0);
let curve = ConstantCurve::new(Interval::EVERYWHERE, 5.0);
assert!(curve.sample_unchecked(-35.0) == 5.0);
let curve = constant_curve(Interval::UNIT, true);
let curve = ConstantCurve::new(Interval::UNIT, true);
assert!(curve.sample_unchecked(2.0));
assert!(curve.sample(2.0).is_none());
}
#[test]
fn function_curves() {
let curve = function_curve(Interval::EVERYWHERE, |t| t * t);
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * t);
assert!(curve.sample_unchecked(2.0).abs_diff_eq(&4.0, f32::EPSILON));
assert!(curve.sample_unchecked(-3.0).abs_diff_eq(&9.0, f32::EPSILON));
let curve = function_curve(interval(0.0, f32::INFINITY).unwrap(), ops::log2);
let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::log2);
assert_eq!(curve.sample_unchecked(3.5), ops::log2(3.5));
assert!(curve.sample_unchecked(-1.0).is_nan());
assert!(curve.sample(-1.0).is_none());
@ -1038,7 +1019,7 @@ mod tests {
fn linear_curve() {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::Linear);
let curve = EasingCurve::new(start, end, EaseFunction::Linear);
let mid = (start + end) / 2.0;
@ -1054,7 +1035,7 @@ mod tests {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::Steps(4));
let curve = EasingCurve::new(start, end, EaseFunction::Steps(4));
[
(0.0, start),
(0.124, start),
@ -1078,7 +1059,7 @@ mod tests {
let start = Vec2::ZERO;
let end = Vec2::new(1.0, 2.0);
let curve = easing_curve(start, end, EaseFunction::QuadraticIn);
let curve = EasingCurve::new(start, end, EaseFunction::QuadraticIn);
[
(0.0, start),
(0.25, Vec2::new(0.0625, 0.125)),
@ -1093,7 +1074,7 @@ mod tests {
#[test]
fn mapping() {
let curve = function_curve(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let mapped_curve = curve.map(|x| x / 7.0);
assert_eq!(mapped_curve.sample_unchecked(3.5), (3.5 * 3.0 + 1.0) / 7.0);
assert_eq!(
@ -1102,7 +1083,7 @@ mod tests {
);
assert_eq!(mapped_curve.domain(), Interval::EVERYWHERE);
let curve = function_curve(Interval::UNIT, |t| t * TAU);
let curve = FunctionCurve::new(Interval::UNIT, |t| t * TAU);
let mapped_curve = curve.map(Quat::from_rotation_z);
assert_eq!(mapped_curve.sample_unchecked(0.0), Quat::IDENTITY);
assert!(mapped_curve.sample_unchecked(1.0).is_near_identity());
@ -1111,7 +1092,7 @@ mod tests {
#[test]
fn reverse() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-0.1), None);
assert_eq!(rev_curve.sample(0.0), Some(1.0 * 3.0 + 1.0));
@ -1119,7 +1100,7 @@ mod tests {
assert_eq!(rev_curve.sample(1.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(rev_curve.sample(1.1), None);
let curve = function_curve(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::new(-2.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let rev_curve = curve.reverse().unwrap();
assert_eq!(rev_curve.sample(-2.1), None);
assert_eq!(rev_curve.sample(-2.0), Some(1.0 * 3.0 + 1.0));
@ -1130,7 +1111,7 @@ mod tests {
#[test]
fn repeat() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let repeat_curve = curve.by_ref().repeat(1).unwrap();
assert_eq!(repeat_curve.sample(-0.1), None);
assert_eq!(repeat_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1159,7 +1140,7 @@ mod tests {
#[test]
fn ping_pong() {
let curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-0.1), None);
assert_eq!(ping_pong_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1169,7 +1150,7 @@ mod tests {
assert_eq!(ping_pong_curve.sample(2.0), Some(0.0 * 3.0 + 1.0));
assert_eq!(ping_pong_curve.sample(2.1), None);
let curve = function_curve(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::new(-2.0, 2.0).unwrap(), |t| t * 3.0 + 1.0);
let ping_pong_curve = curve.ping_pong().unwrap();
assert_eq!(ping_pong_curve.sample(-2.1), None);
assert_eq!(ping_pong_curve.sample(-2.0), Some(-2.0 * 3.0 + 1.0));
@ -1182,8 +1163,8 @@ mod tests {
#[test]
fn continue_chain() {
let first = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let second = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * t);
let first = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let second = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * t);
let c0_chain_curve = first.chain_continue(second).unwrap();
assert_eq!(c0_chain_curve.sample(-0.1), None);
assert_eq!(c0_chain_curve.sample(0.0), Some(0.0 * 3.0 + 1.0));
@ -1195,8 +1176,8 @@ mod tests {
}
#[test]
fn reparametrization() {
let curve = function_curve(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
fn reparameterization() {
let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let reparametrized_curve = curve
.by_ref()
.reparametrize(interval(0.0, f32::INFINITY).unwrap(), ops::exp2);
@ -1216,7 +1197,7 @@ mod tests {
#[test]
fn multiple_maps() {
// Make sure these actually happen in the right order.
let curve = function_curve(Interval::UNIT, ops::exp2);
let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_mapped = curve.map(ops::log2);
let second_mapped = first_mapped.map(|x| x * -2.0);
assert_abs_diff_eq!(second_mapped.sample_unchecked(0.0), 0.0);
@ -1227,7 +1208,7 @@ mod tests {
#[test]
fn multiple_reparams() {
// Make sure these happen in the right order too.
let curve = function_curve(Interval::UNIT, ops::exp2);
let curve = FunctionCurve::new(Interval::UNIT, ops::exp2);
let first_reparam = curve.reparametrize(interval(1.0, 2.0).unwrap(), ops::log2);
let second_reparam = first_reparam.reparametrize(Interval::UNIT, |t| t + 1.0);
assert_abs_diff_eq!(second_reparam.sample_unchecked(0.0), 1.0);
@ -1237,7 +1218,7 @@ mod tests {
#[test]
fn resampling() {
let curve = function_curve(interval(1.0, 4.0).unwrap(), ops::log2);
let curve = FunctionCurve::new(interval(1.0, 4.0).unwrap(), ops::log2);
// Need at least one segment to sample.
let nice_try = curve.by_ref().resample_auto(0);
@ -1257,7 +1238,7 @@ mod tests {
}
// Another example.
let curve = function_curve(interval(0.0, TAU).unwrap(), ops::cos);
let curve = FunctionCurve::new(interval(0.0, TAU).unwrap(), ops::cos);
let resampled_curve = curve.by_ref().resample_auto(1000).unwrap();
for test_pt in curve.domain().spaced_points(1001).unwrap() {
let expected = curve.sample_unchecked(test_pt);
@ -1271,7 +1252,7 @@ mod tests {
#[test]
fn uneven_resampling() {
let curve = function_curve(interval(0.0, f32::INFINITY).unwrap(), ops::exp);
let curve = FunctionCurve::new(interval(0.0, f32::INFINITY).unwrap(), ops::exp);
// Need at least two points to resample.
let nice_try = curve.by_ref().resample_uneven_auto([1.0; 1]);
@ -1290,7 +1271,7 @@ mod tests {
assert_abs_diff_eq!(resampled_curve.domain().end(), 9.9, epsilon = 1e-6);
// Another example.
let curve = function_curve(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let curve = FunctionCurve::new(interval(1.0, f32::INFINITY).unwrap(), ops::log2);
let sample_points = (0..10).map(|idx| ops::exp2(idx as f32));
let resampled_curve = curve.by_ref().resample_uneven_auto(sample_points).unwrap();
for idx in 0..10 {
@ -1306,7 +1287,7 @@ mod tests {
fn sample_iterators() {
let times = [-0.5, 0.0, 0.5, 1.0, 1.5];
let curve = function_curve(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let curve = FunctionCurve::new(Interval::EVERYWHERE, |t| t * 3.0 + 1.0);
let samples = curve.sample_iter_unchecked(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();
@ -1316,7 +1297,7 @@ mod tests {
assert_eq!(y3, 1.0 * 3.0 + 1.0);
assert_eq!(y4, 1.5 * 3.0 + 1.0);
let finite_curve = function_curve(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let finite_curve = FunctionCurve::new(Interval::new(0.0, 1.0).unwrap(), |t| t * 3.0 + 1.0);
let samples = finite_curve.sample_iter(times).collect::<Vec<_>>();
let [y0, y1, y2, y3, y4] = samples.try_into().unwrap();

View file

@ -58,17 +58,19 @@ pub use sampling::{FromRng, ShapeSample};
pub mod prelude {
#[doc(hidden)]
pub use crate::{
bvec2, bvec3, bvec3a, bvec4, bvec4a,
cubic_splines::{
CubicBSpline, CubicBezier, CubicCardinalSpline, CubicCurve, CubicGenerator,
CubicHermite, CubicNurbs, CubicNurbsError, CubicSegment, CyclicCubicGenerator,
RationalCurve, RationalGenerator, RationalSegment,
},
direction::{Dir2, Dir3, Dir3A},
ops,
ivec2, ivec3, ivec4, mat2, mat3, mat3a, mat4, ops,
primitives::*,
BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d,
Isometry3d, Mat2, Mat3, Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect,
UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles,
quat, uvec2, uvec3, uvec4, vec2, vec3, vec3a, vec4, BVec2, BVec3, BVec3A, BVec4, BVec4A,
EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d, Isometry3d, Mat2, Mat3, Mat3A,
Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3A, Vec3Swizzles, Vec4, Vec4Swizzles,
};
#[doc(hidden)]

View file

@ -292,6 +292,22 @@ impl Default for CircularSector {
}
}
impl Measured2d for CircularSector {
#[inline(always)]
fn area(&self) -> f32 {
self.arc.radius.squared() * self.arc.half_angle
}
#[inline(always)]
fn perimeter(&self) -> f32 {
if self.half_angle() >= PI {
self.arc.radius * 2.0 * PI
} else {
2.0 * self.radius() + self.arc_length()
}
}
}
impl CircularSector {
/// Create a new [`CircularSector`] from a `radius` and an `angle`
#[inline(always)]
@ -382,12 +398,6 @@ impl CircularSector {
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
/// Returns the area of this sector
#[inline(always)]
pub fn area(&self) -> f32 {
self.arc.radius.squared() * self.arc.half_angle
}
}
/// A primitive representing a circular segment:
@ -425,6 +435,17 @@ impl Default for CircularSegment {
}
}
impl Measured2d for CircularSegment {
#[inline(always)]
fn area(&self) -> f32 {
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
}
#[inline(always)]
fn perimeter(&self) -> f32 {
self.chord_length() + self.arc_length()
}
}
impl CircularSegment {
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
#[inline(always)]
@ -515,17 +536,12 @@ impl CircularSegment {
pub fn sagitta(&self) -> f32 {
self.arc.sagitta()
}
/// Returns the area of this segment
#[inline(always)]
pub fn area(&self) -> f32 {
0.5 * self.arc.radius.squared() * (self.arc.angle() - ops::sin(self.arc.angle()))
}
}
#[cfg(test)]
mod arc_tests {
use core::f32::consts::FRAC_PI_4;
use core::f32::consts::SQRT_2;
use approx::assert_abs_diff_eq;
@ -548,7 +564,9 @@ mod arc_tests {
is_minor: bool,
is_major: bool,
sector_area: f32,
sector_perimeter: f32,
segment_area: f32,
segment_perimeter: f32,
}
impl ArcTestCase {
@ -581,6 +599,7 @@ mod arc_tests {
assert_abs_diff_eq!(self.apothem, sector.apothem());
assert_abs_diff_eq!(self.sagitta, sector.sagitta());
assert_abs_diff_eq!(self.sector_area, sector.area());
assert_abs_diff_eq!(self.sector_perimeter, sector.perimeter());
}
fn check_segment(&self, segment: CircularSegment) {
@ -593,6 +612,7 @@ mod arc_tests {
assert_abs_diff_eq!(self.apothem, segment.apothem());
assert_abs_diff_eq!(self.sagitta, segment.sagitta());
assert_abs_diff_eq!(self.segment_area, segment.area());
assert_abs_diff_eq!(self.segment_perimeter, segment.perimeter());
}
}
@ -615,7 +635,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 2.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(1.0, 0.0));
@ -642,7 +664,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: 0.0,
sector_perimeter: 0.0,
segment_area: 0.0,
segment_perimeter: 0.0,
};
tests.check_arc(Arc2d::new(0.0, FRAC_PI_4));
@ -670,7 +694,9 @@ mod arc_tests {
is_minor: true,
is_major: false,
sector_area: FRAC_PI_4,
sector_perimeter: FRAC_PI_2 + 2.0,
segment_area: FRAC_PI_4 - 0.5,
segment_perimeter: FRAC_PI_2 + SQRT_2,
};
tests.check_arc(Arc2d::from_turns(1.0, 0.25));
@ -697,7 +723,9 @@ mod arc_tests {
is_minor: true,
is_major: true,
sector_area: FRAC_PI_2,
sector_perimeter: PI + 2.0,
segment_area: FRAC_PI_2,
segment_perimeter: PI + 2.0,
};
tests.check_arc(Arc2d::from_radians(1.0, PI));
@ -724,7 +752,9 @@ mod arc_tests {
is_minor: false,
is_major: true,
sector_area: PI,
sector_perimeter: 2.0 * PI,
segment_area: PI,
segment_perimeter: 2.0 * PI,
};
tests.check_arc(Arc2d::from_degrees(1.0, 360.0));

View file

@ -331,7 +331,17 @@ impl Rot2 {
/// Returns the angle in radians needed to make `self` and `other` coincide.
#[inline]
#[deprecated(
since = "0.15.0",
note = "Use `angle_to` instead, the semantics of `angle_between` will change in the future."
)]
pub fn angle_between(self, other: Self) -> f32 {
self.angle_to(other)
}
/// Returns the angle in radians needed to make `self` and `other` coincide.
#[inline]
pub fn angle_to(self, other: Self) -> f32 {
(other * self.inverse()).as_radians()
}
@ -424,7 +434,7 @@ impl Rot2 {
/// ```
#[inline]
pub fn slerp(self, end: Self, s: f32) -> Self {
self * Self::radians(self.angle_between(end) * s)
self * Self::radians(self.angle_to(end) * s)
}
}
@ -567,10 +577,7 @@ mod tests {
assert_relative_eq!((rotation1 * rotation2.inverse()).as_degrees(), 45.0);
// This should be equivalent to the above
assert_relative_eq!(
rotation2.angle_between(rotation1),
core::f32::consts::FRAC_PI_4
);
assert_relative_eq!(rotation2.angle_to(rotation1), core::f32::consts::FRAC_PI_4);
}
#[test]

View file

@ -24,7 +24,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
# misc
bitflags = { version = "2.3", features = ["serde"] }
bytemuck = { version = "1.5" }
wgpu = { version = "22", default-features = false }
wgpu = { version = "23", default-features = false }
serde = { version = "1", features = ["derive"] }
hexasphere = "15.0"
derive_more = { version = "1", default-features = false, features = [

View file

@ -103,19 +103,36 @@ impl Indices {
/// Add an index. If the index is greater than `u16::MAX`,
/// the storage will be converted to `u32`.
pub fn push(&mut self, index: u32) {
self.extend([index]);
}
}
/// Extend the indices with indices from an iterator.
/// Semantically equivalent to calling [`push`](Indices::push) for each element in the iterator,
/// but more efficient.
impl Extend<u32> for Indices {
fn extend<T: IntoIterator<Item = u32>>(&mut self, iter: T) {
let mut iter = iter.into_iter();
match self {
Indices::U32(vec) => vec.push(index),
Indices::U16(vec) => match u16::try_from(index) {
Ok(index) => vec.push(index),
Err(_) => {
let new_vec = vec
.iter()
.map(|&index| u32::from(index))
.chain(iter::once(index))
.collect::<Vec<u32>>();
*self = Indices::U32(new_vec);
Indices::U32(indices) => indices.extend(iter),
Indices::U16(indices) => {
indices.reserve(iter.size_hint().0);
while let Some(index) = iter.next() {
match u16::try_from(index) {
Ok(index) => indices.push(index),
Err(_) => {
let new_vec = indices
.iter()
.map(|&index| u32::from(index))
.chain(iter::once(index))
.chain(iter)
.collect::<Vec<u32>>();
*self = Indices::U32(new_vec);
break;
}
}
}
},
}
}
}
}
@ -181,4 +198,27 @@ mod tests {
indices.iter().collect::<Vec<_>>()
);
}
#[test]
fn test_indices_extend() {
let mut indices = Indices::U16(Vec::new());
indices.extend([10, 11]);
assert_eq!(IndexFormat::Uint16, IndexFormat::from(&indices));
assert_eq!(vec![10, 11], indices.iter().collect::<Vec<_>>());
// Add a value that is too large for `u16` so the storage should be converted to `U32`.
indices.extend([12, 0x10013, 0x10014]);
assert_eq!(IndexFormat::Uint32, IndexFormat::from(&indices));
assert_eq!(
vec![10, 11, 12, 0x10013, 0x10014],
indices.iter().collect::<Vec<_>>()
);
indices.extend([15, 0x10016]);
assert_eq!(IndexFormat::Uint32, IndexFormat::from(&indices));
assert_eq!(
vec![10, 11, 12, 0x10013, 0x10014, 15, 0x10016],
indices.iter().collect::<Vec<_>>()
);
}
}

View file

@ -793,10 +793,7 @@ impl Mesh {
use VertexAttributeValues::*;
// The indices of `other` should start after the last vertex of `self`.
let index_offset = self
.attribute(Mesh::ATTRIBUTE_POSITION)
.get_or_insert(&Float32x3(Vec::default()))
.len();
let index_offset = self.count_vertices();
// Extend attributes of `self` with attributes of `other`.
for (attribute, values) in self.attributes_mut() {
@ -842,20 +839,7 @@ impl Mesh {
// Extend indices of `self` with indices of `other`.
if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) {
match (indices, other_indices) {
(Indices::U16(i1), Indices::U16(i2)) => {
i1.extend(i2.iter().map(|i| *i + index_offset as u16));
}
(Indices::U32(i1), Indices::U32(i2)) => {
i1.extend(i2.iter().map(|i| *i + index_offset as u32));
}
(Indices::U16(i1), Indices::U32(i2)) => {
i1.extend(i2.iter().map(|i| *i as u16 + index_offset as u16));
}
(Indices::U32(i1), Indices::U16(i2)) => {
i1.extend(i2.iter().map(|i| *i as u32 + index_offset as u32));
}
}
indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32));
}
}

View file

@ -14,7 +14,7 @@ webgpu = []
pbr_transmission_textures = []
pbr_multi_layer_material_textures = []
pbr_anisotropy_texture = []
pbr_pcss = []
experimental_pbr_pcss = []
shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"]
ios_simulator = ["bevy_render/ios_simulator"]
@ -37,6 +37,7 @@ bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.15.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.15.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
"bevy",
@ -62,7 +63,7 @@ lz4_flex = { version = "0.11", default-features = false, features = [
], optional = true }
range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.3.0", optional = true }
meshopt = { version = "0.4", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.13", optional = true }
bitvec = { version = "1", optional = true }

View file

@ -5,6 +5,9 @@ use crate::{
ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
};
use crate::{
MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, ViewLightsUniformOffset,
};
use bevy_app::prelude::*;
use bevy_asset::{load_internal_asset, Handle};
use bevy_core_pipeline::{
@ -16,6 +19,7 @@ use bevy_core_pipeline::{
tonemapping::{DebandDither, Tonemapping},
};
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_image::BevyDefault as _;
use bevy_render::{
extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
@ -23,15 +27,10 @@ use bevy_render::{
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
render_resource::{binding_types::uniform_buffer, *},
renderer::{RenderContext, RenderDevice},
texture::BevyDefault,
view::{ExtractedView, ViewTarget, ViewUniformOffset},
Render, RenderApp, RenderSet,
};
use crate::{
MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset, ViewLightsUniformOffset,
};
pub struct DeferredPbrLightingPlugin;
pub const DEFERRED_LIGHTING_SHADER_HANDLE: Handle<Shader> =
@ -390,6 +389,7 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
}),
multisample: MultisampleState::default(),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}

Some files were not shown because too many files have changed in this diff Show more