1use glam::{Mat4, Vec3, Vec4};
7
8#[derive(Debug, Clone)]
13pub struct SlicePlane {
14 name: String,
16 origin: Vec3,
18 normal: Vec3,
20 enabled: bool,
22 draw_plane: bool,
24 draw_widget: bool,
26 color: Vec4,
28 transparency: f32,
30 plane_size: f32,
32}
33
34impl SlicePlane {
35 pub fn new(name: impl Into<String>) -> Self {
39 Self {
40 name: name.into(),
41 origin: Vec3::ZERO,
42 normal: Vec3::Y,
43 enabled: true,
44 draw_plane: true,
45 draw_widget: true,
46 color: Vec4::new(0.5, 0.5, 0.5, 1.0),
47 transparency: 0.5,
48 plane_size: 0.05,
49 }
50 }
51
52 pub fn with_pose(name: impl Into<String>, origin: Vec3, normal: Vec3) -> Self {
54 Self {
55 name: name.into(),
56 origin,
57 normal: normal.normalize(),
58 enabled: true,
59 draw_plane: true,
60 draw_widget: true,
61 color: Vec4::new(0.5, 0.5, 0.5, 1.0),
62 transparency: 0.5,
63 plane_size: 0.05,
64 }
65 }
66
67 #[must_use]
69 pub fn name(&self) -> &str {
70 &self.name
71 }
72
73 #[must_use]
75 pub fn origin(&self) -> Vec3 {
76 self.origin
77 }
78
79 pub fn set_origin(&mut self, origin: Vec3) {
81 self.origin = origin;
82 }
83
84 #[must_use]
86 pub fn normal(&self) -> Vec3 {
87 self.normal
88 }
89
90 pub fn set_normal(&mut self, normal: Vec3) {
92 self.normal = normal.normalize();
93 }
94
95 pub fn set_pose(&mut self, origin: Vec3, normal: Vec3) {
97 self.origin = origin;
98 self.normal = normal.normalize();
99 }
100
101 #[must_use]
103 pub fn is_enabled(&self) -> bool {
104 self.enabled
105 }
106
107 pub fn set_enabled(&mut self, enabled: bool) {
109 self.enabled = enabled;
110 }
111
112 #[must_use]
114 pub fn draw_plane(&self) -> bool {
115 self.draw_plane
116 }
117
118 pub fn set_draw_plane(&mut self, draw: bool) {
120 self.draw_plane = draw;
121 }
122
123 #[must_use]
125 pub fn draw_widget(&self) -> bool {
126 self.draw_widget
127 }
128
129 pub fn set_draw_widget(&mut self, draw: bool) {
131 self.draw_widget = draw;
132 }
133
134 #[must_use]
136 pub fn color(&self) -> Vec4 {
137 self.color
138 }
139
140 pub fn set_color(&mut self, color: Vec3) {
142 self.color = color.extend(1.0);
143 }
144
145 #[must_use]
147 pub fn transparency(&self) -> f32 {
148 self.transparency
149 }
150
151 pub fn set_transparency(&mut self, transparency: f32) {
153 self.transparency = transparency.clamp(0.0, 1.0);
154 }
155
156 #[must_use]
158 pub fn plane_size(&self) -> f32 {
159 self.plane_size
160 }
161
162 pub fn set_plane_size(&mut self, size: f32) {
164 self.plane_size = size.max(0.001);
165 }
166
167 #[must_use]
171 pub fn signed_distance(&self, point: Vec3) -> f32 {
172 (point - self.origin).dot(self.normal)
173 }
174
175 #[must_use]
177 pub fn is_kept(&self, point: Vec3) -> bool {
178 !self.enabled || self.signed_distance(point) >= 0.0
179 }
180
181 #[must_use]
183 pub fn project(&self, point: Vec3) -> Vec3 {
184 point - self.signed_distance(point) * self.normal
185 }
186
187 #[must_use]
196 pub fn to_transform(&self) -> Mat4 {
197 let x_axis = self.normal.normalize();
198
199 let up = if x_axis.dot(Vec3::Y).abs() < 0.99 {
201 Vec3::Y
202 } else {
203 Vec3::Z
204 };
205
206 let y_axis = up.cross(x_axis).normalize();
208 let z_axis = x_axis.cross(y_axis).normalize();
209
210 Mat4::from_cols(
211 Vec4::new(x_axis.x, x_axis.y, x_axis.z, 0.0),
212 Vec4::new(y_axis.x, y_axis.y, y_axis.z, 0.0),
213 Vec4::new(z_axis.x, z_axis.y, z_axis.z, 0.0),
214 Vec4::new(self.origin.x, self.origin.y, self.origin.z, 1.0),
215 )
216 }
217
218 pub fn set_from_transform(&mut self, transform: Mat4) {
223 self.origin = transform.w_axis.truncate();
225 self.normal = transform.x_axis.truncate().normalize();
227 }
228}
229
230impl Default for SlicePlane {
231 fn default() -> Self {
232 Self::new("default")
233 }
234}
235
236#[repr(C)]
238#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
239#[allow(clippy::pub_underscore_fields)]
240pub struct SlicePlaneUniforms {
241 pub origin: [f32; 3],
243 pub enabled: f32,
245 pub normal: [f32; 3],
247 pub _padding: f32,
249}
250
251impl From<&SlicePlane> for SlicePlaneUniforms {
252 fn from(plane: &SlicePlane) -> Self {
253 Self {
254 origin: plane.origin.to_array(),
255 enabled: if plane.enabled { 1.0 } else { 0.0 },
256 normal: plane.normal.to_array(),
257 _padding: 0.0,
258 }
259 }
260}
261
262impl Default for SlicePlaneUniforms {
263 fn default() -> Self {
264 Self {
265 origin: [0.0; 3],
266 enabled: 0.0,
267 normal: [0.0, 1.0, 0.0],
268 _padding: 0.0,
269 }
270 }
271}
272
273pub const MAX_SLICE_PLANES: usize = 4;
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn test_signed_distance() {
282 let plane = SlicePlane::with_pose("test", Vec3::ZERO, Vec3::Y);
283
284 assert!(plane.signed_distance(Vec3::new(0.0, 1.0, 0.0)) > 0.0);
286
287 assert!(plane.signed_distance(Vec3::new(0.0, -1.0, 0.0)) < 0.0);
289
290 assert!((plane.signed_distance(Vec3::new(1.0, 0.0, 1.0))).abs() < 1e-6);
292 }
293
294 #[test]
295 fn test_is_kept() {
296 let plane = SlicePlane::with_pose("test", Vec3::ZERO, Vec3::Y);
297
298 assert!(plane.is_kept(Vec3::new(0.0, 1.0, 0.0)));
300
301 assert!(!plane.is_kept(Vec3::new(0.0, -1.0, 0.0)));
303
304 let mut disabled_plane = plane.clone();
306 disabled_plane.set_enabled(false);
307 assert!(disabled_plane.is_kept(Vec3::new(0.0, -1.0, 0.0)));
308 }
309
310 #[test]
311 fn test_project() {
312 let plane = SlicePlane::with_pose("test", Vec3::ZERO, Vec3::Y);
313
314 let projected = plane.project(Vec3::new(1.0, 5.0, 2.0));
316 assert!((projected - Vec3::new(1.0, 0.0, 2.0)).length() < 1e-6);
317 }
318
319 #[test]
320 fn test_uniforms() {
321 let plane = SlicePlane::with_pose("test", Vec3::new(1.0, 2.0, 3.0), Vec3::Z);
322 let uniforms = SlicePlaneUniforms::from(&plane);
323
324 assert_eq!(uniforms.origin, [1.0, 2.0, 3.0]);
325 assert_eq!(uniforms.normal, [0.0, 0.0, 1.0]);
326 assert_eq!(uniforms.enabled, 1.0);
327 }
328
329 #[test]
330 fn test_to_transform() {
331 let plane = SlicePlane::with_pose("test", Vec3::new(1.0, 2.0, 3.0), Vec3::X);
332 let transform = plane.to_transform();
333
334 let extracted_origin = transform.w_axis.truncate();
336 assert!((extracted_origin - Vec3::new(1.0, 2.0, 3.0)).length() < 1e-6);
337
338 let extracted_normal = transform.x_axis.truncate().normalize();
340 assert!((extracted_normal - Vec3::X).length() < 1e-6);
341 }
342
343 #[test]
344 fn test_transform_roundtrip() {
345 let original =
346 SlicePlane::with_pose("test", Vec3::new(1.0, 2.0, 3.0), Vec3::new(1.0, 1.0, 0.0));
347 let transform = original.to_transform();
348
349 let mut restored = SlicePlane::new("test2");
350 restored.set_from_transform(transform);
351
352 assert!((restored.origin() - original.origin()).length() < 1e-6);
354
355 assert!((restored.normal() - original.normal().normalize()).length() < 1e-6);
357 }
358}