Skip to content

Commit

Permalink
reworking edges search algoritm
Browse files Browse the repository at this point in the history
  • Loading branch information
salam99823 committed Oct 30, 2024
1 parent 5301356 commit 60cb046
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 91 deletions.
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ repository = "https://github.com/shnewto/edges"
license = "MIT OR Apache-2.0"

[lints.clippy]
type_complexity = { level = "allow", priority = 1 }
needless_pass_by_value = { level = "allow", priority = 1 }
cast_precision_loss = { level = "allow", priority = 1 }
pedantic = { level = "warn", priority = 0 }

Expand Down
45 changes: 31 additions & 14 deletions src/bin_image.rs → src/bin_image/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{UVec2, Vec2};
pub mod neighbors;

pub struct BinImage {
data: Vec<u8>,
Expand Down Expand Up @@ -74,19 +75,35 @@ impl BinImage {
///
/// # Returns
///
/// An array of 8 boolean values representing the state of the neighboring pixels.
pub fn get_neighbors(&self, p: UVec2) -> [bool; 8] {
/// An byte representing the state of the neighboring pixels.
pub fn get_neighbors(&self, p: UVec2) -> u8 {
let (x, y) = (p.x, p.y);
[
y < u32::MAX && self.get((x, y + 1).into()), // North
y > u32::MIN && self.get((x, y - 1).into()), // South
x < u32::MAX && self.get((x + 1, y).into()), // East
x > u32::MIN && self.get((x - 1, y).into()), // West
x < u32::MAX && y < u32::MAX && self.get((x + 1, y + 1).into()), // Northeast
x > u32::MIN && y > u32::MIN && self.get((x - 1, y - 1).into()), // Southwest
x < u32::MAX && y > u32::MIN && self.get((x + 1, y - 1).into()), // Southeast
x > u32::MIN && y < u32::MAX && self.get((x - 1, y + 1).into()), // Northwest
]
let mut neighbors = 0;
if y < u32::MAX && self.get(UVec2::new(x, y + 1)) {
neighbors |= neighbors::NORTH;
}
if y > u32::MIN && self.get(UVec2::new(x, y - 1)) {
neighbors |= neighbors::SOUTH;
}
if x < u32::MAX && self.get(UVec2::new(x + 1, y)) {
neighbors |= neighbors::EAST;
}
if x > u32::MIN && self.get(UVec2::new(x - 1, y)) {
neighbors |= neighbors::WEST;
}
if x < u32::MAX && y < u32::MAX && self.get(UVec2::new(x + 1, y + 1)) {
neighbors |= neighbors::NORTHEAST;
}
if x > u32::MIN && y > u32::MIN && self.get(UVec2::new(x - 1, y - 1)) {
neighbors |= neighbors::NORTHWEST;
}
if x < u32::MAX && y > u32::MIN && self.get(UVec2::new(x + 1, y - 1)) {
neighbors |= neighbors::SOUTHEAST;
}
if x > u32::MIN && y < u32::MAX && self.get(UVec2::new(x - 1, y + 1)) {
neighbors |= neighbors::SOUTHWEST;
}
neighbors
}

/// Translates a point in positive (x, y) coordinates to a coordinate system centered at (0, 0).
Expand All @@ -100,8 +117,8 @@ impl BinImage {
/// A new `Vec2` representing the translated coordinates
fn translate_point(&self, p: Vec2) -> Vec2 {
Vec2::new(
p.x - (self.width as f32 / 2.0 - 1.0),
(self.height as f32 / 2.0 - 1.0) - p.y,
p.x - ((self.width / 2) as f32 - 1.0),
((self.height / 2) as f32 - 1.0) - p.y,
)
}

Expand Down
8 changes: 8 additions & 0 deletions src/bin_image/neighbors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub const NORTH: u8 = 0b1000_0000;
pub const SOUTH: u8 = 0b0100_0000;
pub const EAST: u8 = 0b0010_0000;
pub const WEST: u8 = 0b0001_0000;
pub const NORTHEAST: u8 = 0b0000_1000;
pub const NORTHWEST: u8 = 0b0000_0100;
pub const SOUTHEAST: u8 = 0b0000_0010;
pub const SOUTHWEST: u8 = 0b0000_0001;
70 changes: 55 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#![doc = include_str!("../README.md")]

use crate::bin_image::BinImage;
#[cfg(feature = "bevy")]
pub use bevy_math::prelude::{UVec2, Vec2};
#[cfg(not(feature = "bevy"))]
pub use glam::{UVec2, Vec2};
use std::fmt;

use crate::{bin_image::BinImage, utils::points_to_drawing_order};
use utils::{is_corner, match_neighbors};

mod bin_image;
#[cfg(feature = "bevy")]
Expand Down Expand Up @@ -62,24 +62,64 @@ impl Edges {
// Marching squares adjacent, walks all the pixels in the provided data and keeps track of
// any that have at least one transparent / zero value neighbor then, while sorting into drawing
// order, groups them into sets of connected pixels
let edge_points = (0..image.height() * image.width())
.map(|i| (i / image.height(), i % image.height()))
.map(|(x, y)| UVec2::new(x, y))
.filter(|p| image.get(*p))
.filter(|p| (0..8).contains(&image.get_neighbors(*p).iter().filter(|i| **i).count()))
let edge_points: Vec<_> = (0..image.height() * image.width())
.map(|i| UVec2::new(i / image.height(), i % image.height()))
.filter(|p| image.get(*p) && is_corner(image.get_neighbors(*p)))
.collect();

points_to_drawing_order(edge_points)
let groups: Vec<_> = self
.points_to_drawing_order(&edge_points)
.into_iter()
.map(|group| {
let group = group.into_iter().map(|p| p.as_vec2()).collect();
if translate {
self.translate(group)
.map(|group| group.into_iter().map(|p| p.as_vec2()).collect())
.collect();
if translate {
groups
.into_iter()
.map(|group| self.translate(group))
.collect()
} else {
groups
}
}

/// Takes a collection of coordinates and attempts to sort them according to drawing order
///
/// Pixel sorted so that the distance to previous and next is 1. When there is no pixel left
/// with distance 1, another group is created and sorted the same way.
fn points_to_drawing_order(&self, points: &[UVec2]) -> Vec<Vec<UVec2>> {
if points.is_empty() {
return Vec::new();
}

let mut groups: Vec<Vec<_>> = Vec::new();
let mut group: Vec<_> = Vec::new();
let mut start = points[0];
let mut current = start;
group.push(current);

loop {
match_neighbors(self.image.get_neighbors(current), &mut current, &mut group);

// 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 {
groups.push(group.clone());
group.clear();

if let Some(new_start) = points
.iter()
.find(|p1| groups.iter().flatten().all(|p2| *p1 != p2))
{
start = *new_start;
current = start;
group.push(current);
} else {
group
break;
}
})
.collect()
}
}

groups
}

/// Translates an `Vec` of points in positive (x, y) coordinates to a coordinate system centered at (0, 0).
Expand Down
Loading

0 comments on commit 60cb046

Please sign in to comment.