hoomd_bevy/representation/
ellipse.rs1#![allow(
5 clippy::missing_docs_in_private_items,
6 reason = "clippy reports a false positive errors in this file"
7)]
8
9use bevy::{
15 asset::embedded_asset,
16 mesh::MeshTag,
17 prelude::*,
18 reflect::TypePath,
19 render::{render_resource::AsBindGroup, storage::ShaderStorageBuffer},
20 shader::ShaderRef,
21 sprite_render::{AlphaMode2d, Material2d, Material2dPlugin},
22};
23#[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
24use bevy::{
25 mesh::MeshVertexBufferLayoutRef,
26 render::render_resource::{RenderPipelineDescriptor, SpecializedMeshPipelineError},
27 sprite_render::Material2dKey,
28};
29use itertools::{
30 EitherOrBoth::{Both, Left, Right},
31 Itertools,
32};
33use std::marker::PhantomData;
34
35use crate::PRIMARY_COLOR;
36
37const SHADER_ASSET_PATH: &str = "embedded://hoomd_bevy/representation/ellipse.wgsl";
39
40#[derive(Component)]
51pub struct Ellipse<T> {
52 marker: PhantomData<T>,
54}
55
56#[derive(Resource)]
58pub struct Representation<T> {
59 mesh: Handle<Mesh>,
61 material: Handle<Material>,
63 marker: PhantomData<T>,
65}
66
67impl<T> Representation<T> {
68 #[must_use]
70 pub fn material(&self) -> &Handle<Material> {
71 &self.material
72 }
73}
74
75pub(crate) fn build(app: &mut App) {
77 app.add_plugins(Material2dPlugin::<Material>::default());
78 embedded_asset!(app, "ellipse.wgsl");
79}
80
81impl<T: Send + Sync + 'static> Ellipse<T> {
82 pub fn setup(
84 material: In<MaterialParameters>,
85 mut commands: Commands,
86 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))] mut buffers: ResMut<
87 Assets<ShaderStorageBuffer>,
88 >,
89 mut meshes: ResMut<Assets<Mesh>>,
90 mut materials: ResMut<Assets<Material>>,
91 ) {
92 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
93 let background_colors = [material.0.background_color; 1024];
94
95 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
96 let background_colors =
97 buffers.add(ShaderStorageBuffer::from([material.0.background_color]));
98
99 let mesh = meshes.add(Rectangle::new(1.0, 1.0));
100 let material = Material {
101 background_colors,
102 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
103 n_background_colors: 1,
104 outline_color: material.0.outline_color,
105 outline_width: material.0.outline_width,
106 };
107 let material = materials.add(material);
108
109 commands.insert_resource(Representation::<T> {
110 mesh,
111 material,
112 marker: PhantomData,
113 });
114 }
115
116 pub fn sync<I>(
118 commands: &mut Commands,
119 ellipse_representation: Res<Representation<T>>,
120 query: Query<(Entity, &mut Transform), With<Self>>,
121 ellipses: I,
122 ) where
123 I: IntoIterator<Item = (Vec3, f32, f32, f32)>,
124 {
125 for (tag, item) in &mut query.into_iter().zip_longest(ellipses).enumerate() {
126 match item {
127 Both((_, mut transform), (position, theta, a, b)) => {
128 transform.translation = position;
129 transform.rotation = Quat::from_rotation_z(theta);
130 transform.scale = Vec3::new(a, b, 1.0);
131 }
132 Left((entity, _)) => commands.entity(entity).despawn(),
133 Right((position, theta, a, b)) => {
134 commands.spawn((
135 MeshTag(tag as u32),
136 Mesh2d(ellipse_representation.mesh.clone()),
137 MeshMaterial2d(ellipse_representation.material.clone()),
138 Transform::from_translation(position)
139 .with_scale(Vec3::new(a, b, 1.0))
140 .with_rotation(Quat::from_rotation_z(theta)),
141 Self {
142 marker: PhantomData,
143 },
144 ));
145 }
146 }
147 }
148 }
149}
150
151pub struct MaterialParameters {
153 pub background_color: LinearRgba,
155
156 pub outline_color: LinearRgba,
158
159 pub outline_width: f32,
161}
162
163impl Default for MaterialParameters {
164 fn default() -> Self {
165 Self {
166 background_color: PRIMARY_COLOR.into(),
167 outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
168 outline_width: 0.05,
169 }
170 }
171}
172
173#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
194pub struct Material {
195 #[uniform(0)]
197 outline_color: LinearRgba,
198
199 #[uniform(0)]
201 outline_width: f32,
202
203 #[uniform(0)]
205 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
206 n_background_colors: u32,
207
208 #[uniform(1)]
210 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
211 background_colors: [LinearRgba; 1024],
212
213 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
215 #[storage(1, read_only)]
216 background_colors: Handle<ShaderStorageBuffer>,
217}
218
219impl Material {
220 pub fn set_background_colors(
230 &mut self,
231 #[allow(
232 unused_variables,
233 unused_mut,
234 reason = "Not used in all build configurations."
235 )]
236 mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
237 colors: &[LinearRgba],
238 ) {
239 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
240 {
241 assert!(
242 colors.len() <= 1024,
243 "webgl2 builds support up to 1024 colors, got {}",
244 colors.len()
245 );
246 self.background_colors[..colors.len()].copy_from_slice(colors);
247 self.n_background_colors = colors.len() as u32;
248 }
249
250 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
251 {
252 let color_buffer = buffers
253 .get_mut(&self.background_colors)
254 .expect("Ellipse::setup should have added the storage buffer");
255
256 color_buffer.set_data(colors);
257 }
258 }
259}
260
261impl Material2d for Material {
262 fn fragment_shader() -> ShaderRef {
263 SHADER_ASSET_PATH.into()
264 }
265
266 fn vertex_shader() -> ShaderRef {
267 SHADER_ASSET_PATH.into()
268 }
269
270 fn alpha_mode(&self) -> AlphaMode2d {
271 AlphaMode2d::Mask(0.5)
272 }
273
274 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
275 fn specialize(
276 descriptor: &mut RenderPipelineDescriptor,
277 _layout: &MeshVertexBufferLayoutRef,
278 _key: Material2dKey<Self>,
279 ) -> Result<(), SpecializedMeshPipelineError> {
280 descriptor.vertex.shader_defs.push("WEBGL2".into());
281
282 Ok(())
283 }
284}