diff --git a/tsunami/src/lib.rs b/tsunami/src/lib.rs index 47b8c7f..6845872 100644 --- a/tsunami/src/lib.rs +++ b/tsunami/src/lib.rs @@ -236,8 +236,20 @@ pub async fn run() { ), ); - let mut point_mesh = CpuMesh::sphere(10); - point_mesh.transform(&Mat4::from_scale(0.002)).unwrap(); + let point_size = 0.002; + let point_mesh = { + let mut point_mesh = CpuMesh::sphere(10); + point_mesh.transform(&Mat4::from_scale(point_size)).unwrap(); + point_mesh + }; + let line_mesh = { + let mut line_mesh = CpuMesh::cylinder(10); + line_mesh + .transform(&Mat4::from_nonuniform_scale(1., point_size, point_size)) + .unwrap(); + line_mesh + }; + let mut quadrature_point_cloud_model = Gm { geometry: InstancedMesh::new( &context, @@ -250,7 +262,7 @@ pub async fn run() { ), material: ColorMaterial::default(), }; - let mut tracer_point_cloud_model = Gm { + let mut tracers_model = Gm { geometry: InstancedMesh::new( &context, &PointCloud { @@ -390,7 +402,7 @@ pub async fn run() { None, ); - if let param::ShowPoints::Quadrature = params.visualization.show_points { + if let param::ShowFeatures::Quadrature = params.visualization.show_features { quadrature_point_cloud_model.geometry = InstancedMesh::new( &context, &PointCloud { @@ -417,51 +429,13 @@ pub async fn run() { quadrature_point_cloud_model.material.render_states.cull = Cull::FrontAndBack; } - if let param::ShowPoints::Tracer = params.visualization.show_points { - tracer_point_cloud_model.geometry = InstancedMesh::new( - &context, - &PointCloud { - positions: Positions::F32( - rendering_data - .tracer_points - .points - .into_iter() - .map(|point| { - rotation.transform_vector(Vector3 { - x: point.x as f32, - y: point.y as f32, - z: point.z as f32, - }) - }) - .collect(), - ), - colors: Some( - rendering_data - .tracer_points - .positions - .into_iter() - .map(|position| { - Srgba::new( - u8::MAX, - u8::MAX, - u8::MAX, - // Add transparency based on position in tracer's trail. - (u8::MAX as Float - * (position as Float - / geom::TRACER_TAIL_LENGTH as Float) - .powf(0.25)) - as u8, - ) - }) - .collect(), - ), - } - .into(), - &point_mesh, - ); - tracer_point_cloud_model.material.render_states.cull = Cull::None; + if let param::ShowFeatures::Tracers = params.visualization.show_features { + tracers_model.geometry = rendering_data + .tracer_points + .make_mesh(&context, &line_mesh, point_size, &rotation); + tracers_model.material.render_states.cull = Cull::None; } else { - tracer_point_cloud_model.material.render_states.cull = Cull::FrontAndBack; + tracers_model.material.render_states.cull = Cull::FrontAndBack; } } @@ -642,7 +616,7 @@ pub async fn run() { std::iter::empty() .chain(&mesh_model) .chain(&quadrature_point_cloud_model) - .chain(&tracer_point_cloud_model) + .chain(&tracers_model) .chain(&tsunami_hint_circle_object) .chain(&moon_sprite_object) .chain(&skybox), diff --git a/tsunami/src/param.rs b/tsunami/src/param.rs index 4b0c440..53007ba 100644 --- a/tsunami/src/param.rs +++ b/tsunami/src/param.rs @@ -163,7 +163,7 @@ impl Default for PhysicsParameters { pub struct VisualizationParameters { pub height_exaggeration_factor: Float, pub velocity_exaggeration_factor: Float, - pub show_points: ShowPoints, + pub show_features: ShowFeatures, pub show_rotation: ShowRotation, } @@ -182,7 +182,7 @@ impl VisualizationParameters { .text("velocity exaggeration") .suffix("×"), ); - self.show_points.generate_ui(ui); + self.show_features.generate_ui(ui); self.show_rotation.generate_ui(ui); ui.add_space(10.); } @@ -193,7 +193,7 @@ impl Default for VisualizationParameters { Self { height_exaggeration_factor: 500., velocity_exaggeration_factor: 1.5e3, - show_points: ShowPoints::Tracer, + show_features: ShowFeatures::Tracers, show_rotation: ShowRotation::None, } } @@ -311,13 +311,13 @@ impl Default for Parameters { } #[derive(Copy, Clone, Debug, PartialEq, strum::EnumIter)] -pub enum ShowPoints { +pub enum ShowFeatures { Quadrature, - Tracer, + Tracers, None, } -impl ShowPoints { +impl ShowFeatures { fn generate_ui(&mut self, ui: &mut egui::Ui) { ui.horizontal(|ui| { use strum::IntoEnumIterator; @@ -325,17 +325,17 @@ impl ShowPoints { for value in Self::iter() { ui.radio_value(self, value, value.to_string()); } - ui.label("show points"); + ui.label("show"); }); } } -impl std::fmt::Display for ShowPoints { +impl std::fmt::Display for ShowFeatures { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { - ShowPoints::Quadrature => "quadrature", - ShowPoints::Tracer => "tracer", - ShowPoints::None => "none", + ShowFeatures::Quadrature => "quadrature points", + ShowFeatures::Tracers => "path lines", + ShowFeatures::None => "none", }) } } diff --git a/tsunami/src/render.rs b/tsunami/src/render.rs index 794bdd9..863e874 100644 --- a/tsunami/src/render.rs +++ b/tsunami/src/render.rs @@ -1,11 +1,11 @@ use flow::{float_consts, Float}; use ndarray as nd; -use three_d::{CpuMesh, Indices, Positions, Vector3}; +use three_d::*; #[derive(Clone)] pub struct RenderingData { pub quadrature_points: Vec>, - pub tracer_points: TracerPoints, + pub tracer_points: Tracers, pub mesh: CpuMesh, } @@ -131,7 +131,7 @@ impl SphereRenderable { RenderingData { quadrature_points, - tracer_points: self.make_tracer_points(height_exaggeration_factor), + tracer_points: self.make_tracers(height_exaggeration_factor), mesh, } } @@ -147,31 +147,34 @@ impl SphereRenderable { points } - fn make_tracer_points(&self, height_exaggeration_factor: Float) -> TracerPoints { - let (points, positions) = self + fn make_tracers(&self, height_exaggeration_factor: Float) -> Tracers { + if self.tracer_points_history_theta_phi.is_empty() { + return Tracers::new(Vec::new()); + } + + let mut path_lines: Vec<_> = + (0..self.tracer_points_history_theta_phi.first().unwrap().len()) + .map(|_| Vec::with_capacity(self.tracer_points_history_theta_phi.len())) + .collect(); + for (tracer_points_theta_phi, tracer_heights) in self .tracer_points_history_theta_phi .iter() - .enumerate() - .zip(&self.tracer_heights_history) - .map(|((position, tracer_points_theta_phi), tracer_heights)| { - tracer_points_theta_phi - .axis_iter(nd::Axis(1)) - .zip(tracer_heights.iter()) - .map(move |(point_theta_phi, &height)| { - ( - self.make_point( - point_theta_phi[[flow::bases::ylm::Component::Theta as usize]], - point_theta_phi[[flow::bases::ylm::Component::Phi as usize]], - height, - height_exaggeration_factor, - ), - position, - ) - }) - }) - .flatten() - .unzip(); - TracerPoints { points, positions } + .zip(self.tracer_heights_history.iter()) + { + for (j, (tracer_point_theta_phi, &tracer_height)) in tracer_points_theta_phi + .axis_iter(nd::Axis(1)) + .zip(tracer_heights.iter()) + .enumerate() + { + path_lines[j].push(self.make_point( + tracer_point_theta_phi[[flow::bases::ylm::Component::Theta as usize]], + tracer_point_theta_phi[[flow::bases::ylm::Component::Phi as usize]], + tracer_height, + height_exaggeration_factor, + )); + } + } + Tracers::new(path_lines) } fn make_point( @@ -279,7 +282,7 @@ impl TorusRenderable { RenderingData { quadrature_points, - tracer_points: self.make_tracer_points(height_exaggeration_factor), + tracer_points: self.make_tracers(height_exaggeration_factor), mesh, } } @@ -295,31 +298,34 @@ impl TorusRenderable { points } - fn make_tracer_points(&self, height_exaggeration_factor: Float) -> TracerPoints { - let (points, positions) = self + fn make_tracers(&self, height_exaggeration_factor: Float) -> Tracers { + if self.tracer_points_history_theta_phi.is_empty() { + return Tracers::new(Vec::new()); + } + + let mut path_lines: Vec<_> = + (0..self.tracer_points_history_theta_phi.first().unwrap().len()) + .map(|_| Vec::with_capacity(self.tracer_points_history_theta_phi.len())) + .collect(); + for (tracer_points_theta_phi, tracer_heights) in self .tracer_points_history_theta_phi .iter() - .enumerate() - .zip(&self.tracer_heights_history) - .map(|((position, tracer_points_theta_phi), tracer_heights)| { - tracer_points_theta_phi - .axis_iter(nd::Axis(1)) - .zip(tracer_heights.iter()) - .map(move |(point_theta_phi, &height)| { - ( - self.make_point( - point_theta_phi[[0]], - point_theta_phi[[1]], - height, - height_exaggeration_factor, - ), - position, - ) - }) - }) - .flatten() - .unzip(); - TracerPoints { points, positions } + .zip(self.tracer_heights_history.iter()) + { + for (j, (tracer_point_theta_phi, &tracer_height)) in tracer_points_theta_phi + .axis_iter(nd::Axis(1)) + .zip(tracer_heights.iter()) + .enumerate() + { + path_lines[j].push(self.make_point( + tracer_point_theta_phi[[flow::bases::ylm::Component::Theta as usize]], + tracer_point_theta_phi[[flow::bases::ylm::Component::Phi as usize]], + tracer_height, + height_exaggeration_factor, + )); + } + } + Tracers::new(path_lines) } fn make_point( @@ -340,7 +346,70 @@ impl TorusRenderable { } #[derive(Clone)] -pub struct TracerPoints { - pub points: Vec>, - pub positions: Vec, +pub struct Tracers { + path_lines: Vec>>, +} + +impl Tracers { + pub fn new(path_lines: Vec>>) -> Self { + Self { path_lines } + } + + pub fn make_mesh( + &self, + context: &three_d::Context, + line_mesh: &three_d::CpuMesh, + line_width: f32, + rotation: &three_d::Mat4, + ) -> three_d::InstancedMesh { + let (transformations, colors) = self + .path_lines + .iter() + .map(|path_line| { + path_line + .iter() + .zip(path_line.iter().skip(1)) + .enumerate() + .map(|(position, (start, end))| { + let start = Vec3::new(start.x as f32, start.y as f32, start.z as f32); + let end = Vec3::new(end.x as f32, end.y as f32, end.z as f32); + let displacement = end - start; + let length = displacement.magnitude(); + let axis = Vec3::unit_x().cross(displacement).normalize(); + let angle = displacement.angle(Vec3::unit_x()); + + let direction_rotation = Mat4::from_axis_angle(axis, angle); + let length_scale = Mat4::from_nonuniform_scale(length, 1., 1.); + + // Add transparency based on position in tracer's trail and length of line + // segment. + let opacity = (4. * length + / (crate::geom::TRACER_TAIL_LENGTH as f32 * line_width)) + .min(1.) + * (position as f32 / crate::geom::TRACER_TAIL_LENGTH as f32); + let color = + Srgba::new(u8::MAX, u8::MAX, u8::MAX, (u8::MAX as f32 * opacity) as u8); + + ( + rotation + * Mat4::from_translation(start) + * direction_rotation + * length_scale, + color, + ) + }) + }) + .flatten() + .unzip(); + + InstancedMesh::new( + context, + &Instances { + transformations, + colors: Some(colors), + texture_transformations: None, + }, + line_mesh, + ) + } }