polyscope_structures/point_cloud/
mod.rs1mod quantities;
4
5use glam::{Mat4, Vec3, Vec4};
6use polyscope_core::pick::PickResult;
7use polyscope_core::quantity::Quantity;
8use polyscope_core::structure::{HasQuantities, RenderContext, Structure};
9use polyscope_render::{ColorMapRegistry, PickUniforms, PointCloudRenderData, PointUniforms};
10use wgpu::util::DeviceExt;
11
12pub use quantities::*;
13
14pub struct PointCloud {
16 name: String,
17 points: Vec<Vec3>,
18 enabled: bool,
19 transform: Mat4,
20 quantities: Vec<Box<dyn Quantity>>,
21 render_data: Option<PointCloudRenderData>,
22 material: String,
23 point_radius: f32,
24 base_color: Vec4,
25 pick_uniform_buffer: Option<wgpu::Buffer>,
27 pick_bind_group: Option<wgpu::BindGroup>,
28 global_start: u32,
29}
30
31impl PointCloud {
32 pub fn new(name: impl Into<String>, points: Vec<Vec3>) -> Self {
34 Self {
35 name: name.into(),
36 points,
37 enabled: true,
38 transform: Mat4::IDENTITY,
39 quantities: Vec::new(),
40 render_data: None,
41 material: "clay".to_string(),
42 point_radius: 0.01,
43 base_color: Vec4::new(0.2, 0.5, 0.8, 1.0),
44 pick_uniform_buffer: None,
45 pick_bind_group: None,
46 global_start: 0,
47 }
48 }
49
50 #[must_use]
52 pub fn num_points(&self) -> usize {
53 self.points.len()
54 }
55
56 #[must_use]
58 pub fn points(&self) -> &[Vec3] {
59 &self.points
60 }
61
62 pub fn update_points(&mut self, points: Vec<Vec3>) {
64 self.points = points;
65 self.refresh();
66 }
67
68 pub fn add_scalar_quantity(&mut self, name: impl Into<String>, values: Vec<f32>) -> &mut Self {
70 let quantity = PointCloudScalarQuantity::new(name, self.name.clone(), values);
71 self.add_quantity(Box::new(quantity));
72 self
73 }
74
75 pub fn add_vector_quantity(
77 &mut self,
78 name: impl Into<String>,
79 vectors: Vec<Vec3>,
80 ) -> &mut Self {
81 let quantity = PointCloudVectorQuantity::new(name, self.name.clone(), vectors);
82 self.add_quantity(Box::new(quantity));
83 self
84 }
85
86 pub fn add_color_quantity(&mut self, name: impl Into<String>, colors: Vec<Vec3>) -> &mut Self {
88 let quantity = PointCloudColorQuantity::new(name, self.name.clone(), colors);
89 self.add_quantity(Box::new(quantity));
90 self
91 }
92
93 pub fn init_gpu_resources(
95 &mut self,
96 device: &wgpu::Device,
97 bind_group_layout: &wgpu::BindGroupLayout,
98 camera_buffer: &wgpu::Buffer,
99 ) {
100 self.render_data = Some(PointCloudRenderData::new(
101 device,
102 bind_group_layout,
103 camera_buffer,
104 &self.points,
105 None, ));
107 }
108
109 #[must_use]
111 pub fn render_data(&self) -> Option<&PointCloudRenderData> {
112 self.render_data.as_ref()
113 }
114
115 pub fn init_pick_resources(
117 &mut self,
118 device: &wgpu::Device,
119 pick_bind_group_layout: &wgpu::BindGroupLayout,
120 camera_buffer: &wgpu::Buffer,
121 global_start: u32,
122 ) {
123 self.global_start = global_start;
124
125 let pick_uniforms = PickUniforms {
127 global_start,
128 point_radius: self.point_radius,
129 _padding: [0.0; 2],
130 };
131 let pick_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
132 label: Some("point cloud pick uniforms"),
133 contents: bytemuck::cast_slice(&[pick_uniforms]),
134 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
135 });
136
137 if let Some(render_data) = &self.render_data {
139 let pick_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
140 label: Some("point cloud pick bind group"),
141 layout: pick_bind_group_layout,
142 entries: &[
143 wgpu::BindGroupEntry {
144 binding: 0,
145 resource: camera_buffer.as_entire_binding(),
146 },
147 wgpu::BindGroupEntry {
148 binding: 1,
149 resource: pick_uniform_buffer.as_entire_binding(),
150 },
151 wgpu::BindGroupEntry {
152 binding: 2,
153 resource: render_data.position_buffer.as_entire_binding(),
154 },
155 ],
156 });
157 self.pick_bind_group = Some(pick_bind_group);
158 }
159
160 self.pick_uniform_buffer = Some(pick_uniform_buffer);
161 }
162
163 #[must_use]
165 pub fn pick_bind_group(&self) -> Option<&wgpu::BindGroup> {
166 self.pick_bind_group.as_ref()
167 }
168
169 pub fn update_pick_uniforms(&self, queue: &wgpu::Queue) {
171 if let Some(buffer) = &self.pick_uniform_buffer {
172 let pick_uniforms = PickUniforms {
173 global_start: self.global_start,
174 point_radius: self.point_radius,
175 _padding: [0.0; 2],
176 };
177 queue.write_buffer(buffer, 0, bytemuck::cast_slice(&[pick_uniforms]));
178 }
179 }
180
181 pub fn set_point_radius(&mut self, radius: f32) {
183 self.point_radius = radius;
184 }
185
186 #[must_use]
188 pub fn point_radius(&self) -> f32 {
189 self.point_radius
190 }
191
192 pub fn set_base_color(&mut self, color: Vec3) {
194 self.base_color = color.extend(1.0);
195 }
196
197 #[must_use]
199 pub fn base_color(&self) -> Vec4 {
200 self.base_color
201 }
202
203 #[must_use]
205 pub fn active_color_quantity(&self) -> Option<&PointCloudColorQuantity> {
206 use polyscope_core::quantity::QuantityKind;
207
208 for q in &self.quantities {
209 if q.is_enabled() && q.kind() == QuantityKind::Color {
210 if let Some(cq) = q.as_any().downcast_ref::<PointCloudColorQuantity>() {
211 return Some(cq);
212 }
213 }
214 }
215 None
216 }
217
218 #[must_use]
220 pub fn active_scalar_quantity(&self) -> Option<&PointCloudScalarQuantity> {
221 use polyscope_core::quantity::QuantityKind;
222
223 for q in &self.quantities {
224 if q.is_enabled() && q.kind() == QuantityKind::Scalar {
225 if let Some(sq) = q.as_any().downcast_ref::<PointCloudScalarQuantity>() {
226 return Some(sq);
227 }
228 }
229 }
230 None
231 }
232
233 #[must_use]
235 pub fn active_vector_quantity(&self) -> Option<&PointCloudVectorQuantity> {
236 use polyscope_core::quantity::QuantityKind;
237
238 for q in &self.quantities {
239 if q.is_enabled() && q.kind() == QuantityKind::Vector {
240 if let Some(vq) = q.as_any().downcast_ref::<PointCloudVectorQuantity>() {
241 return Some(vq);
242 }
243 }
244 }
245 None
246 }
247
248 pub fn active_vector_quantity_mut(&mut self) -> Option<&mut PointCloudVectorQuantity> {
250 use polyscope_core::quantity::QuantityKind;
251
252 for q in &mut self.quantities {
253 if q.is_enabled() && q.kind() == QuantityKind::Vector {
254 if let Some(vq) = q.as_any_mut().downcast_mut::<PointCloudVectorQuantity>() {
255 return Some(vq);
256 }
257 }
258 }
259 None
260 }
261
262 pub fn build_egui_ui(&mut self, ui: &mut egui::Ui, available_materials: &[&str]) {
264 let mut color = [self.base_color.x, self.base_color.y, self.base_color.z];
265 let mut radius = self.point_radius;
266
267 if polyscope_ui::build_point_cloud_ui(
268 ui,
269 self.points.len(),
270 &mut radius,
271 &mut color,
272 &mut self.material,
273 available_materials,
274 ) {
275 self.base_color = Vec4::new(color[0], color[1], color[2], self.base_color.w);
276 self.point_radius = radius;
277 }
278
279 if !self.quantities.is_empty() {
281 ui.separator();
282 ui.label("Quantities:");
283 for quantity in &mut self.quantities {
284 if let Some(sq) = quantity
286 .as_any_mut()
287 .downcast_mut::<PointCloudScalarQuantity>()
288 {
289 sq.build_egui_ui(ui);
290 } else if let Some(cq) = quantity
291 .as_any_mut()
292 .downcast_mut::<PointCloudColorQuantity>()
293 {
294 cq.build_egui_ui(ui);
295 } else if let Some(vq) = quantity
296 .as_any_mut()
297 .downcast_mut::<PointCloudVectorQuantity>()
298 {
299 vq.build_egui_ui(ui);
300 }
301 }
302 }
303 }
304
305 pub fn update_gpu_buffers(&self, queue: &wgpu::Queue, color_maps: &ColorMapRegistry) {
307 let Some(render_data) = &self.render_data else {
308 return;
309 };
310
311 let model_matrix = self.transform.to_cols_array_2d();
313
314 let mut uniforms = PointUniforms {
315 model_matrix,
316 point_radius: self.point_radius,
317 use_per_point_color: 0,
318 _padding: [0.0; 2],
319 base_color: self.base_color.to_array(),
320 };
321
322 if let Some(color_q) = self.active_color_quantity() {
324 uniforms.use_per_point_color = 1;
325 color_q.apply_to_render_data(queue, render_data);
326 } else if let Some(scalar_q) = self.active_scalar_quantity() {
327 if let Some(colormap) = color_maps.get(scalar_q.colormap_name()) {
328 uniforms.use_per_point_color = 1;
329 let colors = scalar_q.compute_colors(colormap);
330 render_data.update_colors(queue, &colors);
331 }
332 }
333
334 render_data.update_uniforms(queue, &uniforms);
335 }
336}
337
338impl Structure for PointCloud {
339 fn as_any(&self) -> &dyn std::any::Any {
340 self
341 }
342
343 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
344 self
345 }
346
347 fn name(&self) -> &str {
348 &self.name
349 }
350
351 fn type_name(&self) -> &'static str {
352 "PointCloud"
353 }
354
355 fn bounding_box(&self) -> Option<(Vec3, Vec3)> {
356 if self.points.is_empty() {
357 return None;
358 }
359
360 let mut min = Vec3::splat(f32::MAX);
361 let mut max = Vec3::splat(f32::MIN);
362
363 for &p in &self.points {
364 min = min.min(p);
365 max = max.max(p);
366 }
367
368 let transform = self.transform;
370 let corners = [
371 transform.transform_point3(Vec3::new(min.x, min.y, min.z)),
372 transform.transform_point3(Vec3::new(max.x, min.y, min.z)),
373 transform.transform_point3(Vec3::new(min.x, max.y, min.z)),
374 transform.transform_point3(Vec3::new(max.x, max.y, min.z)),
375 transform.transform_point3(Vec3::new(min.x, min.y, max.z)),
376 transform.transform_point3(Vec3::new(max.x, min.y, max.z)),
377 transform.transform_point3(Vec3::new(min.x, max.y, max.z)),
378 transform.transform_point3(Vec3::new(max.x, max.y, max.z)),
379 ];
380
381 let mut world_min = Vec3::splat(f32::MAX);
382 let mut world_max = Vec3::splat(f32::MIN);
383 for corner in corners {
384 world_min = world_min.min(corner);
385 world_max = world_max.max(corner);
386 }
387
388 Some((world_min, world_max))
389 }
390
391 fn length_scale(&self) -> f32 {
392 self.bounding_box()
393 .map_or(1.0, |(min, max)| (max - min).length())
394 }
395
396 fn transform(&self) -> Mat4 {
397 self.transform
398 }
399
400 fn set_transform(&mut self, transform: Mat4) {
401 self.transform = transform;
402 }
403
404 fn is_enabled(&self) -> bool {
405 self.enabled
406 }
407
408 fn set_enabled(&mut self, enabled: bool) {
409 self.enabled = enabled;
410 }
411
412 fn material(&self) -> &str {
413 &self.material
414 }
415
416 fn set_material(&mut self, material: &str) {
417 self.material = material.to_string();
418 }
419
420 fn draw(&self, _ctx: &mut dyn RenderContext) {
421 }
423
424 fn draw_pick(&self, _ctx: &mut dyn RenderContext) {
425 }
427
428 fn build_ui(&mut self, _ui: &dyn std::any::Any) {
429 }
431
432 fn build_pick_ui(&self, _ui: &dyn std::any::Any, _pick: &PickResult) {
433 }
435
436 fn clear_gpu_resources(&mut self) {
437 self.render_data = None;
438 self.pick_uniform_buffer = None;
439 self.pick_bind_group = None;
440 for quantity in &mut self.quantities {
441 quantity.clear_gpu_resources();
442 }
443 }
444
445 fn refresh(&mut self) {
446 for quantity in &mut self.quantities {
447 quantity.refresh();
448 }
449 }
450}
451
452impl HasQuantities for PointCloud {
453 fn add_quantity(&mut self, quantity: Box<dyn Quantity>) {
454 self.quantities.push(quantity);
455 }
456
457 fn get_quantity(&self, name: &str) -> Option<&dyn Quantity> {
458 self.quantities
459 .iter()
460 .find(|q| q.name() == name)
461 .map(std::convert::AsRef::as_ref)
462 }
463
464 fn get_quantity_mut(&mut self, name: &str) -> Option<&mut Box<dyn Quantity>> {
465 self.quantities.iter_mut().find(|q| q.name() == name)
466 }
467
468 fn remove_quantity(&mut self, name: &str) -> Option<Box<dyn Quantity>> {
469 let idx = self.quantities.iter().position(|q| q.name() == name)?;
470 Some(self.quantities.remove(idx))
471 }
472
473 fn quantities(&self) -> &[Box<dyn Quantity>] {
474 &self.quantities
475 }
476}