Skip to content

Commit 869a5f8

Browse files
making sqare merging deterministic
1 parent de5dc82 commit 869a5f8

File tree

3 files changed

+127
-90
lines changed

3 files changed

+127
-90
lines changed

node-graph/nodes/vector/src/generator_nodes.rs

Lines changed: 8 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -187,113 +187,31 @@ fn star<T: AsU64>(
187187
}
188188

189189
/// Generates a QR code from the input text.
190-
#[node_macro::node(category("Vector: Shape"))]
191-
fn qr_code(
192-
_: impl Ctx,
193-
_primary: (),
194-
#[default("https://graphite.art")] text: String,
195-
#[default(true)]
196-
#[name("Merge Adjacent Tiles")]
197-
merge: bool,
190+
#[node_macro::node(category("Vector: Shape"), name("QR Code"))]
191+
fn qr_code(_: impl Ctx, _primary: (), #[default("https://graphite.art")] text: String, #[default(false)] individual_squares: bool
198192
) -> Table<Vector> {
199193
let ecc = qrcodegen::QrCodeEcc::Medium;
200194

201195
let Ok(qr_code) = qrcodegen::QrCode::encode_text(&text, ecc) else {
202196
return Table::default();
203197
};
204198

205-
let size = qr_code.size();
199+
let size = qr_code.size() as usize;
206200
let mut vector = Vector::default();
207-
let offset = DVec2::splat(0.0);
208201

209-
if merge {
210-
use std::collections::{HashMap, HashSet, VecDeque};
211-
212-
let mut remaining: HashSet<(i32, i32)> = HashSet::new();
202+
if individual_squares {
213203
for y in 0..size {
214204
for x in 0..size {
215-
if qr_code.get_module(x, y) {
216-
remaining.insert((x, y));
217-
}
218-
}
219-
}
220-
221-
while let Some(&(start_x, start_y)) = remaining.iter().next() {
222-
let mut island = HashSet::new();
223-
let mut queue = VecDeque::new();
224-
queue.push_back((start_x, start_y));
225-
remaining.remove(&(start_x, start_y));
226-
227-
while let Some((x, y)) = queue.pop_front() {
228-
island.insert((x, y));
229-
for (dx, dy) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
230-
let nx = x + dx;
231-
let ny = y + dy;
232-
if remaining.remove(&(nx, ny)) {
233-
queue.push_back((nx, ny));
234-
}
235-
}
236-
}
237-
238-
let mut island_edges = HashSet::new();
239-
for &(x, y) in &island {
240-
for (p1, p2) in [((x, y), (x + 1, y)), ((x + 1, y), (x + 1, y + 1)), ((x + 1, y + 1), (x, y + 1)), ((x, y + 1), (x, y))] {
241-
if !island_edges.remove(&(p2, p1)) {
242-
island_edges.insert((p1, p2));
243-
}
244-
}
245-
}
246-
247-
let mut adjacency: HashMap<(i32, i32), Vec<(i32, i32)>> = HashMap::new();
248-
for (p1, p2) in island_edges {
249-
adjacency.entry(p1).or_default().push(p2);
250-
}
251-
252-
while let Some(&start_point) = adjacency.keys().next() {
253-
let mut loop_points = Vec::new();
254-
let mut current = start_point;
255-
256-
loop {
257-
loop_points.push(DVec2::new(current.0 as f64, current.1 as f64));
258-
let next = adjacency.get_mut(&current).and_then(|n| n.pop()).unwrap();
259-
if adjacency.get(&current).map_or(false, |n| n.is_empty()) {
260-
adjacency.remove(&current);
261-
}
262-
current = next;
263-
if current == start_point {
264-
break;
265-
}
266-
}
267-
268-
if loop_points.len() > 2 {
269-
let mut simplified = Vec::new();
270-
for i in 0..loop_points.len() {
271-
let prev = loop_points[(i + loop_points.len() - 1) % loop_points.len()];
272-
let curr = loop_points[i];
273-
let next = loop_points[(i + 1) % loop_points.len()];
274-
275-
if (curr - prev).perp_dot(next - curr).abs() > 1e-6 {
276-
simplified.push(curr + offset);
277-
}
278-
}
279-
if !simplified.is_empty() {
280-
vector.append_subpath(subpath::Subpath::from_anchors(simplified, true), false);
281-
}
282-
}
283-
}
284-
}
285-
} else {
286-
for y in 0..size {
287-
for x in 0..size {
288-
if qr_code.get_module(x, y) {
289-
let corner1 = offset + DVec2::new(x as f64, y as f64);
205+
if qr_code.get_module(x as i32, y as i32) {
206+
let corner1 = DVec2::new(x as f64, y as f64);
290207
let corner2 = corner1 + DVec2::splat(1.);
291208
vector.append_subpath(subpath::Subpath::new_rect(corner1, corner2), false);
292209
}
293210
}
294211
}
212+
} else {
213+
crate::merge_qr_squares::merge_qr_squares(&qr_code, &mut vector);
295214
}
296-
297215
Table::new_from_element(vector)
298216
}
299217

node-graph/nodes/vector/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod generator_nodes;
22
pub mod instance;
3+
pub mod merge_qr_squares;
34
pub mod vector_modification_nodes;
45
mod vector_nodes;
56

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use glam::DVec2;
2+
use graphic_types::Vector;
3+
use std::collections::VecDeque;
4+
use vector_types::subpath;
5+
6+
pub fn merge_qr_squares(qr_code: &qrcodegen::QrCode, vector: &mut Vector) {
7+
let size = qr_code.size() as usize;
8+
9+
// 0 = empty
10+
// 1 = black, unvisited
11+
// 2 = black, current island
12+
let mut remaining = vec![vec![0u8; size]; size];
13+
14+
for y in 0..size {
15+
for x in 0..size {
16+
if qr_code.get_module(x as i32, y as i32) {
17+
remaining[y][x] = 1;
18+
}
19+
}
20+
}
21+
22+
for y in 0..size {
23+
for x in 0..size {
24+
if remaining[y][x] != 1 {
25+
continue;
26+
}
27+
28+
// fill island
29+
let mut island = Vec::new();
30+
let mut queue = VecDeque::new();
31+
queue.push_back((x, y));
32+
remaining[y][x] = 2;
33+
34+
while let Some((ix, iy)) = queue.pop_front() {
35+
island.push((ix, iy));
36+
37+
for (dx, dy) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
38+
let nx = ix as i32 + dx;
39+
let ny = iy as i32 + dy;
40+
41+
if nx >= 0 && nx < size as i32 && ny >= 0 && ny < size as i32 && remaining[ny as usize][nx as usize] == 1 {
42+
remaining[ny as usize][nx as usize] = 2;
43+
queue.push_back((nx as usize, ny as usize));
44+
}
45+
}
46+
}
47+
48+
// boundary detection
49+
let mut outbound = vec![vec![0u8; size + 1]; size + 1];
50+
51+
for &(ix, iy) in &island {
52+
if iy == 0 || remaining[iy - 1][ix] != 2 {
53+
outbound[iy][ix] |= 1 << 0;
54+
}
55+
if ix == size - 1 || remaining[iy][ix + 1] != 2 {
56+
outbound[iy][ix + 1] |= 1 << 1;
57+
}
58+
if iy == size - 1 || remaining[iy + 1][ix] != 2 {
59+
outbound[iy + 1][ix + 1] |= 1 << 2;
60+
}
61+
if ix == 0 || remaining[iy][ix - 1] != 2 {
62+
outbound[iy + 1][ix] |= 1 << 3;
63+
}
64+
}
65+
66+
// tracing loops
67+
for vy in 0..=size {
68+
for vx in 0..=size {
69+
while outbound[vy][vx] != 0 {
70+
let mut dir = outbound[vy][vx].trailing_zeros() as usize;
71+
let start = (vx, vy);
72+
let mut current = start;
73+
let mut points = Vec::new();
74+
75+
loop {
76+
points.push(DVec2::new(current.0 as f64, current.1 as f64));
77+
outbound[current.1][current.0] &= !(1 << dir);
78+
79+
current = match dir {
80+
0 => (current.0 + 1, current.1),
81+
1 => (current.0, current.1 + 1),
82+
2 => (current.0 - 1, current.1),
83+
3 => (current.0, current.1 - 1),
84+
_ => unreachable!(),
85+
};
86+
87+
if current == start {
88+
break;
89+
}
90+
dir = outbound[current.1][current.0].trailing_zeros() as usize;
91+
}
92+
93+
if points.len() > 2 {
94+
let mut simplified = Vec::new();
95+
for i in 0..points.len() {
96+
let prev = points[(i + points.len() - 1) % points.len()];
97+
let curr = points[i];
98+
let next = points[(i + 1) % points.len()];
99+
if (curr - prev).perp_dot(next - curr).abs() > 1e-6 {
100+
simplified.push(curr);
101+
}
102+
}
103+
104+
if !simplified.is_empty() {
105+
vector.append_subpath(subpath::Subpath::from_anchors(simplified, true), false);
106+
}
107+
}
108+
}
109+
}
110+
}
111+
112+
// marking island as processed
113+
for &(ix, iy) in &island {
114+
remaining[iy][ix] = 0;
115+
}
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)