1use crate::core::{
4 vertex_utils, BoundingBox, DrawCall, GpuVertexBuffer, Material, PipelineType, RenderData,
5 Vertex,
6};
7use glam::{Vec3, Vec4};
8
9#[derive(Debug, Clone)]
11pub struct Scatter3Plot {
12 pub points: Vec<Vec3>,
14 pub colors: Vec<Vec4>,
16 pub point_size: f32,
18 pub point_sizes: Option<Vec<f32>>,
20 pub label: Option<String>,
22 pub visible: bool,
24 vertices: Option<Vec<Vertex>>,
25 bounds: Option<BoundingBox>,
26 gpu_vertices: Option<GpuVertexBuffer>,
27 gpu_point_count: Option<usize>,
28}
29
30impl Scatter3Plot {
31 pub fn new(points: Vec<Vec3>) -> Result<Self, String> {
33 let default_color = Vec4::new(0.1, 0.7, 0.3, 1.0);
34 let colors = vec![default_color; points.len()];
35 Ok(Self {
36 points,
37 colors,
38 point_size: 8.0,
39 point_sizes: None,
40 label: None,
41 visible: true,
42 vertices: None,
43 bounds: None,
44 gpu_vertices: None,
45 gpu_point_count: None,
46 })
47 }
48
49 pub fn from_gpu_buffer(
51 buffer: GpuVertexBuffer,
52 point_count: usize,
53 color: Vec4,
54 point_size: f32,
55 bounds: BoundingBox,
56 ) -> Self {
57 Self {
58 points: Vec::new(),
59 colors: vec![color],
60 point_size,
61 point_sizes: None,
62 label: None,
63 visible: true,
64 vertices: None,
65 bounds: Some(bounds),
66 gpu_vertices: Some(buffer),
67 gpu_point_count: Some(point_count),
68 }
69 }
70
71 pub fn with_color(mut self, color: Vec4) -> Self {
73 self.colors = vec![color; self.points.len()];
74 self.vertices = None;
75 self.gpu_vertices = None;
76 self.gpu_point_count = None;
77 self
78 }
79
80 pub fn with_colors(mut self, colors: Vec<Vec4>) -> Result<Self, String> {
82 if colors.len() != self.points.len() {
83 return Err(format!(
84 "Point cloud color count ({}) must match point count ({})",
85 colors.len(),
86 self.points.len()
87 ));
88 }
89 self.colors = colors;
90 self.vertices = None;
91 self.gpu_vertices = None;
92 self.gpu_point_count = None;
93 Ok(self)
94 }
95
96 pub fn with_label<S: Into<String>>(mut self, label: S) -> Self {
98 self.label = Some(label.into());
99 self
100 }
101
102 pub fn with_point_size(mut self, size: f32) -> Self {
104 self.point_size = size.max(1.0);
105 self.point_sizes = None;
106 self.gpu_vertices = None;
107 self.gpu_point_count = None;
108 self
109 }
110
111 pub fn set_visible(&mut self, visible: bool) {
113 self.visible = visible;
114 }
115
116 pub fn with_gpu_vertices(mut self, buffer: GpuVertexBuffer, point_count: usize) -> Self {
119 self.gpu_vertices = Some(buffer);
120 self.gpu_point_count = Some(point_count);
121 self.vertices = None;
122 self
123 }
124
125 pub fn set_point_sizes(&mut self, sizes: Vec<f32>) {
127 self.point_sizes = Some(sizes);
128 self.vertices = None;
129 self.gpu_vertices = None;
130 self.gpu_point_count = None;
131 }
132
133 fn ensure_vertices(&mut self) {
134 if self.vertices.is_none() {
135 let mut verts = vertex_utils::create_point_cloud(&self.points, &self.colors);
136 if let Some(sizes) = self.point_sizes.as_ref() {
137 for (idx, vertex) in verts.iter_mut().enumerate() {
138 let size = sizes.get(idx).copied().unwrap_or(self.point_size);
139 vertex.normal[2] = size;
140 }
141 } else {
142 for vertex in &mut verts {
143 vertex.normal[2] = self.point_size;
144 }
145 }
146 self.vertices = Some(verts);
147 }
148 }
149
150 fn ensure_bounds(&mut self) {
151 if self.bounds.is_none() {
152 self.bounds = Some(BoundingBox::from_points(&self.points));
153 }
154 }
155
156 pub fn estimated_memory_usage(&self) -> usize {
158 let gpu_bytes = self
159 .gpu_point_count
160 .map(|count| count * std::mem::size_of::<Vertex>())
161 .unwrap_or(0);
162 self.points.len() * std::mem::size_of::<Vec3>()
163 + self.colors.len() * std::mem::size_of::<Vec4>()
164 + self
165 .point_sizes
166 .as_ref()
167 .map(|sizes| sizes.len() * std::mem::size_of::<f32>())
168 .unwrap_or(0)
169 + gpu_bytes
170 }
171
172 pub fn render_data(&mut self) -> RenderData {
174 let bounds = self.bounds();
175 let vertex_count = self.gpu_point_count.unwrap_or_else(|| {
176 self.ensure_vertices();
177 self.vertices
178 .as_ref()
179 .map(|v| v.len())
180 .unwrap_or(self.points.len())
181 });
182
183 let vertices = if self.gpu_vertices.is_some() {
184 Vec::new()
185 } else {
186 self.ensure_vertices();
187 self.vertices.clone().unwrap_or_default()
188 };
189
190 RenderData {
191 pipeline_type: PipelineType::Scatter3,
192 vertices,
193 indices: None,
194 gpu_vertices: self.gpu_vertices.clone(),
195 bounds: Some(bounds),
196 material: Material {
197 albedo: Vec4::ONE,
198 roughness: 0.0,
199 metallic: 0.0,
200 emissive: Vec4::ZERO,
201 alpha_mode: crate::core::scene::AlphaMode::Blend,
202 double_sided: true,
203 },
204 draw_calls: vec![DrawCall {
205 vertex_offset: 0,
206 vertex_count,
207 index_offset: None,
208 index_count: None,
209 instance_count: 1,
210 }],
211 image: None,
212 }
213 }
214
215 pub fn bounds(&mut self) -> BoundingBox {
217 self.ensure_bounds();
218 self.bounds.unwrap_or_default()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn scatter3_defaults() {
228 let points = vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 2.0, 3.0)];
229 let cloud = Scatter3Plot::new(points.clone()).unwrap();
230 assert_eq!(cloud.points.len(), points.len());
231 assert_eq!(cloud.colors.len(), points.len());
232 assert!(cloud.visible);
233 }
234
235 #[test]
236 fn scatter3_custom_colors() {
237 let points = vec![Vec3::new(0.0, 0.0, 0.0)];
238 let colors = vec![Vec4::new(1.0, 0.0, 0.0, 1.0)];
239 let cloud = Scatter3Plot::new(points)
240 .unwrap()
241 .with_colors(colors)
242 .unwrap();
243 assert_eq!(cloud.colors[0], Vec4::new(1.0, 0.0, 0.0, 1.0));
244 }
245
246 #[test]
247 fn scatter3_render_data_contains_vertices() {
248 let points = vec![Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0)];
249 let mut cloud = Scatter3Plot::new(points).unwrap();
250 let render_data = cloud.render_data();
251 assert_eq!(render_data.vertices.len(), 2);
252 assert_eq!(render_data.pipeline_type, PipelineType::Scatter3);
253 }
254}