Merge branch 'main' into split-more-window
This commit is contained in:
commit
b65ef4da1b
@ -4325,6 +4325,14 @@ description = "Demonstrates specular tints and maps"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "test_invalid_skinned_mesh"
|
||||
path = "tests/3d/test_invalid_skinned_mesh.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.test_invalid_skinned_mesh]
|
||||
hidden = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// For 2d replace `bevy_pbr::mesh_functions` with `bevy_sprite::mesh2d_functions`
|
||||
// and `mesh_position_local_to_clip` with `mesh2d_position_local_to_clip`.
|
||||
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
|
||||
|
||||
struct CustomMaterial {
|
||||
|
||||
@ -17,6 +17,12 @@
|
||||
|
||||
struct MyExtendedMaterial {
|
||||
quantize_steps: u32,
|
||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
|
||||
// Web examples WebGL2 support: structs must be 16 byte aligned.
|
||||
_webgl2_padding_8b: u32,
|
||||
_webgl2_padding_12b: u32,
|
||||
_webgl2_padding_16b: u32,
|
||||
#endif
|
||||
}
|
||||
|
||||
@group(3) @binding(100)
|
||||
|
||||
@ -3,6 +3,7 @@ use bevy_asset::{Asset, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::Vec3;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::components::Transform;
|
||||
|
||||
/// The way Bevy manages the sound playback.
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
@ -10,10 +11,10 @@ use bevy_reflect::prelude::*;
|
||||
pub enum PlaybackMode {
|
||||
/// Play the sound once. Do nothing when it ends.
|
||||
///
|
||||
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
||||
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
||||
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
|
||||
/// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
|
||||
///
|
||||
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
||||
/// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
|
||||
/// added again.
|
||||
Once,
|
||||
/// Repeat the sound forever.
|
||||
@ -27,7 +28,7 @@ pub enum PlaybackMode {
|
||||
/// Initial settings to be used when audio starts playing.
|
||||
///
|
||||
/// If you would like to control the audio while it is playing, query for the
|
||||
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink]
|
||||
/// [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink)
|
||||
/// components. Changes to this component will *not* be applied to already-playing audio.
|
||||
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
@ -78,10 +79,10 @@ impl Default for PlaybackSettings {
|
||||
impl PlaybackSettings {
|
||||
/// Will play the associated audio source once.
|
||||
///
|
||||
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
|
||||
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
|
||||
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
|
||||
/// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
|
||||
///
|
||||
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and
|
||||
/// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
|
||||
/// added again.
|
||||
pub const ONCE: PlaybackSettings = PlaybackSettings {
|
||||
mode: PlaybackMode::Once,
|
||||
@ -164,14 +165,15 @@ impl PlaybackSettings {
|
||||
|
||||
/// Settings for the listener for spatial audio sources.
|
||||
///
|
||||
/// This must be accompanied by `Transform` and `GlobalTransform`.
|
||||
/// Only one entity with a `SpatialListener` should be present at any given time.
|
||||
/// This is accompanied by [`Transform`] and [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
/// Only one entity with a [`SpatialListener`] should be present at any given time.
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[require(Transform)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
pub struct SpatialListener {
|
||||
/// Left ear position relative to the `GlobalTransform`.
|
||||
/// Left ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
pub left_ear_offset: Vec3,
|
||||
/// Right ear position relative to the `GlobalTransform`.
|
||||
/// Right ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
|
||||
pub right_ear_offset: Vec3,
|
||||
}
|
||||
|
||||
@ -182,7 +184,7 @@ impl Default for SpatialListener {
|
||||
}
|
||||
|
||||
impl SpatialListener {
|
||||
/// Creates a new `SpatialListener` component.
|
||||
/// Creates a new [`SpatialListener`] component.
|
||||
///
|
||||
/// `gap` is the distance between the left and right "ears" of the listener. Ears are
|
||||
/// positioned on the x axis.
|
||||
@ -203,12 +205,12 @@ impl SpatialListener {
|
||||
pub struct SpatialScale(pub Vec3);
|
||||
|
||||
impl SpatialScale {
|
||||
/// Create a new `SpatialScale` with the same value for all 3 dimensions.
|
||||
/// Create a new [`SpatialScale`] with the same value for all 3 dimensions.
|
||||
pub const fn new(scale: f32) -> Self {
|
||||
Self(Vec3::splat(scale))
|
||||
}
|
||||
|
||||
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0`
|
||||
/// Create a new [`SpatialScale`] with the same value for `x` and `y`, and `0.0`
|
||||
/// for `z`.
|
||||
pub const fn new_2d(scale: f32) -> Self {
|
||||
Self(Vec3::new(scale, scale, 0.0))
|
||||
@ -238,11 +240,11 @@ pub struct DefaultSpatialScale(pub SpatialScale);
|
||||
/// If the handle refers to an unavailable asset (such as if it has not finished loading yet),
|
||||
/// the audio will not begin playing immediately. The audio will play when the asset is ready.
|
||||
///
|
||||
/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be
|
||||
/// When Bevy begins the audio playback, an [`AudioSink`](crate::AudioSink) component will be
|
||||
/// added to the entity. You can use that component to control the audio settings during playback.
|
||||
///
|
||||
/// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the
|
||||
/// `PlaybackSettings` component will *not* affect already-playing audio.
|
||||
/// [`PlaybackSettings`] component will *not* affect already-playing audio.
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component, Clone)]
|
||||
#[require(PlaybackSettings)]
|
||||
|
||||
@ -103,7 +103,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
Entity,
|
||||
&AudioPlayer<Source>,
|
||||
&PlaybackSettings,
|
||||
Option<&GlobalTransform>,
|
||||
&GlobalTransform,
|
||||
),
|
||||
(Without<AudioSink>, Without<SpatialAudioSink>),
|
||||
>,
|
||||
@ -118,7 +118,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
return;
|
||||
};
|
||||
|
||||
for (entity, source_handle, settings, maybe_emitter_transform) in &query_nonplaying {
|
||||
for (entity, source_handle, settings, emitter_transform) in &query_nonplaying {
|
||||
let Some(audio_source) = audio_sources.get(&source_handle.0) else {
|
||||
continue;
|
||||
};
|
||||
@ -136,14 +136,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
|
||||
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
|
||||
|
||||
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
|
||||
(emitter_transform.translation() * scale).into()
|
||||
} else {
|
||||
warn!("Spatial AudioPlayer with no GlobalTransform component. Using zero.");
|
||||
Vec3::ZERO.into()
|
||||
};
|
||||
|
||||
let emitter_translation = (emitter_transform.translation() * scale).into();
|
||||
let sink = match SpatialSink::try_new(
|
||||
stream_handle,
|
||||
emitter_translation,
|
||||
|
||||
@ -39,24 +39,30 @@ pub mod prelude {
|
||||
pub use crate::input::PointerInputPlugin;
|
||||
}
|
||||
|
||||
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
|
||||
/// that you can replace with your own plugin as needed.
|
||||
///
|
||||
/// [`crate::PickingPlugin::is_input_enabled`] can be used to toggle whether
|
||||
/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
|
||||
///
|
||||
/// This plugin contains several settings, and is added to the world as a resource after initialization.
|
||||
/// You can configure pointer input settings at runtime by accessing the resource.
|
||||
#[derive(Copy, Clone, Resource, Debug, Reflect)]
|
||||
#[reflect(Resource, Default, Clone)]
|
||||
pub struct PointerInputPlugin {
|
||||
/// Settings for enabling and disabling updating mouse and touch inputs for picking
|
||||
///
|
||||
/// ## Custom initialization
|
||||
/// ```
|
||||
/// # use bevy_app::App;
|
||||
/// # use bevy_picking::input::{PointerInputSettings,PointerInputPlugin};
|
||||
/// App::new()
|
||||
/// .insert_resource(PointerInputSettings {
|
||||
/// is_touch_enabled: false,
|
||||
/// is_mouse_enabled: true,
|
||||
/// })
|
||||
/// // or DefaultPlugins
|
||||
/// .add_plugins(PointerInputPlugin);
|
||||
/// ```
|
||||
pub struct PointerInputSettings {
|
||||
/// Should touch inputs be updated?
|
||||
pub is_touch_enabled: bool,
|
||||
/// Should mouse inputs be updated?
|
||||
pub is_mouse_enabled: bool,
|
||||
}
|
||||
|
||||
impl PointerInputPlugin {
|
||||
impl PointerInputSettings {
|
||||
fn is_mouse_enabled(state: Res<Self>) -> bool {
|
||||
state.is_mouse_enabled
|
||||
}
|
||||
@ -66,7 +72,7 @@ impl PointerInputPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PointerInputPlugin {
|
||||
impl Default for PointerInputSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_touch_enabled: true,
|
||||
@ -75,25 +81,35 @@ impl Default for PointerInputPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
|
||||
/// that you can replace with your own plugin as needed.
|
||||
///
|
||||
/// Toggling mouse input or touch input can be done at runtime by modifying
|
||||
/// [`PointerInputSettings`] resource.
|
||||
///
|
||||
/// [`PointerInputSettings`] can be initialized with custom values, but will be
|
||||
/// initialized with default values if it is not present at the moment this is
|
||||
/// added to the app.
|
||||
pub struct PointerInputPlugin;
|
||||
|
||||
impl Plugin for PointerInputPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(*self)
|
||||
app.init_resource::<PointerInputSettings>()
|
||||
.register_type::<PointerInputSettings>()
|
||||
.add_systems(Startup, spawn_mouse_pointer)
|
||||
.add_systems(
|
||||
First,
|
||||
(
|
||||
mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
|
||||
touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
|
||||
mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
|
||||
touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
|
||||
)
|
||||
.chain()
|
||||
.in_set(PickingSystems::Input),
|
||||
)
|
||||
.add_systems(
|
||||
Last,
|
||||
deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
|
||||
)
|
||||
.register_type::<Self>()
|
||||
.register_type::<PointerInputPlugin>();
|
||||
deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -293,20 +293,31 @@ pub struct DefaultPickingPlugins;
|
||||
impl PluginGroup for DefaultPickingPlugins {
|
||||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(input::PointerInputPlugin::default())
|
||||
.add(PickingPlugin::default())
|
||||
.add(input::PointerInputPlugin)
|
||||
.add(PickingPlugin)
|
||||
.add(InteractionPlugin)
|
||||
}
|
||||
}
|
||||
|
||||
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
|
||||
/// types used by other picking plugins.
|
||||
///
|
||||
/// This plugin contains several settings, and is added to the world as a resource after initialization. You
|
||||
/// can configure picking settings at runtime through the resource.
|
||||
#[derive(Copy, Clone, Debug, Resource, Reflect)]
|
||||
#[reflect(Resource, Default, Debug, Clone)]
|
||||
pub struct PickingPlugin {
|
||||
/// Controls the behavior of picking
|
||||
///
|
||||
/// ## Custom initialization
|
||||
/// ```
|
||||
/// # use bevy_app::App;
|
||||
/// # use bevy_picking::{PickingSettings, PickingPlugin};
|
||||
/// App::new()
|
||||
/// .insert_resource(PickingSettings {
|
||||
/// is_enabled: true,
|
||||
/// is_input_enabled: false,
|
||||
/// is_hover_enabled: true,
|
||||
/// is_window_picking_enabled: false,
|
||||
/// })
|
||||
/// // or DefaultPlugins
|
||||
/// .add_plugins(PickingPlugin);
|
||||
/// ```
|
||||
pub struct PickingSettings {
|
||||
/// Enables and disables all picking features.
|
||||
pub is_enabled: bool,
|
||||
/// Enables and disables input collection.
|
||||
@ -317,7 +328,7 @@ pub struct PickingPlugin {
|
||||
pub is_window_picking_enabled: bool,
|
||||
}
|
||||
|
||||
impl PickingPlugin {
|
||||
impl PickingSettings {
|
||||
/// Whether or not input collection systems should be running.
|
||||
pub fn input_should_run(state: Res<Self>) -> bool {
|
||||
state.is_input_enabled && state.is_enabled
|
||||
@ -335,7 +346,7 @@ impl PickingPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PickingPlugin {
|
||||
impl Default for PickingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_enabled: true,
|
||||
@ -346,9 +357,18 @@ impl Default for PickingPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
|
||||
/// types used by other picking plugins.
|
||||
///
|
||||
/// Behavior of picking can be controlled by modifying [`PickingSettings`].
|
||||
///
|
||||
/// [`PickingSettings`] will be initialized with default values if it
|
||||
/// is not present at the moment this is added to the app.
|
||||
pub struct PickingPlugin;
|
||||
|
||||
impl Plugin for PickingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(*self)
|
||||
app.init_resource::<PickingSettings>()
|
||||
.init_resource::<pointer::PointerMap>()
|
||||
.init_resource::<backend::ray::RayMap>()
|
||||
.add_event::<pointer::PointerInput>()
|
||||
@ -369,7 +389,7 @@ impl Plugin for PickingPlugin {
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
window::update_window_hits
|
||||
.run_if(Self::window_picking_should_run)
|
||||
.run_if(PickingSettings::window_picking_should_run)
|
||||
.in_set(PickingSystems::Backend),
|
||||
)
|
||||
.configure_sets(
|
||||
@ -382,15 +402,15 @@ impl Plugin for PickingPlugin {
|
||||
.configure_sets(
|
||||
PreUpdate,
|
||||
(
|
||||
PickingSystems::ProcessInput.run_if(Self::input_should_run),
|
||||
PickingSystems::ProcessInput.run_if(PickingSettings::input_should_run),
|
||||
PickingSystems::Backend,
|
||||
PickingSystems::Hover.run_if(Self::hover_should_run),
|
||||
PickingSystems::Hover.run_if(PickingSettings::hover_should_run),
|
||||
PickingSystems::PostHover,
|
||||
PickingSystems::Last,
|
||||
)
|
||||
.chain(),
|
||||
)
|
||||
.register_type::<Self>()
|
||||
.register_type::<PickingSettings>()
|
||||
.register_type::<Pickable>()
|
||||
.register_type::<hover::PickingInteraction>()
|
||||
.register_type::<hover::Hovered>()
|
||||
|
||||
@ -50,6 +50,8 @@ pub struct OnTransition<S: States> {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This schedule is split up into four phases, as described in [`StateTransitionSteps`].
|
||||
///
|
||||
/// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html
|
||||
/// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
//! This example demonstrates the built-in 3d shapes in Bevy.
|
||||
//! The scene includes a patterned texture and a rotation for visualizing the normals and UVs.
|
||||
//! Here we use shape primitives to generate meshes for 3d objects as well as attaching a runtime-generated patterned texture to each 3d object.
|
||||
//!
|
||||
//! "Shape primitives" here are just the mathematical definition of certain shapes, they're not meshes on their own! A sphere with radius `1.0` can be defined with [`Sphere::new(1.0)`][Sphere::new] but all this does is store the radius. So we need to turn these descriptions of shapes into meshes.
|
||||
//!
|
||||
//! While a shape is not a mesh, turning it into one in Bevy is easy. In this example we call [`meshes.add(/* Shape here! */)`][Assets<A>::add] on the shape, which works because the [`Assets<A>::add`] method takes anything that can be turned into the asset type it stores. There's an implementation for [`From`] on shape primitives into [`Mesh`], so that will get called internally by [`Assets<A>::add`].
|
||||
//!
|
||||
//! [`Extrusion`] lets us turn 2D shape primitives into versions of those shapes that have volume by extruding them. A 1x1 square that gets wrapped in this with an extrusion depth of 2 will give us a rectangular prism of size 1x1x2, but here we're just extruding these 2d shapes by depth 1.
|
||||
//!
|
||||
//! The material applied to these shapes is a texture that we generate at run time by looping through a "palette" of RGBA values (stored adjacent to each other in the array) and writing values to positions in another array that represents the buffer for an 8x8 texture. This texture is then registered with the assets system just one time, with that [`Handle<StandardMaterial>`] then applied to all the shapes in this example.
|
||||
//!
|
||||
//! The mesh and material are [`Handle<Mesh>`] and [`Handle<StandardMaterial>`] at the moment, neither of which implement `Component` on their own. Handles are put behind "newtypes" to prevent ambiguity, as some entities might want to have handles to meshes (or images, or materials etc.) for different purposes! All we need to do to make them rendering-relevant components is wrap the mesh handle and the material handle in [`Mesh3d`] and [`MeshMaterial3d`] respectively.
|
||||
//!
|
||||
//! You can toggle wireframes with the space bar except on wasm. Wasm does not support
|
||||
//! `POLYGON_MODE_LINE` on the gpu.
|
||||
|
||||
@ -41,7 +41,7 @@ fn setup(
|
||||
// change the above to `OpaqueRendererMethod::Deferred` or add the `DefaultOpaqueRendererMethod` resource.
|
||||
..Default::default()
|
||||
},
|
||||
extension: MyExtension { quantize_steps: 3 },
|
||||
extension: MyExtension::new(1),
|
||||
})),
|
||||
Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
));
|
||||
@ -69,12 +69,30 @@ fn rotate_things(mut q: Query<&mut Transform, With<Rotate>>, time: Res<Time>) {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone)]
|
||||
#[derive(Asset, AsBindGroup, Reflect, Debug, Clone, Default)]
|
||||
struct MyExtension {
|
||||
// We need to ensure that the bindings of the base material and the extension do not conflict,
|
||||
// so we start from binding slot 100, leaving slots 0-99 for the base material.
|
||||
#[uniform(100)]
|
||||
quantize_steps: u32,
|
||||
// Web examples WebGL2 support: structs must be 16 byte aligned.
|
||||
#[cfg(feature = "webgl2")]
|
||||
#[uniform(100)]
|
||||
_webgl2_padding_8b: u32,
|
||||
#[cfg(feature = "webgl2")]
|
||||
#[uniform(100)]
|
||||
_webgl2_padding_12b: u32,
|
||||
#[cfg(feature = "webgl2")]
|
||||
#[uniform(100)]
|
||||
_webgl2_padding_16b: u32,
|
||||
}
|
||||
impl MyExtension {
|
||||
fn new(quantize_steps: u32) -> Self {
|
||||
Self {
|
||||
quantize_steps,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaterialExtension for MyExtension {
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Extract `PickingPlugin` members into `PickingSettings`
|
||||
pull_requests: [19078]
|
||||
---
|
||||
|
||||
Controlling the behavior of picking should be done through
|
||||
the `PickingSettings` resource instead of `PickingPlugin`.
|
||||
|
||||
To initialize `PickingSettings` with non-default values, simply add
|
||||
the resource to the app using `insert_resource` with the desired value.
|
||||
@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Extract `PointerInputPlugin` members into `PointerInputSettings`
|
||||
pull_requests: [19078]
|
||||
---
|
||||
|
||||
Toggling mouse and touch input update for picking should be done through
|
||||
the `PointerInputSettings` resource instead of `PointerInputPlugin`.
|
||||
|
||||
To initialize `PointerInputSettings` with non-default values, simply add
|
||||
the resource to the app using `insert_resource` with the desired value.
|
||||
233
tests/3d/test_invalid_skinned_mesh.rs
Normal file
233
tests/3d/test_invalid_skinned_mesh.rs
Normal file
@ -0,0 +1,233 @@
|
||||
//! Test that the renderer can handle various invalid skinned meshes
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::motion_blur::MotionBlur,
|
||||
math::ops,
|
||||
prelude::*,
|
||||
render::{
|
||||
camera::ScalingMode,
|
||||
mesh::{
|
||||
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
|
||||
Indices, PrimitiveTopology, VertexAttributeValues,
|
||||
},
|
||||
render_asset::RenderAssetUsages,
|
||||
},
|
||||
};
|
||||
use core::f32::consts::TAU;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.insert_resource(AmbientLight {
|
||||
brightness: 20_000.0,
|
||||
..default()
|
||||
})
|
||||
.add_systems(Startup, (setup_environment, setup_meshes))
|
||||
.add_systems(Update, update_animated_joints)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup_environment(
|
||||
mut commands: Commands,
|
||||
mut mesh_assets: ResMut<Assets<Mesh>>,
|
||||
mut material_assets: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let description = "(left to right)\n\
|
||||
0: Normal skinned mesh.\n\
|
||||
1: Mesh asset is missing skinning attributes.\n\
|
||||
2: One joint entity is missing.\n\
|
||||
3: Mesh entity is missing SkinnedMesh component.";
|
||||
|
||||
commands.spawn((
|
||||
Text::new(description),
|
||||
Node {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.0, 0.0, 1.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
Projection::Orthographic(OrthographicProjection {
|
||||
scaling_mode: ScalingMode::AutoMin {
|
||||
min_width: 19.0,
|
||||
min_height: 6.0,
|
||||
},
|
||||
..OrthographicProjection::default_3d()
|
||||
}),
|
||||
// Add motion blur so we can check if it's working for skinned meshes.
|
||||
// This also exercises the renderer's prepass path.
|
||||
MotionBlur {
|
||||
// Use an unrealistically large shutter angle so that motion blur is clearly visible.
|
||||
shutter_angle: 3.0,
|
||||
samples: 2,
|
||||
},
|
||||
// MSAA and MotionBlur together are not compatible on WebGL.
|
||||
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
Msaa::Off,
|
||||
));
|
||||
|
||||
// Add a directional light to make sure we exercise the renderer's shadow path.
|
||||
commands.spawn((
|
||||
Transform::from_xyz(1.0, 1.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
// Add a plane behind the meshes so we can see the shadows.
|
||||
commands.spawn((
|
||||
Transform::from_xyz(0.0, 0.0, -1.0),
|
||||
Mesh3d(mesh_assets.add(Plane3d::default().mesh().size(100.0, 100.0).normal(Dir3::Z))),
|
||||
MeshMaterial3d(material_assets.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.05, 0.05, 0.15),
|
||||
reflectance: 0.2,
|
||||
..default()
|
||||
})),
|
||||
));
|
||||
}
|
||||
|
||||
fn setup_meshes(
|
||||
mut commands: Commands,
|
||||
mut mesh_assets: ResMut<Assets<Mesh>>,
|
||||
mut material_assets: ResMut<Assets<StandardMaterial>>,
|
||||
mut inverse_bindposes_assets: ResMut<Assets<SkinnedMeshInverseBindposes>>,
|
||||
) {
|
||||
// Create a mesh with two rectangles.
|
||||
let unskinned_mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::default(),
|
||||
)
|
||||
.with_inserted_attribute(
|
||||
Mesh::ATTRIBUTE_POSITION,
|
||||
vec![
|
||||
[-0.3, -0.3, 0.0],
|
||||
[0.3, -0.3, 0.0],
|
||||
[-0.3, 0.3, 0.0],
|
||||
[0.3, 0.3, 0.0],
|
||||
[-0.4, 0.8, 0.0],
|
||||
[0.4, 0.8, 0.0],
|
||||
[-0.4, 1.8, 0.0],
|
||||
[0.4, 1.8, 0.0],
|
||||
],
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 8])
|
||||
.with_inserted_indices(Indices::U16(vec![0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6]));
|
||||
|
||||
// Copy the mesh and add skinning attributes that bind each rectangle to a joint.
|
||||
let skinned_mesh = unskinned_mesh
|
||||
.clone()
|
||||
.with_inserted_attribute(
|
||||
Mesh::ATTRIBUTE_JOINT_INDEX,
|
||||
VertexAttributeValues::Uint16x4(vec![
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
]),
|
||||
)
|
||||
.with_inserted_attribute(
|
||||
Mesh::ATTRIBUTE_JOINT_WEIGHT,
|
||||
vec![[1.00, 0.00, 0.0, 0.0]; 8],
|
||||
);
|
||||
|
||||
let unskinned_mesh_handle = mesh_assets.add(unskinned_mesh);
|
||||
let skinned_mesh_handle = mesh_assets.add(skinned_mesh);
|
||||
|
||||
let inverse_bindposes_handle = inverse_bindposes_assets.add(vec![
|
||||
Mat4::IDENTITY,
|
||||
Mat4::from_translation(Vec3::new(0.0, -1.3, 0.0)),
|
||||
]);
|
||||
|
||||
let mesh_material_handle = material_assets.add(StandardMaterial::default());
|
||||
|
||||
let background_material_handle = material_assets.add(StandardMaterial {
|
||||
base_color: Color::srgb(0.05, 0.15, 0.05),
|
||||
reflectance: 0.2,
|
||||
..default()
|
||||
});
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Variation {
|
||||
Normal,
|
||||
MissingMeshAttributes,
|
||||
MissingJointEntity,
|
||||
MissingSkinnedMeshComponent,
|
||||
}
|
||||
|
||||
for (index, variation) in [
|
||||
Variation::Normal,
|
||||
Variation::MissingMeshAttributes,
|
||||
Variation::MissingJointEntity,
|
||||
Variation::MissingSkinnedMeshComponent,
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
// Skip variations that are currently broken. See https://github.com/bevyengine/bevy/issues/16929,
|
||||
// https://github.com/bevyengine/bevy/pull/18074.
|
||||
if (variation == Variation::MissingSkinnedMeshComponent)
|
||||
|| (variation == Variation::MissingMeshAttributes)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let transform = Transform::from_xyz(((index as f32) - 1.5) * 4.5, 0.0, 0.0);
|
||||
|
||||
let joint_0 = commands.spawn(transform).id();
|
||||
|
||||
let joint_1 = commands
|
||||
.spawn((ChildOf(joint_0), AnimatedJoint, Transform::IDENTITY))
|
||||
.id();
|
||||
|
||||
if variation == Variation::MissingJointEntity {
|
||||
commands.entity(joint_1).despawn();
|
||||
}
|
||||
|
||||
let mesh_handle = match variation {
|
||||
Variation::MissingMeshAttributes => &unskinned_mesh_handle,
|
||||
_ => &skinned_mesh_handle,
|
||||
};
|
||||
|
||||
let mut entity_commands = commands.spawn((
|
||||
Mesh3d(mesh_handle.clone()),
|
||||
MeshMaterial3d(mesh_material_handle.clone()),
|
||||
transform,
|
||||
));
|
||||
|
||||
if variation != Variation::MissingSkinnedMeshComponent {
|
||||
entity_commands.insert(SkinnedMesh {
|
||||
inverse_bindposes: inverse_bindposes_handle.clone(),
|
||||
joints: vec![joint_0, joint_1],
|
||||
});
|
||||
}
|
||||
|
||||
// Add a square behind the mesh to distinguish it from the other meshes.
|
||||
commands.spawn((
|
||||
Transform::from_xyz(transform.translation.x, transform.translation.y, -0.8),
|
||||
Mesh3d(mesh_assets.add(Plane3d::default().mesh().size(4.3, 4.3).normal(Dir3::Z))),
|
||||
MeshMaterial3d(background_material_handle.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct AnimatedJoint;
|
||||
|
||||
fn update_animated_joints(time: Res<Time>, query: Query<&mut Transform, With<AnimatedJoint>>) {
|
||||
for mut transform in query {
|
||||
let angle = TAU * 4.0 * ops::cos((time.elapsed_secs() / 8.0) * TAU);
|
||||
let rotation = Quat::from_rotation_z(angle);
|
||||
|
||||
transform.rotation = rotation;
|
||||
transform.translation = rotation.mul_vec3(Vec3::new(0.0, 1.3, 0.0));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user