mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 22:50:19 +00:00
Merge commit '2d91a6fc39ffef7554aa7f989c024338a366e08d' into native-depth-clamp
This commit is contained in:
commit
8d210215ce
295 changed files with 4085 additions and 2635 deletions
|
@ -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: ''
|
||||
---
|
||||
|
||||
|
|
24
.github/workflows/ci.yml
vendored
24
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
|
4
.github/workflows/validation-jobs.yml
vendored
4
.github/workflows/validation-jobs.yml
vendored
|
@ -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
|
||||
|
|
26
.github/workflows/weekly.yml
vendored
26
.github/workflows/weekly.yml
vendored
|
@ -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
12
.gitignore
vendored
|
@ -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
|
||||
|
|
49
Cargo.toml
49
Cargo.toml
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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;
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
5
crates/bevy_animation/src/lib.rs
Executable file → Normal 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 {
|
||||
|
|
|
@ -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])*
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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?;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
//! ));
|
||||
//! }
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -98,6 +98,7 @@ impl SpecializedRenderPipeline for BlitPipeline {
|
|||
..Default::default()
|
||||
},
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ impl SpecializedRenderPipeline for BloomDownsamplingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -124,6 +124,7 @@ impl SpecializedRenderPipeline for BloomUpsamplingPipeline {
|
|||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -160,6 +160,7 @@ impl FromWorld for CopyDeferredLightingIdPipeline {
|
|||
}),
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: vec![],
|
||||
zero_initialize_workgroup_memory: false,
|
||||
});
|
||||
|
||||
Self {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>`
|
||||
///
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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())
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
/// });
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
0
crates/bevy_hierarchy/src/lib.rs
Executable file → Normal 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",
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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",
|
||||
] }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>()
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 \
|
||||
/// / \
|
||||
/// |-------------------------------------|
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue