jagua_rs/io/svg/
svg_util.rs

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)]
13pub struct SvgDrawOptions {
14    ///The theme to use for the svg
15    #[serde(default)]
16    pub theme: SvgLayoutTheme,
17    ///Draw the quadtree on top
18    #[serde(default)]
19    pub quadtree: bool,
20    ///Draw the fail fast surrogate on top of each item
21    #[serde(default)]
22    pub surrogate: bool,
23    ///Draw dashed lines between colliding items
24    #[serde(default)]
25    pub highlight_collisions: bool,
26    ///Draw the modified shapes used internally instead of the original ones
27    #[serde(default)]
28    pub draw_cd_shapes: bool,
29    #[serde(default)]
30    pub highlight_cd_shapes: bool,
31}
32
33impl Default for SvgDrawOptions {
34    fn default() -> Self {
35        Self {
36            theme: SvgLayoutTheme::default(),
37            quadtree: false,
38            surrogate: true,
39            highlight_collisions: true,
40            draw_cd_shapes: false,
41            highlight_cd_shapes: true,
42        }
43    }
44}
45
46#[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Copy)]
47pub struct SvgLayoutTheme {
48    pub stroke_width_multiplier: f32,
49    pub container_fill: Color,
50    pub item_fill: Color,
51    pub hole_fill: Color,
52    pub qz_fill: [Color; N_QUALITIES],
53    pub qz_stroke_opac: f32,
54    pub collision_highlight_color: Color,
55}
56
57impl Default for SvgLayoutTheme {
58    fn default() -> Self {
59        SvgLayoutTheme::EARTH_TONES
60    }
61}
62
63impl SvgLayoutTheme {
64    pub const EARTH_TONES: SvgLayoutTheme = SvgLayoutTheme {
65        stroke_width_multiplier: 2.0,
66        container_fill: Color(0xCC, 0x82, 0x4A),
67        item_fill: Color(0xFF, 0xC8, 0x79),
68        hole_fill: Color(0x2D, 0x2D, 0x2D),
69        qz_fill: [
70            Color(0x00, 0x00, 0x00), // BLACK
71            Color(0xFF, 0x00, 0x00), // RED
72            Color(0xFF, 0x5E, 0x00), // ORANGE
73            Color(0xFF, 0xA5, 0x00), // LIGHT ORANGE
74            Color(0xC7, 0xA9, 0x00), // DARK YELLOW
75            Color(0xFF, 0xFF, 0x00), // YELLOW
76            Color(0xCB, 0xFF, 0x00), // GREEN
77            Color(0xCB, 0xFF, 0x00), // GREEN
78            Color(0xCB, 0xFF, 0x00), // GREEN
79            Color(0xCB, 0xFF, 0x00), // GREEN
80        ],
81        qz_stroke_opac: 0.5,
82        collision_highlight_color: Color(0x00, 0xFF, 0x00), // LIME
83    };
84
85    pub const GRAY: SvgLayoutTheme = SvgLayoutTheme {
86        stroke_width_multiplier: 2.5,
87        container_fill: Color(0xD3, 0xD3, 0xD3),
88        item_fill: Color(0x7A, 0x7A, 0x7A),
89        hole_fill: Color(0xFF, 0xFF, 0xFF),
90        qz_fill: [
91            Color(0x63, 0x63, 0x63), //GRAY
92            Color(0x63, 0x63, 0x63), //GRAY
93            Color(0x63, 0x63, 0x63), //GRAY
94            Color(0x63, 0x63, 0x63), //GRAY
95            Color(0x63, 0x63, 0x63), //GRAY
96            Color(0x63, 0x63, 0x63), //GRAY
97            Color(0x63, 0x63, 0x63), //GRAY
98            Color(0x63, 0x63, 0x63), //GRAY
99            Color(0x63, 0x63, 0x63), //GRAY
100            Color(0x63, 0x63, 0x63), //GRAY
101        ],
102        qz_stroke_opac: 0.9,
103        collision_highlight_color: Color(0xD0, 0x00, 0x00), //LIME
104    };
105}
106
107pub fn change_brightness(color: Color, fraction: f32) -> Color {
108    let Color(r, g, b) = color;
109
110    let r = (r as f32 * fraction) as u8;
111    let g = (g as f32 * fraction) as u8;
112    let b = (b as f32 * fraction) as u8;
113    Color(r, g, b)
114}
115
116pub fn blend_colors(color_1: Color, color_2: Color) -> Color {
117    //blend color_1 and color_2
118    let Color(r_1, g_1, b_1) = color_1;
119    let Color(r_2, g_2, b_2) = color_2;
120
121    let r = ((r_1 as f32 * 0.5) + (r_2 as f32 * 0.5)) as u8;
122    let g = ((g_1 as f32 * 0.5) + (g_2 as f32 * 0.5)) as u8;
123    let b = ((b_1 as f32 * 0.5) + (b_2 as f32 * 0.5)) as u8;
124
125    Color(r, g, b)
126}
127
128#[derive(Copy, Clone, PartialEq, Debug)]
129pub struct Color(u8, u8, u8);
130
131impl Display for Color {
132    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
133        write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
134    }
135}
136
137impl From<String> for Color {
138    fn from(mut s: String) -> Self {
139        if s.starts_with('#') {
140            s.remove(0);
141        }
142        let r = u8::from_str_radix(&s[0..2], 16).unwrap();
143        let g = u8::from_str_radix(&s[2..4], 16).unwrap();
144        let b = u8::from_str_radix(&s[4..6], 16).unwrap();
145        Color(r, g, b)
146    }
147}
148
149impl From<&str> for Color {
150    fn from(s: &str) -> Self {
151        Color::from(s.to_owned())
152    }
153}
154
155impl Serialize for Color {
156    fn serialize<S>(&self, serializer: S) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
157    where
158        S: Serializer,
159    {
160        serializer.serialize_str(&format!("{self}"))
161    }
162}
163
164impl<'de> Deserialize<'de> for Color {
165    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
166    where
167        D: Deserializer<'de>,
168    {
169        let s = String::deserialize(deserializer)?;
170        Ok(Color::from(s))
171    }
172}
173
174pub fn original_shape_data(
175    original: &OriginalShape,
176    internal: &SPolygon,
177    draw_internal: bool,
178) -> Data {
179    match draw_internal {
180        true => simple_polygon_data(internal),
181        false => 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
206fn qt_node_data(
207    qt_node: &QTNode,
208    mut data_eh: Data, //entire hazards data
209    mut data_ph: Data, //partial hazards data
210    mut data_nh: Data, //no hazards data
211    filter: &impl HazardFilter,
212) -> (Data, Data, Data) {
213    //Only draw qt_nodes that do not have a child
214
215    match (qt_node.has_children(), qt_node.hazards.strongest(filter)) {
216        (true, Some(_)) => {
217            //not a leaf node, go to children
218            for child in qt_node.children.as_ref().unwrap().iter() {
219                let data = qt_node_data(child, data_eh, data_ph, data_nh, filter);
220                data_eh = data.0;
221                data_ph = data.1;
222                data_nh = data.2;
223            }
224        }
225        (true, None) | (false, _) => {
226            //leaf node, draw it
227            let rect = &qt_node.bbox;
228            let draw = |data: Data| -> Data {
229                data.move_to((rect.x_min, rect.y_min))
230                    .line_to((rect.x_max, rect.y_min))
231                    .line_to((rect.x_max, rect.y_max))
232                    .line_to((rect.x_min, rect.y_max))
233                    .close()
234            };
235
236            match qt_node.hazards.strongest(filter) {
237                Some(ch) => match ch.presence {
238                    QTHazPresence::Entire => data_eh = draw(data_eh),
239                    QTHazPresence::Partial(_) => data_ph = draw(data_ph),
240                    QTHazPresence::None => unreachable!(),
241                },
242                None => data_nh = draw(data_nh),
243            }
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}