Created
May 16, 2023 17:43
-
-
Save gre/5b3167863dd1009b4b73d64e6b8b109f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::f64::consts::PI; | |
use svg::node::element::path::Data; | |
use svg::node::element::{Group, Image, Path}; | |
fn heart_function(t: f64) -> (f64, f64) { | |
let x = 16.0 * f64::sin(t).powi(3); | |
let y = -13.0 * f64::cos(t) | |
+ 5.0 * f64::cos(2.0 * t) | |
+ 2.0 * f64::cos(3.0 * t) | |
+ f64::cos(4.0 * t); | |
(x, y) | |
} | |
fn build_hexagon(center: (f64, f64), radius: f64) -> Vec<(f64, f64)> { | |
let mut points = Vec::new(); | |
let angle_step = 2.0 * PI / 6.0; | |
for i in 0..6 { | |
let angle = i as f64 * angle_step; | |
let x = center.0 + radius * angle.cos(); | |
let y = center.1 + radius * angle.sin(); | |
points.push((x, y)); | |
} | |
points | |
} | |
fn point_in_polygon(point: (f64, f64), polygon: &Vec<(f64, f64)>) -> bool { | |
let mut inside = false; | |
let mut j = polygon.len() - 1; | |
for i in 0..polygon.len() { | |
let (xi, yi) = polygon[i]; | |
let (xj, yj) = polygon[j]; | |
let intersect = ((yi > point.1) != (yj > point.1)) | |
&& (point.0 < (xj - xi) * (point.1 - yi) / (yj - yi) + xi); | |
if intersect { | |
inside = !inside; | |
} | |
j = i; | |
} | |
inside | |
} | |
pub fn art() -> svg::Document { | |
let width = 200.; | |
let height = 200.; | |
let hex_border = 6.0; | |
let hex_rad = 92.0; | |
let hex_poly = build_hexagon((width / 2.0, height / 2.0), hex_rad); | |
let mut routes_bg = Vec::new(); | |
let mut routes_bg_fill = Vec::new(); | |
let stroke_width = 0.5; | |
let heart_size = 0.2; | |
let distx = 24.0 * heart_size; | |
let disty = 24.0 * heart_size; | |
let count = 100; | |
let mut base_shape: Vec<(f64, f64)> = (0..count) | |
.map(|i| { | |
let t = i as f64 * 2.0 * PI / (count as f64); | |
let p = heart_function(t); | |
(p.0 * heart_size, p.1 * heart_size) | |
}) | |
.collect(); | |
base_shape.push(base_shape[0]); | |
let mut xi = 0usize; | |
let mut x = 0.0; | |
while x < width { | |
let mut y = 0.0; | |
let mut yi = 0usize; | |
while y < height { | |
if !point_in_polygon((x, y), &hex_poly) { | |
y += disty; | |
yi += 1; | |
continue; | |
} | |
let route: Vec<(f64, f64)> = | |
base_shape.iter().map(|p| (p.0 + x, p.1 + y)).collect(); | |
let is_outside = |p| !point_in_polygon(p, &hex_poly); | |
routes_bg.extend(clip_routes(&vec![route.clone()], &is_outside, 0.5, 4)); | |
if (xi + 2 * yi) % 4 == 0 { | |
routes_bg_fill.push(route.clone()); | |
} | |
y += disty; | |
yi += 1; | |
} | |
x += distx; | |
xi += 1; | |
} | |
let mut document = svg::Document::new() | |
.set("viewBox", (0, 0, width, height)) | |
.set("width", format!("{}mm", width)) | |
.set("height", format!("{}mm", height)) | |
.set( | |
"xmlns:inkscape", | |
"http://www.inkscape.org/namespaces/inkscape", | |
) | |
.set("xmlns", "http://www.w3.org/2000/svg"); | |
let mut data1 = Data::new(); | |
for route in routes_bg { | |
data1 = render_route(data1, route); | |
} | |
let mut data2 = Data::new(); | |
data2 = render_route(data2, hex_poly); | |
data2 = data2.close(); | |
let mut g = Group::new() | |
//.set("opacity", 0.5) | |
.add( | |
Path::new() | |
.set("fill", "none") | |
.set("stroke", "black") | |
.set("stroke-width", stroke_width) | |
.set("d", data1), | |
); | |
for route in routes_bg_fill { | |
let mut data = Data::new(); | |
data = render_route(data, route); | |
g = g.add( | |
Path::new() | |
.set("fill", "black") | |
.set("stroke", "none") | |
.set("d", data), | |
); | |
} | |
document = document.add(g); | |
document = document.add( | |
Path::new() | |
.set("fill", "none") | |
.set("stroke", "black") | |
.set("stroke-width", hex_border) | |
.set("d", data2), | |
); | |
/* | |
document = document.add( | |
Image::new() | |
.set("width", width) | |
.set("height", height) | |
.set("href", "/Users/gre/dev/urne-mariage/embedded.svg"), | |
); | |
*/ | |
document | |
} | |
fn render_route(data: Data, route: Vec<(f64, f64)>) -> Data { | |
if route.len() == 0 { | |
return data; | |
} | |
let first_p = route[0]; | |
let mut d = data.move_to(first_p); | |
for p in route { | |
d = d.line_to(p); | |
} | |
return d; | |
} | |
fn clip_routes( | |
input_routes: &Vec<Vec<(f64, f64)>>, | |
is_outside: &dyn Fn((f64, f64)) -> bool, | |
stepping: f64, | |
dichotomic_iterations: usize, | |
) -> Vec<Vec<(f64, f64)>> { | |
// locate the intersection where inside and outside cross | |
let search = |inside: (f64, f64), outside: (f64, f64), n| { | |
let mut a = inside; | |
let mut b = outside; | |
for _i in 0..n { | |
let middle = lerp_point(a, b, 0.5); | |
if is_outside(middle) { | |
b = middle; | |
} else { | |
a = middle; | |
} | |
} | |
return lerp_point(a, b, 0.5); | |
}; | |
let mut routes = vec![]; | |
for input_route in input_routes.iter() { | |
if input_route.len() < 2 { | |
continue; | |
} | |
let mut prev = input_route[0]; | |
let mut prev_is_outside = is_outside(prev); | |
let mut route = vec![]; | |
if !prev_is_outside { | |
// prev is not to crop. we can start with it | |
route.push(prev); | |
} | |
for &p in input_route.iter().skip(1) { | |
// we iterate in small steps to detect any interruption | |
let static_prev = prev; | |
let dx = p.0 - prev.0; | |
let dy = p.1 - prev.1; | |
let d = (dx * dx + dy * dy).sqrt(); | |
let vx = dx / d; | |
let vy = dy / d; | |
let iterations = (d / stepping).ceil() as usize; | |
let mut v = 0.0; | |
for _i in 0..iterations { | |
v = (v + stepping).min(d); | |
let q = (static_prev.0 + vx * v, static_prev.1 + vy * v); | |
let q_is_outside = is_outside(q); | |
if prev_is_outside != q_is_outside { | |
// we have a crossing. we search it precisely | |
let intersection = if prev_is_outside { | |
search(q, prev, dichotomic_iterations) | |
} else { | |
search(prev, q, dichotomic_iterations) | |
}; | |
if q_is_outside { | |
// we close the path | |
route.push(intersection); | |
if route.len() > 1 { | |
// we have a valid route to accumulate | |
routes.push(route); | |
} | |
route = vec![]; | |
} else { | |
// we open the path | |
route.push(intersection); | |
} | |
prev_is_outside = q_is_outside; | |
} | |
prev = q; | |
} | |
// prev should be == p | |
if !prev_is_outside { | |
// prev is not to crop. we can start with it | |
route.push(p); | |
} | |
} | |
if route.len() > 1 { | |
// we have a valid route to accumulate | |
routes.push(route); | |
} | |
} | |
routes | |
} | |
#[inline] | |
fn lerp_point(a: (f64, f64), b: (f64, f64), m: f64) -> (f64, f64) { | |
(a.0 * (1. - m) + b.0 * m, a.1 * (1. - m) + b.1 * m) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment