1use crate::{Line, Mesh, Point, Vector, Xform};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5#[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 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 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 pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
169 Ok(serde_json::from_str(json_data)?)
170 }
171
172 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 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;