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::ShaderBuffer},
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<ShaderBuffer>,
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 = buffers.add(ShaderBuffer::from([material.0.background_color]));
97
98 let mesh = meshes.add(Rectangle::new(1.0, 1.0));
99 let material = Material {
100 background_colors,
101 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
102 n_background_colors: 1,
103 outline_color: material.0.outline_color,
104 outline_width: material.0.outline_width,
105 };
106 let material = materials.add(material);
107
108 commands.insert_resource(Representation::<T> {
109 mesh,
110 material,
111 marker: PhantomData,
112 });
113 }
114
115 pub fn sync<I>(
117 commands: &mut Commands,
118 ellipse_representation: Res<Representation<T>>,
119 query: Query<(Entity, &mut Transform), With<Self>>,
120 ellipses: I,
121 ) where
122 I: IntoIterator<Item = (Vec3, f32, f32, f32)>,
123 {
124 for (tag, item) in &mut query.into_iter().zip_longest(ellipses).enumerate() {
125 match item {
126 Both((_, mut transform), (position, theta, a, b)) => {
127 transform.translation = position;
128 transform.rotation = Quat::from_rotation_z(theta);
129 transform.scale = Vec3::new(a, b, 1.0);
130 }
131 Left((entity, _)) => commands.entity(entity).despawn(),
132 Right((position, theta, a, b)) => {
133 commands.spawn((
134 MeshTag(tag as u32),
135 Mesh2d(ellipse_representation.mesh.clone()),
136 MeshMaterial2d(ellipse_representation.material.clone()),
137 Transform::from_translation(position)
138 .with_scale(Vec3::new(a, b, 1.0))
139 .with_rotation(Quat::from_rotation_z(theta)),
140 Self {
141 marker: PhantomData,
142 },
143 ));
144 }
145 }
146 }
147 }
148}
149
150pub struct MaterialParameters {
152 pub background_color: LinearRgba,
154
155 pub outline_color: LinearRgba,
157
158 pub outline_width: f32,
160}
161
162impl Default for MaterialParameters {
163 fn default() -> Self {
164 Self {
165 background_color: PRIMARY_COLOR.into(),
166 outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
167 outline_width: 0.05,
168 }
169 }
170}
171
172#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
193pub struct Material {
194 #[uniform(0)]
196 outline_color: LinearRgba,
197
198 #[uniform(0)]
200 outline_width: f32,
201
202 #[uniform(0)]
204 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
205 n_background_colors: u32,
206
207 #[uniform(1)]
209 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
210 background_colors: [LinearRgba; 1024],
211
212 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
214 #[storage(1, read_only)]
215 background_colors: Handle<ShaderBuffer>,
216}
217
218impl Material {
219 pub fn set_background_colors(
229 &mut self,
230 #[allow(
231 unused_variables,
232 unused_mut,
233 reason = "Not used in all build configurations."
234 )]
235 mut buffers: ResMut<Assets<ShaderBuffer>>,
236 colors: &[LinearRgba],
237 ) {
238 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
239 {
240 assert!(
241 colors.len() <= 1024,
242 "webgl2 builds support up to 1024 colors, got {}",
243 colors.len()
244 );
245 self.background_colors[..colors.len()].copy_from_slice(colors);
246 self.n_background_colors = colors.len() as u32;
247 }
248
249 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
250 {
251 let mut color_buffer = buffers
252 .get_mut(&self.background_colors)
253 .expect("Ellipse::setup should have added the storage buffer");
254
255 color_buffer.set_data(colors);
256 }
257 }
258}
259
260impl Material2d for Material {
261 fn fragment_shader() -> ShaderRef {
262 SHADER_ASSET_PATH.into()
263 }
264
265 fn vertex_shader() -> ShaderRef {
266 SHADER_ASSET_PATH.into()
267 }
268
269 fn alpha_mode(&self) -> AlphaMode2d {
270 AlphaMode2d::Mask(0.5)
271 }
272
273 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
274 fn specialize(
275 descriptor: &mut RenderPipelineDescriptor,
276 _layout: &MeshVertexBufferLayoutRef,
277 _key: Material2dKey<Self>,
278 ) -> Result<(), SpecializedMeshPipelineError> {
279 descriptor.vertex.shader_defs.push("WEBGL2".into());
280
281 Ok(())
282 }
283}