hoomd_bevy/representation/
hyperbolic_disk.rs1use crate::PRIMARY_COLOR;
7use bevy::{
8 asset::embedded_asset,
9 image::TRANSPARENT_IMAGE_HANDLE,
10 prelude::*,
11 reflect::TypePath,
12 render::render_resource::AsBindGroup,
13 shader::ShaderRef,
14 sprite_render::{AlphaMode2d, Material2d, Material2dPlugin},
15};
16use hoomd_manifold::{Hyperbolic, Minkowski};
17use itertools::{
18 EitherOrBoth::{Both, Left, Right},
19 Itertools,
20};
21use std::marker::PhantomData;
22
23const SHADER_ASSET_PATH: &str = "embedded://hoomd_bevy/representation/hyperbolic_disk.wgsl";
25
26#[derive(Component)]
44pub struct HyperbolicDisk<T> {
45 marker: PhantomData<T>,
47}
48
49#[derive(Resource)]
51pub struct HyperbolicDiskAssets<T> {
52 mesh: Handle<Mesh>,
54 material: Handle<HyperbolicDiskMaterial>,
56 marker: PhantomData<T>,
58}
59
60pub(crate) fn build(app: &mut App) {
62 app.add_plugins(Material2dPlugin::<HyperbolicDiskMaterial>::default());
63 embedded_asset!(app, "hyperbolic_disk.wgsl");
64}
65
66impl<T: Send + Sync + 'static> HyperbolicDisk<T> {
67 pub fn setup(
69 material: In<HyperbolicDiskMaterial>,
70 mut commands: Commands,
71 mut meshes: ResMut<Assets<Mesh>>,
72 mut materials: ResMut<Assets<HyperbolicDiskMaterial>>,
73 ) {
74 let mesh = meshes.add(Rectangle::new(1.0, 1.0));
75 let material = materials.add(material.0);
76 commands.insert_resource(HyperbolicDiskAssets::<T> {
77 mesh,
78 material,
79 marker: PhantomData,
80 });
81 }
82
83 pub fn sync<I>(
85 commands: &mut Commands,
86 disk_assets: Res<HyperbolicDiskAssets<T>>,
87 query: Query<(Entity, &mut Transform), With<Self>>,
88 disks: I,
89 ) where
90 I: IntoIterator<Item = (Minkowski<3>, f64)>,
91 {
92 for item in &mut query.into_iter().zip_longest(disks) {
93 match item {
94 Both((_, mut transform), (position, diameter)) => {
95 let (poincare_position, max_projected_radius) = poincare(&position, diameter);
96 let rad_arg = (diameter / 2.0).sinh() / (1.0 + (diameter / 2.0).cosh());
97 let poincare_radius = (0.5)
98 * (1.0 + 2.0 * rad_arg.powi(2) / (1.0 - (rad_arg.powi(2)))).acosh() as f32;
99 transform.translation = Vec3::from_array(poincare_position);
100 transform.scale = Vec3::from_array([
102 max_projected_radius,
103 max_projected_radius,
104 poincare_radius,
105 ]);
106 }
107 Left((entity, _)) => commands.entity(entity).despawn(),
108 Right((position, diameter)) => {
109 let (poincare_position, max_projected_radius) = poincare(&position, diameter);
110 let rad_arg = (diameter / 2.0).sinh() / (1.0 + (diameter / 2.0).cosh());
111 let poincare_radius = (0.5)
112 * (1.0 + 2.0 * rad_arg.powi(2) / (1.0 - (rad_arg.powi(2)))).acosh() as f32;
113 commands.spawn((
114 Mesh2d(disk_assets.mesh.clone()),
115 MeshMaterial2d(disk_assets.material.clone()),
116 Transform::from_translation(Vec3::from_array(poincare_position))
117 .with_scale(Vec3::from_array([
118 max_projected_radius,
119 max_projected_radius,
120 poincare_radius,
121 ])),
122 Self {
123 marker: PhantomData,
124 },
125 ));
126 }
127 }
128 }
129 }
130}
131
132fn poincare(point: &Minkowski<3>, diameter: f64) -> ([f32; 3], f32) {
134 let pt = Hyperbolic::from_minkowski_coordinates(*point);
135 let proj = pt.to_poincare();
136 let v = diameter / 2.0;
137 let eta = (point.coordinates[2]).acosh();
138 let edge_proj = ((eta - v).sinh()) / (1.0 + (eta - v).cosh());
139 let rad_proj = ((eta).sinh()) / (1.0 + (eta).cosh()) - edge_proj;
140 ([proj[0] as f32, proj[1] as f32, 0.0_f32], rad_proj as f32)
141}
142
143#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
151pub struct HyperbolicDiskMaterial {
152 #[uniform(0)]
154 pub background_color: LinearRgba,
155
156 #[uniform(1)]
158 pub outline_color: LinearRgba,
159
160 #[uniform(2)]
162 pub outline_width: f32,
163
164 #[uniform(3)]
166 pub texture_scale: f32,
167
168 #[texture(4)]
170 #[sampler(5)]
171 pub texture: Handle<Image>,
172}
173
174impl Default for HyperbolicDiskMaterial {
175 fn default() -> Self {
176 Self {
177 background_color: PRIMARY_COLOR.into(),
178 outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
179 outline_width: 0.005,
180 texture: TRANSPARENT_IMAGE_HANDLE,
181 texture_scale: 1.2,
182 }
183 }
184}
185
186impl HyperbolicDiskMaterial {
187 #[must_use]
189 pub fn ghost() -> Self {
190 Self {
191 background_color: Color::linear_rgb(0.5, 0.5, 0.5).into(),
192 outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
193 outline_width: 0.005,
194 texture: TRANSPARENT_IMAGE_HANDLE,
195 texture_scale: 1.2,
196 }
197 }
198}
199
200impl Material2d for HyperbolicDiskMaterial {
201 fn fragment_shader() -> ShaderRef {
202 SHADER_ASSET_PATH.into()
203 }
204
205 fn vertex_shader() -> ShaderRef {
206 SHADER_ASSET_PATH.into()
207 }
208
209 fn alpha_mode(&self) -> AlphaMode2d {
210 AlphaMode2d::Mask(0.5)
211 }
212}