# Objective
Expose the `EventKey` for an `Event` through `ReflectEvent`, similar to
how we expose `ComponentId` through
`ReflectComponent::register_component`. This makes it possible to use
dynamic observer APIs like `Observer::with_event_key` with
`ReflectEvent` values.
## Solution
Expose `register_event_key` through `ReflectEvent`.
builds on top of #23967
# Objective
It is common for text input fields to select all text when they are
focused. Bevy text inputs should optionally support this too.
## Solution
Provide a new component `SelectAllOnFocus` that can be added to an
entity with `EditableText` to enable selecting all text on
`FocusGained`.
The select all behavior for `FocusGained` by pointer press needs special
attention: select all is deferred until pointer release, because if the
pointer press is followed by pointer dragging to select only a section
of the text, the select all should not fire.
If the focus gaining pointer press is released on a different entity
(but no text was selected by dragging) the select all still fires. This
was not universally the case in all text input fields from other
applications I tested and could be changed. The last selection in the
video below shows an example of this.
## Testing
Added `SelectAllOnFocus` to the `number_input` of `bevy_feathers`:
https://github.com/user-attachments/assets/e643f396-0b29-4d23-ae7a-c16c942e574f
## Questions
I'm not familiar with ime and I'm unsure how/if this PR interacts with
it.
# Objective
- Demonstrate taking a screenshot through BRP
## Solution
- Have the remote integration example take a screenshot through BRP
before clicking the button
- Depends on #23797#23798#23799#23800
## Testing
- Run the examples, get a screenshot
# Objective
Currently `FocusGained` and `FocusLost` events are send even when the
entity was already in focus. For example when clicking the same text
field twice, the text field will receive another `FocusLost` and
`FocusGained` event on the second click, although it was already in
focus.
This could be intended behavior and it can always be worked around by
deduplicating focus events yourself, so this PR is only a suggestion.
## Testing
I have more PRs ready to go that will add optional select all on focus
gained support , which I used for testing this deduplication.
# Objective
There's a new warning for code that uses literals with `Into<f32>`
because the compiler can no longer infer that the literal should be an
`f32` after `From<f16> for f32` was introduced.
See: https://github.com/rust-lang/rust/issues/154024
## Solution
Explicit `f32` literals.
## Note
This behavior might make `Into<f32>` arguments inconvenient in the
future.
# Objective
- Text input is one of the headline feature in Bevy 0.19.
- Our defaults are currently very ugly, and shown across our user-facing
text examples.
- Good default matter for user perception of quality, and to make quick
prototyping easier.
- Additionally, the layout in the text_input example is erratic in a way
that looks poorly made.
Fixes#23955.
## Solution
1. Clean up the default colors for text input by picking boring,
uncontroversial tailwind colors.
2. Change the css::YELLOW borders in our text input examples to
something neutral that looks nice with our background.
3. Fix up the layout for the `text_input` example so it's both simpler
and looks much better.
## Testing
`cargo run --example text_input`
## Showcase
Before:
<img width="873" height="773" alt="image"
src="https://github.com/user-attachments/assets/bcc70bf6-e6b1-4bf4-b671-7924ac24e984"
/>
After:
<img width="757" height="274" alt="image"
src="https://github.com/user-attachments/assets/e1a3e325-b3a7-4a9b-831e-6886e6a818fc"
/>
# Objective
- Fixes#23061
## Solution
- Following @beicause ’s guidance and previous work in and around the
issue, I just copied all of the relevant Motion Blur WebGL2 changes from
their PR that fixes it (#22554) and tested that it still works.
- WebGPU, as far as I can test on main, already works correctly, so this
is strictly for WebGL2. I will say it *does* look a little different on
WebGL2, but the motion blur is at least working in some capacity. WebGPU
motion blur still looks the same after this change
This PR does *not* include the dof fixes that #22554 also contains.
## Testing
```
cargo build --release --example motion_blur --target wasm32-unknown-unknown
wasm-bindgen --out-name wasm_example \
--out-dir examples/wasm/target \
--target web target/wasm32-unknown-unknown/release/examples/motion_blur.wasm
```
And `basic-http-server examples/wasm` in Google Chrome works.
(Note that #23627 might affect your ability to run this example, so try
to run it on Chrome if it’s available to you at least)
## Showcase
<details>
<summary>Video of Motion Blur Examples</summary>
WebGL2 (Chrome on Mac):
https://github.com/user-attachments/assets/081cd232-4c88-4d57-922c-cf3adbfc5f4f
WebGPU (Chrome on Mac):
https://github.com/user-attachments/assets/61452178-29f9-4d04-ba56-6b4485d23b14
</details>
Co-authored-by: Luo Zhihao <luo_zhihao@outlook.com>
# Objective
Add a platform-agnostic interface for interacting with the clipboard.
## Solution
New crate `bevy_clipboard` with a `ClipboardPlugin` that adds a
`Clipboard` resource. The clipboard is accessed using the methods
`fetch_text`, `fetch_image`, `set_text` and `set_image` on the
`Clipboard` resource. `fetch_text` returns a `ClipboardRead` with a
`poll_result` method that's used to get the actual value once it's
ready.
The `windows` and `unix` implementations both use the `arboard` crate.
On windows the `Clipboard` resource is a unit struct and a new arboard
clipboard instance is created and dropped for each clipboard access. On
unix targets the `Clipboard` resource holds a clipboard instance it
reuses each time. On both targets the `fetch_*` and `set_*` methods work
instantly.
On `wasm32` `Clipboard` is a unit struct. The `fetch_text` and
`set_text` functions spawn async tasks. The task spawned by `fetch_text`
updates the shared arc mutex option once the future is evaluated to get
the clipboard text. There is no image support on `wasm32`.
Everything seems to work but it feels like the design is a bit clumsy
and not very idiomatic. I don't tend to do much asynchronous
programming, maybe a reviewer can suggest an improved construction.
I also added an alternative `fetch_text_async` function for async access
that returns a `Result<String, ClipboardError>` future.
### Notes
* Doesn't support android targets yet.
* The wasm32 implementation doesn't support images. It's much more
complicated and probably best to left to a follow up.
## Testing
The PR includes a basic example `clipboard` that can be used for
testing.
The image display will only work if the image is already in the
clipboard before the example starts.
---------
Co-authored-by: Gilles Henaux <ghx_github_priv@fastmail.com>
Co-authored-by: Andrew Zhurov <zhurov.andrew@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
to reflect current state of implemented features
Fixes#23815
I just moved the bullet point items from the "not implemented" list to
the "current features" list. One item, "Text input labels" I simply
deleted because we didn't know what that meant.
# Objective
- Adds the ability for picking backends to add custom hit data
- Fixes#16186
## Solution
- Adds an `extra` field to the HitData struct which can take any Data
that implements HitDataExtra. This is stored in an Arc which can be
downcast with a helper function in the system that consumes the hit.
- In the original ticket I suggested using a generic, however this
caused extensive code changes and complications down the line with the
HoverMap and OverMap so I decided against it.
## Testing
- I added an example custom_hit_data to test the new feature
- I tested all other picking examples to ensure they still work as
expected
- I tested the examples on Ubuntu
- I additionally tested the custom_hit_data example in wasm
---
## Showcase
<details>
<summary>Click to view showcase</summary>
### Creating custom hits:
```rust
let picks: Vec<(Entity, HitData)> = ray_cast
.cast_ray(ray, &settings)
.iter()
.map(|(entity, hit)| {
let extra = TriangleHitInfo {
triangle_vertices: hit.triangle,
};
let hit_data = HitData::new_with_extra(
ray_id.camera,
hit.distance,
Some(hit.point),
Some(hit.normal),
extra,
);
(*entity, hit_data)
})
.collect();
```
### Reading custom hits:
```rust
for hits in pointer_hits.read() {
for (_, hit) in &hits.picks {
let Some(info) = hit.extra_as::<TriangleHitInfo>() else {
continue;
};
let Some(vertices) = info.triangle_vertices else {
continue;
};
// do something cool with your custom hit data
}
}
```
### An example of what you can do with this
<img width="1291" height="730" alt="image"
src="https://github.com/user-attachments/assets/2d0e8aed-7059-470a-a0f6-1453356cfe6a"
/>
</details>
# Objective
- I strongly agree with the doc style suggested by @mate-h in #23110.
When I originally wrote the vignette docs, I just tried to match the
style of the initial chromatic aberration comments without thinking too
much about it. Now, I’ve updated them to be consistent with the current
lens distortion docs.
- I also made a few other minor changes to make the code cleaner.
## Solution
- Merged Samplers: Replaced `source_sampler` and
`chromatic_aberration_lut_sampler` with a single `common_sampler` (Both
samplers are configured identically).
- CPU-side Clamping: Moved mathematical bounds checks (e.g., clamp, max)
from WGSL shaders into `prepare_post_processing_uniforms`. I only clamp
values that would cause shader errors or result in unexpected behavior.
- Unified Thresholds: Standardized the “skip post-processing” check to >
1e-4 for both `ChromaticAberration` and `Vignette ` extraction, avoiding
unnecessary draw calls for negligible values.
- Cleanup: Removed redundant `DEFAULT_*` constants in favor of inline
literals in Default implementations.
## Testing
- `post-processing` example works properly.
---
# Objective
- Adds a missing conversion to the `bevy::reflect::TypeInfo` zoo.
## Solution
- Copied the simple `impl` and `impl_cast_method!` invocations used in
other types.
## Testing
- Added unit test.
# Objective
- Allow `#[derive(QueryData)]` on structs with no fields.
## Solution
- The derive fails for (non-unit) structs with no fields because the
lifetime parameters of the generated structs are unused. Unit structs
use a type alias instead of creating a new struct, avoiding the unused
lifetime problem. Use type aliases for structs with no fields.
## Testing
- Added regression test
- Ran `cargo run -p ci -- compile`
# Objective
`EntityReference` is a template, but it doesn't follow standard naming
conventions. `EntityTemplate` follows conventions and is much more
self-documenting / instructive.
## Solution
- Rename `EntityReference` to `EntityTemplate`
# Objective / Solution
- Revert most of #23924
Manual FromTemplate derives are unnecessary for anything that doesn't
require custom template logic. Letting a type be a template for itself
is less confusing, has fewer corner cases, and does less codegen.
The rule of thumb to follow is "Use `Default + Clone` for anything that
doesn't need custom template logic". In the case of #23924, the only
type that needs custom template logic is `Scrollbar`, because it has the
`target: Entity` field, which benefits from having an "entity template".
# Objective
This prefix was ruled unhelpful, and we previously removed it from most
of our types.
We missed a few apparently (spotted in #23924).
## Solution
Find and replace + update the migration guide.
This is a naive pass over the `bevy_ui_widgets` crate that adds
`FromTemplate` to everything except the `CoreSliderDragState` and
`CoreScrollbarDragState` components, which are state-management
components.
# Objective
All other crates in the Bevy workspace already depend on `ron = "0.12"`,
but `bevy_transform` was still pinned to the outdated `ron = "0.8"`.
This brings it in line with the rest of the workspace and avoids pulling
in two different versions of `ron`.
## Solution
- Updated `crates/bevy_transform/Cargo.toml` to use `ron = "0.12"`.
All crates that depend on `ron` in this workspace now use the same
version:
| Crate | Version |
|---|---|
| `bevy_animation` | `0.12` |
| `bevy_asset` | `0.12` |
| `bevy_dev_tools` | `0.12` |
| `bevy_reflect` | `0.12` |
| `bevy_transform` | `0.12` ← this PR |
| `bevy_world_serialization` | `0.12` |
## Testing
- `cargo check` passes with the updated dependency.
- No behavioral changes — this is a pure dependency version bump.
- Reviewers can verify by running `cargo test -p bevy_transform`.
---
This may be deduced by observing that the `Relationship` component only
stores one entity ID, but in my opinion, to become aware of such a
fundamental limitation, one should not need to look at the
implementation and do some deducing.
Tested by running `cargo doc --no-deps` from the root of the repository
and opening the relevant webpage.
Co-authored-by: Grégoire Locqueville <git@glocq.com>
## Objective
`AssetPath` deserialization panics if the path is malformed. This seems
a bit rude, and prevents the user from getting deserialization errors
that would help track down the problem.
## Solution
Instead of calling `AssetPath::parse`, call `AssetPath::try_parse` and
handle any errors.
Now, calling `ron::de::from_str` on `"a/b.test#"` returns a useful
error:
```rust
Err(SpannedError {
code: Message(
"Asset label must be at least one character. Either specify the label after the '#' or remove the '#'",
),
span: Span {
start: Position { line: 1, col: 2, },
end: Position { line: 1, col: 12 },
},
})
```
The PR also removes the `visit_string` method of the `AssetPath`
deserializer. `visit_string` is an optimization that avoids copies when
the deserializer can take ownership of the string (see [serde
docs](https://docs.rs/serde/latest/serde/de/trait.Visitor.html#method.visit_string)).
But `AssetPath` didn't take ownership of the string, so there's no
reason to implement it.
## Testing
```sh
cargo test -p bevy_asset
cargo run --example world_serialization
```
# Objective
- Fixes#23843 by preventing these systems from recurring inside Bevy.
- Note this does not fix this for users of Bevy. They would need to
setup their own "strict" checking.
## Solution
- Disable `auto_insert_apply_deferred` in the `ambiguity_detection`
example. Now these stages won't accidentally resolve these ambiguities,
so they will be detected!
- Stop running the app in `ambiguity_detection` and just manually
initialize the schedules.
- We have to do this otherwise the schedules just panic because they
depend on various resource initializations to be executed by commands
(which were previously being applied by the automatically inserted
`apply_deferred`).
### Caveats:
- The ambiguity_detection CI test now won't detect ambiguities in
runtime-added systems.
- This is not a pattern we use today, and I'd encourage us to **delete**
systems instead so that tooling can grab the systems before the app
runs.
- To clarify, this is **not** ambiguity detection in general - just
Bevy's CI test that is affected.
## Testing
- Ran the examples and all the ambiguities have been fixed in previous
PRs.
# Objective
`bevy_transform`'s multi-threading behavior was previously gated on
`feature = "std"`, which incorrectly conflated standard library
availability with multi-threading capability. This means:
- Users who enabled `no_std` but had multi-threading available lost
parallelism unintentionally.
- Users who had `std` but wanted single-threaded execution (e.g. WASM)
still attempted to use the parallel path.
- `bevy_internal`'s `multi_threaded` feature did not propagate down to
`bevy_transform`, so the parallel implementation was not activated when
expected.
- The `bevy_log` dependency was pulled in unconditionally via the `std`
feature, when the only use was optional trace-level warnings.
## Solution
- Added an explicit `multi_threaded` feature to `bevy_transform`, backed
by `bevy_tasks/multi_threaded`, matching the pattern used by `bevy_ecs`
and other crates.
- Replaced all `#[cfg(feature = "std")]` / `#[cfg(not(feature =
"std"))]` guards on parallel/serial code paths with
`#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]` /
`#[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]` —
the same guard pattern already used in `bevy_ecs`.
- Added `bevy_transform/multi_threaded` to `bevy_internal`'s and
`bevy_render`'s `multi_threaded` feature lists so parallel transforms
are activated end-to-end when building Bevy with `multi_threaded`.
- Removed the `bevy_log` dependency from `bevy_transform`. Its sole
usage was a `warn_once!` inside `propagate_transforms_for`. This is
replaced with a direct `tracing::warn!` wrapped in `bevy_utils::once!`,
gated on a new opt-in `trace` feature (`dep:tracing`). This keeps the
default build lighter.
- Inlined the `trace` feature spans in `mark_dirty_trees` that
previously depended on `bevy_log`'s re-exported `tracing`, now using
`tracing` directly under `#[cfg(feature = "trace")]`.
## Testing
- All existing unit tests in `bevy_transform::systems::test` pass
unchanged — no behavioral changes, only cfg guard corrections.
- No performance regressions observed by
[Benchmark](https://github.com/bevyengine/bevy/pull/23906).
---
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
# Objective
If I want to spawn a scene as a child of an entity that already exists,
the current api doesn't offer an straightfoward solution.
The easies I found is by doing something like this:
```rust
const my_entity = ...;
commands.spawn_scene(bsn! {
template(move |_| Ok(ChildOf(my_entity)))
scenes::list_item(text)
});
```
## Solution
- Deriving directly `FromTemplate` for `ChildOf`.
- Adding a new variant to `EntityReference`.
- This allows code like this to work directly
```rust
fn scene(root: Entity) -> impl Scene {
bsn! {
( #Child1 ChildOf(root) ),
}
}
```
## Testing
- Running unit tests locally.
---------
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
# Objective
Add basic emoji support.
## Solution
* In `get_outlined_glyph_texture` just use the given rgba image data if
the image from swash is not an alpha mask.
* Add `is_alpha_mask` bool fields to `GlyphAtlasInfo` and
`GlyphAtlasLocation`.
* In `extract_text_sections` only apply a color tint if the glyph image
is an alpha mask.
## Testing
Should be compatible with all the existing features, I tested
`TextShadow`s and they worked correctly.
The changes here feel a bit half baked, but it's super simple and I've
got leave for somewhere so hammered this out in a rush.
<img width="1827" height="887" alt="emoji"
src="https://github.com/user-attachments/assets/633a3711-8761-438d-9e57-928c725f24f6"
/>
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
Part of #19236
## Solution
A numeric text input widget. This includes:
* Colored stripes ("sigils") for designating X, Y and Z input fields.
* Character filtering
* ValueChange events
Not supported:
* Validation error events
* Range and precision options
Additional changes in this PR:
* Added small labels (related to text inputs only insofar as numeric
inputs have small labels above them in the editor design).
* Refactored labels to be simple spans instead of nested entities - this
required a non-inherited text font theme component, so we renamed the
existing component to have the word "inherited" in the name.
* Consolidated the way focus outlines work for text inputs and other
widgets
* I had to make changes to Slider and other widgets to support the
`is_final` flag in `ValueChange`. This is necessary to allow listeners
the choice between spammy and not-spammy updates when listening to
widget outputs.
## Testing
Manual testing
## Showcase
<img width="353" height="101" alt="numeric_input"
src="https://github.com/user-attachments/assets/36efd243-fa01-484e-bab5-689aa6be4d9e"
/>
# Objective
- It's currently very painful to deal with resources holding handles.
You can't just poll if its assets are loaded.
## Solution
- Add a simple function to just poll every dependency of a
`VisitAssetDependencies`.
- Since we can now derive `VisitAssetDependencies` on stuff, we can now
just pass a resource to these functions and find out if they are loaded.
## Testing
- Added a basic test.
Save some performance on specular indirect.
Todo in the future:
* Investigate if we have to clamp `p` at all
* Investigate subgroup-level RR schemes like nvidia used for further
perf improvements
# Objective
IME support during text input is extremely important for non-Latin
languages: notably the CJK family (Chinese, Japanese, Korean). It would
be very nice to support it in our initial release!
Fixes#23795.
## Solution
1. Peek at the `input/text_input` example that @mockersf made and I
forgot about.
2. Steal its strategy and wire up the IME events to Bevy's proper text
input widgets.
3. Delete the now-obsolete example.
4. Add underlines to tentative characters (exposed by parley :D) so then
users can distinguish what they're typing from what they have typed.
5. Rename `editable_text` to the nicer `text_input`.
6. Create a dedicated example for `ime_support`, which uses system
fonts.
7. Add an `error_once!` to fix a footgun I tripped on when using system
fonts...
Per @mockersf's complaints, I've opted not to ship any new fonts in this
PR: the system fonts actually worked great!
## Testing
I've setup IME support on Windows, and added a small font with Japanese
support to the `editable_text` example.
It works quite well! We can even submit the values!
Known issues:
- when entering Japanese characters, the console is spammed with "ICU4X
data error: No segmentation model found for language: ja". I have no
idea where this is coming from: I think a system dependency is causing
this, and it's bubbled up by `parley`? IDK. Doesn't happen with Latin
characters, and it renders fine so...
-~~tab selecting the IME suggestion causes the selected text entry box
to change. I wasn't sure how we want to fix that, so Ieft it for now.~~
EDIT: fixed!
## Showcase
<img width="470" height="233" alt="image"
src="https://github.com/user-attachments/assets/8f8721b3-6746-46d5-8a20-40de634f52e5"
/>
---------
Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
The intention has long been to render shadow maps for point and spot
lights only once, regardless of the number of views. This is reflected
in the fact that `RetainedViewEntity::auxiliary_entity` is
`Entity::PLACEHOLDER` for them. Unfortunately, this is currently
inconsistently implemented, and a separate `ExtractedView` is presently
spawned and rendered to for every point and spot light shadow map. The
behavior of these views is inconsistent because they violate the
invariant that there must only be one render-world view per
`RetainedViewEntity`.
This patch changes Bevy's behavior to spawn only one `ExtractedView` for
point and spot lights. This required some significant rearchitecting of
the render schedule because the render schedule is currently driven off
cameras. Driving the rendering off cameras is incorrect for point and
spot light shadow maps, which aren't associated with any camera.
This PR fixes the regression on the `render_layers` test in `testbed_3d`
in PR #23481, in that it renders the way it rendered before that PR.
Note, however, that the rendering isn't what may have been intended: the
shadows don't match the visible objects. That's because the shadows come
from point lights, which aren't associated with cameras, and therefore
shadows are rendered using the default set of `RenderLayers`. A future
patch may want to add flags to cameras that specify that they should
have their own point light and spot light shadow maps that inherit the
render layer (and HLOD) behavior of their associated cameras. As this
patch is fairly large, though, and because my immediate goal is to fix
the regression in #23481, I think those flags are best implemented in a
follow-up.
<img width="2564" height="1500" alt="Screenshot 2026-04-07 215221"
src="https://github.com/user-attachments/assets/2b37f35c-84e7-4473-9962-968a8cbd7619"
/>
# Objective
- solid_color returned envmap lights that looked black
## Solution
- set intensity to 1.0
- also make sure to use proper color space (linear)
## Testing
- tested in reflection_probe example
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective
`bsn!` currently embeds expressions directly into the patch closure,
which requires moving all of the "expression items" into the closure.
This thrusts constraints like `Send + Sync + 'static` on anything used
in the expression:
```rust
/// Workaround 1 (used in Feathers)
fn label(text: impl Into<String>) -> impl Scene {
let text = Text::new(text.into());
bsn! {
template_value(text)
}
}
/// Workaround 2
fn label(text: impl Into<String> + Send + Sync + 'static) -> impl Scene {
bsn! {
Text(text)
}
}
```
## Solution
Hoist expression above the closures, evaluate them immediately, and move
the _results_ into the closure. This significantly reduces the burden on
developers defining scenes.
This now works! No workaround necessary!
```rust
fn label(text: impl Into<String>) -> impl Scene {
bsn! {
Text(text)
}
}
```
Borrowing like this is also allowed because of implicit `into()`, which
converts the `&str` into `String`, which is then captured in the patch
closure:
```rust
fn label(text: &str) -> impl Scene {
bsn! {
Text(text)
}
}
```
Likewise, patterns like this now work:
```rust
fn cube(meshes: &mut Assets<Mesh>) -> impl Scene {
bsn! {
Mesh3d({meshes.add(Cuboid::default())})
}
}
```
# Objective
support generics in `bsn!` scene inheritance syntax (i.e.
`:some_scene::<T>`)
## Solution
Parse `Path` instead of an `Ident` for scene inheritance
## Testing
added `inheritance_with_generics` test to `bevy_scene/src/lib.rs`
# Objective
`Scene` and `SceneList` currently require "repeatability". This puts
some burden on developers setting "field patches" in BSN, as the "right
hand side" values must either be Copy or have a manual Clone. This
results in scenes like:
```rust
let mesh = meshes.add(Circle::new(4.0));
let material = materials.add(Color::WHITE);
bsn! {
Mesh3d({mesh.clone()})
MeshMaterial3d::<StandardMaterial>({material.clone()})
}
```
In practice, thanks to inheritance caching, repeatability is no longer
required.
## Solution
- Make `Scene` and `SceneList` consume `self`.
- Add `SceneBox` and `SceneListBox` subtraits to enable `Box<dyn Scene>`
to impl `Scene` and consume itself.
- Switch to `FnOnce` for patches.
This enables the following code to work:
```rust
let mesh = meshes.add(Circle::new(4.0));
let material = materials.add(Color::WHITE);
bsn! {
Mesh3d(mesh)
MeshMaterial3d::<StandardMaterial>(material)
}
```
---------
Co-authored-by: François Mockers <francois.mockers@vleue.com>