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