Skip to content

Commit

Permalink
Merge pull request #2 from shnewto/drawing-order-fix
Browse files Browse the repository at this point in the history
Points to drawing order wasn't backtracking
  • Loading branch information
shnewto authored May 12, 2024
2 parents 4e250e9 + c508c2a commit af54fe8
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

edges-*.png

# Added by cargo

Expand Down
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ bevy=["dep:bevy"]

[dependencies]
glam = "0.25.0"
hashbrown = "0.14.5"
image = "0.24.9"
mashmap = "0.1.1"
ordered-float = "4.2.0"
thiserror = "1.0.57"

[dependencies.bevy]
Expand All @@ -29,6 +32,10 @@ default-features = false
features = ["bevy_render"]
optional = true

[dev-dependencies]
raqote = "0.8.4"
open = "5.1.2"

[[example]]
name = "bevy-image"

Expand Down
Binary file added assets/lines.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/more-lines.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/terrain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 60 additions & 6 deletions examples/bevy-image.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,76 @@
use bevy::{prelude::Image, render::texture::ImageType};
use edges::Edges;

use raqote::*;
// in an actual bevy app, you wouldn't need all this building an Image from scratch logic,
// it'd be something closer to this:
// `let image = image_assets.get(handle).unwrap();`
// let e = Edges::from(image);
fn main() {
// read png as bytes and manually construct a bevy Image
let image = Image::from_buffer(
include_bytes!("../assets/car.png"),

let boulders = Image::from_buffer(
include_bytes!("../assets/boulders.png"),
ImageType::Extension("png"),
Default::default(),
true,
Default::default(),
Default::default(),
)
.unwrap();

let more_lines = Image::from_buffer(
include_bytes!("../assets/more-lines.png"),
ImageType::Extension("png"),
Default::default(),
true,
Default::default(),
Default::default(),
);
)
.unwrap();

draw_png(boulders, "boulders.png");
draw_png(more_lines, "more-lines.png");
}

fn draw_png(image: Image, img_path: &str) {
// get the image's edges
let edges = Edges::from(image.unwrap());
println!("{:#?}", edges.single_image_edge_translated());
let edges = Edges::from(image.clone());
let scale = 8;
let (width, height) = (image.width() as i32 * scale, image.height() as i32 * scale);

// draw the edges to a png
let mut dt = DrawTarget::new(width, height);

let objects_iter = edges.multi_image_edges_raw().into_iter();

for object in objects_iter {
let mut pb = PathBuilder::new();
let mut edges_iter = object.into_iter();

if let Some(first_edge) = edges_iter.next() {
pb.move_to(first_edge.x * scale as f32, first_edge.y * scale as f32);
for edge in edges_iter {
pb.line_to(edge.x * scale as f32, edge.y * scale as f32);
}
}

let path = pb.finish();
dt.stroke(
&path,
&Source::Solid(SolidSource {
r: 0xff,
g: 0xff,
b: 0xff,
a: 0xff,
}),
&StrokeStyle {
width: 1.,
..StrokeStyle::default()
},
&DrawOptions::new(),
);
}

dt.write_png(format!("edges-{}", img_path)).unwrap();
_ = open::that(format!("edges-{}", img_path));
}
44 changes: 41 additions & 3 deletions examples/dynamic-image.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
use edges::Edges;
use raqote::*;
use std::path::Path;

fn main() {
let image = image::open(Path::new("assets/car.png"));
let edges = Edges::from(image.unwrap());
println!("{:#?}", edges.single_image_edge_translated());
draw_png("car.png");
draw_png("lines.png");
draw_png("terrain.png");
}

fn draw_png(img_path: &str) {
let image = &image::open(Path::new(&format!("assets/{}", img_path))).unwrap();
let edges = Edges::from(image);
let scale = 8;
let (width, height) = (image.width() as i32 * scale, image.height() as i32 * scale);

// draw the edges to a png
let mut dt = DrawTarget::new(width, height);
let mut pb = PathBuilder::new();

let mut edges_iter = edges.single_image_edge_raw().into_iter();
let first_edge = edges_iter.next().unwrap();
pb.move_to(first_edge.x * scale as f32, first_edge.y * scale as f32);
for edge in edges_iter {
pb.line_to(edge.x * scale as f32, edge.y * scale as f32);
}

let path = pb.finish();
dt.stroke(
&path,
&Source::Solid(SolidSource {
r: 0xff,
g: 0xff,
b: 0xff,
a: 0xff,
}),
&StrokeStyle {
width: 1.,
..StrokeStyle::default()
},
&DrawOptions::new(),
);

dt.write_png(format!("edges-{}", img_path)).unwrap();
_ = open::that(format!("edges-{}", img_path));
}
68 changes: 49 additions & 19 deletions src/edges.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::fmt;

use glam::Vec2;
use hashbrown::HashSet;
use mashmap::MashMap;
use ordered_float::OrderedFloat;

pub enum Edges {
DynamicImage(image::DynamicImage),
Expand Down Expand Up @@ -109,36 +112,63 @@ impl Edges {
rows: usize,
cols: usize,
) -> Vec<Vec<Vec2>> {
let mut edge_points: Vec<Vec2> = points.to_vec();
let mut in_drawing_order: Vec<Vec2> = vec![];
let mut groups: Vec<Vec<Vec2>> = vec![];
while !edge_points.is_empty() {
if in_drawing_order.is_empty() {
in_drawing_order.push(edge_points.swap_remove(0));
}
let mut in_drawing_order: Vec<Vec2> = vec![];
let mut drawn_points_with_counts: MashMap<(OrderedFloat<f32>, OrderedFloat<f32>), ()> =
MashMap::new();
let mut drawn_points: HashSet<(OrderedFloat<f32>, OrderedFloat<f32>)> = HashSet::new();
let hashable = |v: Vec2| (OrderedFloat(v.x), OrderedFloat(v.y));
if points.is_empty() {
return groups;
}

let prev = *in_drawing_order.last().unwrap();
let mut current = points[0];
let mut start = current;
in_drawing_order.push(current);
drawn_points_with_counts.insert(hashable(current), ());
drawn_points.insert(hashable(current));

let neighbor = edge_points
while drawn_points.len() < points.len() {
let neighbors = &points
.iter()
.enumerate()
.find(|(_, p)| Edges::distance(prev, **p) == 1.0);
.filter(|p| Edges::distance(current, **p) == 1.0)
.collect::<Vec<&Vec2>>();

if let Some((i, _)) = neighbor {
let next = edge_points.remove(i);
in_drawing_order.push(next);
continue;
if let Some(p) = neighbors
.iter()
.min_by_key(|n| drawn_points_with_counts.get_iter(&hashable(***n)).count())
{
current = **p;
in_drawing_order.push(**p);
drawn_points_with_counts.insert(hashable(**p), ());
drawn_points.insert(hashable(**p));
}

if !in_drawing_order.is_empty() {
// we've traversed and backtracked and we're back at the start without reaching the end of the points
// so we need to start a collecting the points of a new unconnected object
if current == start {
// remove the connecting coordinate
_ = in_drawing_order.pop();
groups.push(in_drawing_order.clone());
in_drawing_order.clear()
in_drawing_order.clear();
drawn_points_with_counts.clear();

if let Some(c) = points
.iter()
.find(|p| !drawn_points.contains(&hashable(**p)))
{
in_drawing_order.push(*c);
drawn_points_with_counts.insert(hashable(*c), ());
drawn_points.insert(hashable(*c));
current = *c;
start = current;
} else {
break;
}
}
}

if !in_drawing_order.is_empty() {
groups.push(in_drawing_order.clone());
}
groups.push(in_drawing_order.clone());

if translate {
groups = groups
Expand Down

0 comments on commit af54fe8

Please sign in to comment.