Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ spin = "0.10"
clap = "4.5"
spirv-std = { git = "https://github.com/Firestar99/rust-gpu-new", rev = "c12f216121820580731440ee79ebc7403d6ea04f", features = ["bytemuck"] }
cargo-gpu = { git = "https://github.com/Firestar99/cargo-gpu", rev = "3952a22d16edbd38689f3a876e417899f21e1fe7", default-features = false }
qrcodegen = "1.8"

[workspace.lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] }
Expand Down
1 change: 1 addition & 0 deletions node-graph/nodes/vector/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ kurbo = { workspace = true }
rand = { workspace = true }
rustc-hash = { workspace = true }
log = { workspace = true }
qrcodegen = { workspace = true }

# Optional workspace dependencies
serde = { workspace = true, optional = true }
Expand Down
118 changes: 118 additions & 0 deletions node-graph/nodes/vector/src/generator_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,117 @@ fn star<T: AsU64>(
Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}

/// Generates a QR code from the input text.
#[node_macro::node(category("Vector: Shape"))]
fn qr_code(
_: impl Ctx,
_primary: (),
#[default("https://graphite.art")] text: String,
#[default(true)]
#[name("Merge Adjacent Tiles")]
merge: bool,
) -> Table<Vector> {
let ecc = qrcodegen::QrCodeEcc::Medium;

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

let size = qr_code.size();
let mut vector = Vector::default();
let offset = DVec2::splat(0.0);

if merge {
use std::collections::{HashMap, HashSet, VecDeque};

let mut remaining: HashSet<(i32, i32)> = HashSet::new();
for y in 0..size {
for x in 0..size {
if qr_code.get_module(x, y) {
remaining.insert((x, y));
}
}
}

while let Some(&(start_x, start_y)) = remaining.iter().next() {
let mut island = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back((start_x, start_y));
remaining.remove(&(start_x, start_y));

while let Some((x, y)) = queue.pop_front() {
island.insert((x, y));
for (dx, dy) in [(0, 1), (0, -1), (1, 0), (-1, 0)] {
let nx = x + dx;
let ny = y + dy;
if remaining.remove(&(nx, ny)) {
queue.push_back((nx, ny));
}
}
}

let mut island_edges = HashSet::new();
for &(x, y) in &island {
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))] {
if !island_edges.remove(&(p2, p1)) {
island_edges.insert((p1, p2));
}
}
}

let mut adjacency: HashMap<(i32, i32), Vec<(i32, i32)>> = HashMap::new();
for (p1, p2) in island_edges {
adjacency.entry(p1).or_default().push(p2);
}

while let Some(&start_point) = adjacency.keys().next() {
let mut loop_points = Vec::new();
let mut current = start_point;

loop {
loop_points.push(DVec2::new(current.0 as f64, current.1 as f64));
let next = adjacency.get_mut(&current).and_then(|n| n.pop()).unwrap();
if adjacency.get(&current).map_or(false, |n| n.is_empty()) {
adjacency.remove(&current);
}
current = next;
if current == start_point {
break;
}
}

if loop_points.len() > 2 {
let mut simplified = Vec::new();
for i in 0..loop_points.len() {
let prev = loop_points[(i + loop_points.len() - 1) % loop_points.len()];
let curr = loop_points[i];
let next = loop_points[(i + 1) % loop_points.len()];

if (curr - prev).perp_dot(next - curr).abs() > 1e-6 {
simplified.push(curr + offset);
}
}
if !simplified.is_empty() {
vector.append_subpath(subpath::Subpath::from_anchors(simplified, true), false);
}
}
}
}
} else {
for y in 0..size {
for x in 0..size {
if qr_code.get_module(x, y) {
let corner1 = offset + DVec2::new(x as f64, y as f64);
let corner2 = corner1 + DVec2::splat(1.);
vector.append_subpath(subpath::Subpath::new_rect(corner1, corner2), false);
}
}
}
}

Table::new_from_element(vector)
}

/// Generates a line with endpoints at the two chosen coordinates.
#[node_macro::node(category("Vector: Shape"))]
fn line(
Expand Down Expand Up @@ -349,4 +460,11 @@ mod tests {
assert!([90., 150., 40.].into_iter().any(|target| (target - angle).abs() < 1e-10), "unexpected angle of {angle}")
}
}

#[test]
fn qr_code_test() {
let qr = qr_code((), (), "https://graphite.art".to_string(), true);
assert!(qr.iter().next().unwrap().element.point_domain.ids().len() > 0);
assert!(qr.iter().next().unwrap().element.segment_domain.ids().len() > 0);
}
}
Loading