hoomd_bevy/representation/
disk.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/disk.wgsl";
39
40#[derive(Component)]
51pub struct Disk<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, "disk.wgsl");
79}
80
81impl<T: Send + Sync + 'static> Disk<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 asset_server: Res<AssetServer>,
92 ) {
93 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
94 let background_colors = [material.0.background_color; 1024];
95
96 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
97 let background_colors =
98 buffers.add(ShaderStorageBuffer::from([material.0.background_color]));
99
100 let mesh = meshes.add(Rectangle::new(1.0, 1.0));
101 let material = Material {
102 background_colors,
103 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
104 n_background_colors: 1,
105 outline_color: material.0.outline_color,
106 outline_width: material.0.outline_width,
107 texture_scale: material.0.texture_scale,
108 texture: material.0.texture_asset.map(|t| asset_server.load(t)),
109 };
110 let material = materials.add(material);
111
112 commands.insert_resource(Representation::<T> {
113 mesh,
114 material,
115 marker: PhantomData,
116 });
117 }
118
119 pub fn sync<I>(
121 commands: &mut Commands,
122 disk_representation: Res<Representation<T>>,
123 query: Query<(Entity, &mut Transform), With<Self>>,
124 disks: I,
125 ) where
126 I: IntoIterator<Item = (Vec3, f32)>,
127 {
128 for (tag, item) in &mut query.into_iter().zip_longest(disks).enumerate() {
129 match item {
130 Both((_, mut transform), (position, diameter)) => {
131 transform.translation = position;
132 transform.scale = Vec3::splat(diameter);
133 }
134 Left((entity, _)) => commands.entity(entity).despawn(),
135 Right((position, diameter)) => {
136 commands.spawn((
137 MeshTag(tag as u32),
138 Mesh2d(disk_representation.mesh.clone()),
139 MeshMaterial2d(disk_representation.material.clone()),
140 Transform::from_translation(position).with_scale(Vec3::splat(diameter)),
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 pub texture_scale: f32,
164
165 pub texture_asset: Option<String>,
167}
168
169impl Default for MaterialParameters {
170 fn default() -> Self {
171 Self {
172 background_color: PRIMARY_COLOR.into(),
173 outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
174 outline_width: 0.05,
175 texture_asset: None,
176 texture_scale: 1.2,
177 }
178 }
179}
180
181#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
202pub struct Material {
203 #[uniform(0)]
205 outline_color: LinearRgba,
206
207 #[uniform(0)]
209 outline_width: f32,
210
211 #[uniform(0)]
213 texture_scale: f32,
214
215 #[uniform(0)]
217 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
218 n_background_colors: u32,
219
220 #[texture(1)]
222 #[sampler(2)]
223 texture: Option<Handle<Image>>,
224
225 #[uniform(3)]
227 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
228 background_colors: [LinearRgba; 1024],
229
230 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
232 #[storage(3, read_only)]
233 background_colors: Handle<ShaderStorageBuffer>,
234}
235
236impl Material {
237 pub fn set_background_colors(
247 &mut self,
248 #[allow(
249 unused_variables,
250 unused_mut,
251 reason = "Not used in all build configurations."
252 )]
253 mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
254 colors: &[LinearRgba],
255 ) {
256 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
257 {
258 assert!(
259 colors.len() <= 1024,
260 "webgl2 builds support up to 1024 colors, got {}",
261 colors.len()
262 );
263 self.background_colors[..colors.len()].copy_from_slice(colors);
264 self.n_background_colors = colors.len() as u32;
265 }
266
267 #[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
268 {
269 let color_buffer = buffers
270 .get_mut(&self.background_colors)
271 .expect("Disk::setup should have added the storage buffer");
272
273 color_buffer.set_data(colors);
274 }
275 }
276}
277
278impl Material2d for Material {
279 fn fragment_shader() -> ShaderRef {
280 SHADER_ASSET_PATH.into()
281 }
282
283 fn vertex_shader() -> ShaderRef {
284 SHADER_ASSET_PATH.into()
285 }
286
287 fn alpha_mode(&self) -> AlphaMode2d {
288 AlphaMode2d::Mask(0.5)
289 }
290
291 #[cfg(all(target_arch = "wasm32", not(feature = "webgpu")))]
292 fn specialize(
293 descriptor: &mut RenderPipelineDescriptor,
294 _layout: &MeshVertexBufferLayoutRef,
295 _key: Material2dKey<Self>,
296 ) -> Result<(), SpecializedMeshPipelineError> {
297 descriptor.vertex.shader_defs.push("WEBGL2".into());
298
299 Ok(())
300 }
301}