1use crate::collision_detection::hazards::filter::HazardFilter;
2use crate::collision_detection::quadtree::{QTHazPresence, QTNode};
3use crate::entities::N_QUALITIES;
4use crate::geometry;
5use crate::geometry::OriginalShape;
6use crate::geometry::primitives::{Edge, Point, SPolygon};
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::fmt::{Display, Formatter};
9use svg::node::element::path::Data;
10use svg::node::element::{Circle, Path};
11
12#[allow(clippy::struct_excessive_bools)]
13#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Copy)]
14#[serde(default)]
15pub struct SvgDrawOptions {
16 pub theme: SvgLayoutTheme,
18 pub quadtree: bool,
20 pub surrogate: bool,
22 pub highlight_collisions: bool,
24 pub draw_cd_shapes: bool,
26 pub highlight_cd_shapes: bool,
28}
29
30impl Default for SvgDrawOptions {
31 fn default() -> Self {
32 Self {
33 theme: SvgLayoutTheme::default(),
34 quadtree: false,
35 surrogate: false,
36 highlight_collisions: true,
37 draw_cd_shapes: false,
38 highlight_cd_shapes: true,
39 }
40 }
41}
42
43#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Copy)]
44pub struct SvgLayoutTheme {
45 pub stroke_width_multiplier: f32,
46 pub container_fill: Color,
47 pub item_fill: Color,
48 pub hole_fill: Color,
49 pub qz_fill: [Color; N_QUALITIES],
50 pub qz_stroke_opac: f32,
51 pub collision_highlight_color: Color,
52}
53
54impl Default for SvgLayoutTheme {
55 fn default() -> Self {
56 SvgLayoutTheme::EARTH_TONES
57 }
58}
59
60impl SvgLayoutTheme {
61 pub const EARTH_TONES: SvgLayoutTheme = SvgLayoutTheme {
62 stroke_width_multiplier: 2.0,
63 container_fill: Color(0xCC, 0x82, 0x4A),
64 item_fill: Color(0xFF, 0xC8, 0x79),
65 hole_fill: Color(0x2D, 0x2D, 0x2D),
66 qz_fill: [
67 Color(0x00, 0x00, 0x00), Color(0xFF, 0x00, 0x00), Color(0xFF, 0x5E, 0x00), Color(0xFF, 0xA5, 0x00), Color(0xC7, 0xA9, 0x00), Color(0xFF, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), Color(0xCB, 0xFF, 0x00), ],
78 qz_stroke_opac: 0.5,
79 collision_highlight_color: Color(0x00, 0xFF, 0x00), };
81
82 pub const GRAY: SvgLayoutTheme = SvgLayoutTheme {
83 stroke_width_multiplier: 2.5,
84 container_fill: Color(0xD3, 0xD3, 0xD3),
85 item_fill: Color(0x7A, 0x7A, 0x7A),
86 hole_fill: Color(0xFF, 0xFF, 0xFF),
87 qz_fill: [
88 Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), Color(0x63, 0x63, 0x63), ],
99 qz_stroke_opac: 0.9,
100 collision_highlight_color: Color(0xD0, 0x00, 0x00), };
102}
103
104#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
105pub fn change_brightness(color: Color, fraction: f32) -> Color {
106 let Color(r, g, b) = color;
107
108 let r = (f32::from(r) * fraction) as u8;
109 let g = (f32::from(g) * fraction) as u8;
110 let b = (f32::from(b) * fraction) as u8;
111 Color(r, g, b)
112}
113
114#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
115pub fn blend_colors(color_1: Color, color_2: Color) -> Color {
116 let Color(r_1, g_1, b_1) = color_1;
118 let Color(r_2, g_2, b_2) = color_2;
119
120 let r = ((f32::from(r_1) * 0.5) + (f32::from(r_2) * 0.5)) as u8;
121 let g = ((f32::from(g_1) * 0.5) + (f32::from(g_2) * 0.5)) as u8;
122 let b = ((f32::from(b_1) * 0.5) + (f32::from(b_2) * 0.5)) as u8;
123
124 Color(r, g, b)
125}
126
127#[derive(Copy, Clone, PartialEq, Debug)]
128pub struct Color(u8, u8, u8);
129
130impl Display for Color {
131 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
132 write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
133 }
134}
135
136impl From<String> for Color {
137 fn from(mut s: String) -> Self {
138 if s.starts_with('#') {
139 s.remove(0);
140 }
141 let r = u8::from_str_radix(&s[0..2], 16).unwrap();
142 let g = u8::from_str_radix(&s[2..4], 16).unwrap();
143 let b = u8::from_str_radix(&s[4..6], 16).unwrap();
144 Color(r, g, b)
145 }
146}
147
148impl From<&str> for Color {
149 fn from(s: &str) -> Self {
150 Color::from(s.to_owned())
151 }
152}
153
154impl Serialize for Color {
155 fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
156 where
157 S: Serializer,
158 {
159 serializer.serialize_str(&format!("{self}"))
160 }
161}
162
163impl<'de> Deserialize<'de> for Color {
164 fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
165 where
166 D: Deserializer<'de>,
167 {
168 let s = String::deserialize(deserializer)?;
169 Ok(Color::from(s))
170 }
171}
172
173pub fn original_shape_data(
174 original: &OriginalShape,
175 internal: &SPolygon,
176 draw_internal: bool,
177) -> Data {
178 if draw_internal {
179 simple_polygon_data(internal)
180 } else {
181 simple_polygon_data(&original.shape)
182 }
183}
184
185pub fn simple_polygon_data(s_poly: &SPolygon) -> Data {
186 let mut data = Data::new().move_to::<(f32, f32)>(s_poly.vertex(0).into());
187 for i in 1..s_poly.n_vertices() {
188 data = data.line_to::<(f32, f32)>(s_poly.vertex(i).into());
189 }
190 data.close()
191}
192
193pub fn quad_tree_data(
194 qt_root: &QTNode,
195 irrelevant_hazards: &impl HazardFilter,
196) -> (Data, Data, Data) {
197 qt_node_data(
198 qt_root,
199 Data::new(),
200 Data::new(),
201 Data::new(),
202 irrelevant_hazards,
203 )
204}
205
206#[allow(clippy::similar_names)]
207fn qt_node_data(
208 qt_node: &QTNode,
209 mut data_eh: Data, mut data_ph: Data, mut data_nh: Data, filter: &impl HazardFilter,
213) -> (Data, Data, Data) {
214 if let (Some(children), Some(_)) =
217 (qt_node.children.as_ref(), qt_node.hazards.strongest(filter))
218 {
219 for child in children.iter() {
221 let data = qt_node_data(child, data_eh, data_ph, data_nh, filter);
222 data_eh = data.0;
223 data_ph = data.1;
224 data_nh = data.2;
225 }
226 } else {
227 let rect = &qt_node.bbox;
229 let draw = |data: Data| -> Data {
230 data.move_to((rect.x_min, rect.y_min))
231 .line_to((rect.x_max, rect.y_min))
232 .line_to((rect.x_max, rect.y_max))
233 .line_to((rect.x_min, rect.y_max))
234 .close()
235 };
236
237 match qt_node.hazards.strongest(filter) {
238 Some(ch) => match ch.presence {
239 QTHazPresence::Entire => data_eh = draw(data_eh),
240 QTHazPresence::Partial(_) => data_ph = draw(data_ph),
241 QTHazPresence::None => unreachable!(),
242 },
243 None => data_nh = draw(data_nh),
244 }
245 }
246
247 (data_eh, data_ph, data_nh)
248}
249
250pub fn data_to_path(data: Data, params: &[(&str, &str)]) -> Path {
251 let mut path = Path::new();
252 for param in params {
253 path = path.set(param.0, param.1);
254 }
255 path.set("d", data)
256}
257
258pub fn point(Point(x, y): Point, fill: Option<&str>, rad: Option<f32>) -> Circle {
259 Circle::new()
260 .set("cx", x)
261 .set("cy", y)
262 .set("r", rad.unwrap_or(0.5))
263 .set("fill", fill.unwrap_or("black"))
264}
265
266pub fn circle(circle: geometry::primitives::Circle, params: &[(&str, &str)]) -> Circle {
267 let mut circle = Circle::new()
268 .set("cx", circle.center.0)
269 .set("cy", circle.center.1)
270 .set("r", circle.radius);
271 for param in params {
272 circle = circle.set(param.0, param.1);
273 }
274 circle
275}
276
277pub fn edge_data(edge: Edge) -> Data {
278 Data::new()
279 .move_to((edge.start.0, edge.start.1))
280 .line_to((edge.end.0, edge.end.1))
281}
282
283#[allow(dead_code)]
284pub fn aa_rect_data(rect: geometry::primitives::Rect) -> Data {
285 Data::new()
286 .move_to((rect.x_min, rect.y_min))
287 .line_to((rect.x_max, rect.y_min))
288 .line_to((rect.x_max, rect.y_max))
289 .line_to((rect.x_min, rect.y_max))
290 .close()
291}