hoomd_bevy/representation/
hyperbolic_polygon.rs

1// Copyright (c) 2024-2026 The Regents of the University of Michigan.
2// Part of hoomd-rs, released under the BSD 3-Clause License.
3
4//! Implement Hyperbolic disks in Bevy.
5
6use crate::PRIMARY_COLOR;
7use bevy::{
8    asset::embedded_asset,
9    prelude::*,
10    reflect::TypePath,
11    render::render_resource::AsBindGroup,
12    shader::ShaderRef,
13    sprite_render::{AlphaMode2d, Material2d, Material2dPlugin},
14};
15use hoomd_manifold::{Hyperbolic, Minkowski};
16use itertools::{
17    EitherOrBoth::{Both, Left, Right},
18    Itertools,
19};
20use std::marker::PhantomData;
21
22/// Location of the shader implementation
23const SHADER_ASSET_PATH: &str = "embedded://hoomd_bevy/representation/hyperbolic_polygon.wgsl";
24
25/// Represent an entity with a 2D regular polygon in hyperbolic space.
26#[derive(Component)]
27pub struct HyperbolicPolygon<T> {
28    /// Mark the type of the disk.
29    marker: PhantomData<T>,
30}
31
32/// Assets that represent a Disk in the scene.
33#[derive(Resource)]
34pub struct HyperbolicPolygonAssets<T> {
35    /// The polygon mesh.
36    mesh: Handle<Mesh>,
37    /// The polygon material.
38    material: Handle<HyperbolicPolygonMaterial>,
39    /// Mark the type of the polygon assets.
40    marker: PhantomData<T>,
41}
42
43/// Initialize needed plugins and add assets for this representation.
44pub(crate) fn build(app: &mut App) {
45    app.add_plugins(Material2dPlugin::<HyperbolicPolygonMaterial>::default());
46    embedded_asset!(app, "hyperbolic_polygon.wgsl");
47}
48
49impl<T: Send + Sync + 'static> HyperbolicPolygon<T> {
50    /// Create assets to render polygons.
51    pub fn setup(
52        material: In<HyperbolicPolygonMaterialParameters>,
53        mut commands: Commands,
54        //#[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))] mut buffers: ResMut<
55        //    Assets<ShaderStorageBuffer>,
56        //>,
57        mut meshes: ResMut<Assets<Mesh>>,
58        mut materials: ResMut<Assets<HyperbolicPolygonMaterial>>,
59        asset_server: Res<AssetServer>,
60    ) {
61        //#[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
62        let n_sides = material.0.n_sides;
63
64        let mesh = meshes.add(Rectangle::new(1.0, 1.0));
65        let material = HyperbolicPolygonMaterial {
66            n_sides,
67            background_color: material.0.background_color,
68            outline_color: material.0.outline_color,
69            outline_width: material.0.outline_width,
70            texture_scale: material.0.texture_scale,
71            texture: material.0.texture_asset.map(|t| asset_server.load(t)),
72        };
73        let material = materials.add(material);
74        commands.insert_resource(HyperbolicPolygonAssets::<T> {
75            mesh,
76            material,
77            marker: PhantomData,
78        });
79    }
80
81    /// Copy the current positions of simulation particles to bevy entities.
82    pub fn sync<I>(
83        commands: &mut Commands,
84        disk_assets: Res<HyperbolicPolygonAssets<T>>,
85        query: Query<(Entity, &mut Transform), With<Self>>,
86        disks: I,
87    ) where
88        I: IntoIterator<Item = (Minkowski<3>, f64, f32)>,
89    {
90        for item in &mut query.into_iter().zip_longest(disks) {
91            match item {
92                Both((_, mut transform), (position, radius, theta)) => {
93                    let (poincare_position, max_projected_radius) =
94                        poincare(&position, radius, theta);
95                    // let rad_arg = RHO * (radius / RHO).sinh() / (1.0 + (radius / RHO).cosh());
96                    // let poincare_radius = (0.5)
97                    //    * (1.0 + 2.0 * rad_arg.powi(2) / (1.0 - (rad_arg.powi(2)))).acosh() as f32;
98                    transform.translation = Vec3::from_array(poincare_position);
99                    transform.scale = Vec3::from_array([
100                        max_projected_radius * 2.0,
101                        max_projected_radius * 2.0,
102                        radius as f32, // radius in units of rapidity,
103                    ]);
104                    // transform.rotation = Quat::from_rotation_z(theta);
105                }
106                Left((entity, _)) => commands.entity(entity).despawn(),
107                Right((position, radius, theta)) => {
108                    let (poincare_position, max_projected_radius) =
109                        poincare(&position, radius, theta);
110                    // let rad_arg = RHO * (radius / RHO).sinh() / (1.0 + (radius / RHO).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 * 2.0,
119                                max_projected_radius * 2.0,
120                                radius as f32, // radius in units of rapidity,
121                            ])),
122                        Self {
123                            marker: PhantomData,
124                        },
125                    ));
126                }
127            }
128        }
129    }
130}
131
132/// Project coordinates to Poincare disk
133fn poincare(point: &Minkowski<3>, radius: f64, angle: f32) -> ([f32; 3], f32) {
134    let pt = Hyperbolic::from_minkowski_coordinates(*point);
135    let proj = pt.to_poincare();
136    let v = radius;
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, angle], rad_proj as f32)
141}
142
143/// Control how hyperbolic polygons are rendered.
144#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
145pub struct HyperbolicPolygonMaterial {
146    /// Color applied to the interior of the polygon.
147    #[uniform(0)]
148    pub background_color: LinearRgba,
149
150    /// Color applied to the outline.
151    #[uniform(1)]
152    pub outline_color: LinearRgba,
153
154    /// Width of the outline.
155    #[uniform(2)]
156    pub outline_width: f32,
157
158    /// Factor to scale the texture by.
159    #[uniform(3)]
160    pub texture_scale: f32,
161
162    /// Texture to apply. Blended with `color`.
163    #[texture(4)]
164    #[sampler(5)]
165    pub texture: Option<Handle<Image>>,
166    /// Color applied to the interior of the disk (indexed by disk % array size).
167    //#[cfg(not(all(target_arch = "wasm32", not(feature = "webgpu"))))]
168    #[uniform(6)]
169    pub n_sides: f32,
170}
171
172/// Material Parameters for hyperbolic polygon.
173pub struct HyperbolicPolygonMaterialParameters {
174    /// Number of sides of the polygon.
175    pub n_sides: f32,
176    /// Color applied to the interior of the polygon.
177    pub background_color: LinearRgba,
178    /// Color applied to the outline.
179    pub outline_color: LinearRgba,
180    /// Width of the outline.
181    pub outline_width: f32,
182    /// Factor to scale the texture by,.
183    pub texture_scale: f32,
184    /// Name of the texture asset.
185    pub texture_asset: Option<String>,
186}
187
188impl Default for HyperbolicPolygonMaterialParameters {
189    fn default() -> Self {
190        Self {
191            n_sides: 0.0_f32,
192            background_color: PRIMARY_COLOR.into(),
193            outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
194            outline_width: 0.005,
195            texture_asset: None,
196            texture_scale: 1000.0,
197        }
198    }
199}
200
201impl HyperbolicPolygonMaterialParameters {
202    /// color for ghost particles
203    #[must_use]
204    pub fn ghost() -> Self {
205        Self {
206            background_color: Color::linear_rgb(0.5, 0.5, 0.5).into(),
207            outline_color: Color::linear_rgb(0.0, 0.0, 0.0).into(),
208            outline_width: 0.005,
209            texture_asset: None,
210            texture_scale: 1.2,
211            n_sides: 4.0_f32,
212        }
213    }
214}
215
216impl Material2d for HyperbolicPolygonMaterial {
217    fn fragment_shader() -> ShaderRef {
218        SHADER_ASSET_PATH.into()
219    }
220
221    fn vertex_shader() -> ShaderRef {
222        SHADER_ASSET_PATH.into()
223    }
224
225    fn alpha_mode(&self) -> AlphaMode2d {
226        AlphaMode2d::Mask(0.5)
227    }
228}