session_rust/
cylinder.rs

1use crate::{Line, Mesh, Point, Vector, Xform};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5/// A cylinder geometry defined by a line and radius.
6///
7/// The cylinder is generated as a 10-sided cylinder mesh that is oriented
8/// along the line direction and scaled to match the line length and specified radius.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "type", rename = "Cylinder")]
11pub struct Cylinder {
12    pub guid: String,
13    pub name: String,
14    pub radius: f32,
15    pub line: Line,
16    pub mesh: Mesh,
17    #[serde(default = "Xform::identity")]
18    pub xform: Xform,
19}
20
21impl Cylinder {
22    /// Creates a new `Cylinder` from a line and radius.
23    ///
24    /// # Arguments
25    ///
26    /// * `line` - The centerline of the cylinder
27    /// * `radius` - The radius of the cylinder
28    ///
29    /// # Returns
30    ///
31    /// A new `Cylinder` with a generated 10-sided cylinder mesh
32    pub fn new(line: Line, radius: f32) -> Self {
33        let mesh = Self::create_cylinder_mesh(&line, radius);
34        Self {
35            guid: Uuid::new_v4().to_string(),
36            name: "my_cylinder".to_string(),
37            radius,
38            line,
39            mesh,
40            xform: Xform::identity(),
41        }
42    }
43
44    fn create_cylinder_mesh(line: &Line, radius: f32) -> Mesh {
45        let unit_cylinder = Self::unit_cylinder_geometry();
46        let xform = Self::line_to_cylinder_transform(line, radius);
47        Self::transform_geometry(&unit_cylinder, &xform)
48    }
49
50    fn unit_cylinder_geometry() -> (Vec<Point>, Vec<[usize; 3]>) {
51        let vertices = vec![
52            Point::new(0.5, 0.0, -0.5),
53            Point::new(0.404508, 0.293893, -0.5),
54            Point::new(0.154508, 0.475528, -0.5),
55            Point::new(-0.154508, 0.475528, -0.5),
56            Point::new(-0.404508, 0.293893, -0.5),
57            Point::new(-0.5, 0.0, -0.5),
58            Point::new(-0.404508, -0.293893, -0.5),
59            Point::new(-0.154508, -0.475528, -0.5),
60            Point::new(0.154508, -0.475528, -0.5),
61            Point::new(0.404508, -0.293893, -0.5),
62            Point::new(0.5, 0.0, 0.5),
63            Point::new(0.404508, 0.293893, 0.5),
64            Point::new(0.154508, 0.475528, 0.5),
65            Point::new(-0.154508, 0.475528, 0.5),
66            Point::new(-0.404508, 0.293893, 0.5),
67            Point::new(-0.5, 0.0, 0.5),
68            Point::new(-0.404508, -0.293893, 0.5),
69            Point::new(-0.154508, -0.475528, 0.5),
70            Point::new(0.154508, -0.475528, 0.5),
71            Point::new(0.404508, -0.293893, 0.5),
72        ];
73
74        let triangles = vec![
75            [0, 1, 11],
76            [0, 11, 10],
77            [1, 2, 12],
78            [1, 12, 11],
79            [2, 3, 13],
80            [2, 13, 12],
81            [3, 4, 14],
82            [3, 14, 13],
83            [4, 5, 15],
84            [4, 15, 14],
85            [5, 6, 16],
86            [5, 16, 15],
87            [6, 7, 17],
88            [6, 17, 16],
89            [7, 8, 18],
90            [7, 18, 17],
91            [8, 9, 19],
92            [8, 19, 18],
93            [9, 0, 10],
94            [9, 10, 19],
95        ];
96
97        (vertices, triangles)
98    }
99
100    fn line_to_cylinder_transform(line: &Line, radius: f32) -> Xform {
101        let start = line.start();
102        let end = line.end();
103        let line_vec = line.to_vector();
104        let length = line.length();
105
106        let z_axis = line_vec.normalize();
107        let x_axis = if z_axis.z().abs() < 0.9 {
108            Vector::new(0.0, 0.0, 1.0).cross(&z_axis).normalize()
109        } else {
110            Vector::new(1.0, 0.0, 0.0).cross(&z_axis).normalize()
111        };
112        let y_axis = z_axis.cross(&x_axis).normalize();
113
114        let scale = Xform::scale_xyz(radius * 2.0, radius * 2.0, length);
115        let rotation = Xform::from_cols(x_axis, y_axis, z_axis);
116        let center = Point::new(
117            (start.x() + end.x()) * 0.5,
118            (start.y() + end.y()) * 0.5,
119            (start.z() + end.z()) * 0.5,
120        );
121        let translation = Xform::translation(center.x(), center.y(), center.z());
122
123        &translation * &(&rotation * &scale)
124    }
125
126    fn transform_geometry(geometry: &(Vec<Point>, Vec<[usize; 3]>), xform: &Xform) -> Mesh {
127        let (vertices, triangles) = geometry;
128        let mut mesh = Mesh::new();
129
130        let vertex_keys: Vec<usize> = vertices
131            .iter()
132            .map(|v| {
133                let transformed = xform.transformed_point(v);
134                mesh.add_vertex(transformed, None)
135            })
136            .collect();
137
138        for tri in triangles {
139            let face_vertices = vec![
140                vertex_keys[tri[0]],
141                vertex_keys[tri[1]],
142                vertex_keys[tri[2]],
143            ];
144            mesh.add_face(face_vertices, None);
145        }
146
147        mesh
148    }
149
150    ///////////////////////////////////////////////////////////////////////////////////////////
151    // JSON
152    ///////////////////////////////////////////////////////////////////////////////////////////
153
154    /// Serializes the Cylinder to a JSON string.
155    pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
156        let data = serde_json::json!({
157            "type": "Cylinder",
158            "guid": self.guid,
159            "name": self.name,
160            "radius": self.radius,
161            "line": self.line,
162            "mesh": self.mesh.jsondump()
163        });
164        Ok(serde_json::to_string_pretty(&data)?)
165    }
166
167    /// Deserializes a Cylinder from a JSON string.
168    pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
169        Ok(serde_json::from_str(json_data)?)
170    }
171
172    /// Serializes the Cylinder to a JSON file.
173    pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
174        let json = self.jsondump()?;
175        std::fs::write(filepath, json)?;
176        Ok(())
177    }
178
179    /// Deserializes a Cylinder from a JSON file.
180    pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
181        let json = std::fs::read_to_string(filepath)?;
182        Self::jsonload(&json)
183    }
184}
185
186#[cfg(test)]
187#[path = "cylinder_test.rs"]
188mod cylinder_test;