Skip to content
Merged
Changes from all 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
178 changes: 167 additions & 11 deletions iOverlay/src/mesh/outline/offset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,26 +288,23 @@ impl<P: FloatPointCompatible<T> + 'static, T: FloatNumber + 'static> OutlineSolv
offset_overlay.clear();
segments.clear();

if area < 0 {
let contour_fill_rule = if area < 0 {
offset_overlay.options.output_direction = ContourDirection::CounterClockwise;
segments.reserve(self.outer_builder.capacity(path.len()));
self.outer_builder.build(path, &self.adapter, &mut segments);

offset_overlay.add_segments(&segments);

if let Some(graph) = offset_overlay.build_graph_view(FillRule::Positive) {
graph.extract_contours_into(OverlayRule::Subject, &mut bool_buffer, &mut flat_buffer);
}
FillRule::Positive
} else {
offset_overlay.options.output_direction = ContourDirection::Clockwise;
segments.reserve(self.inner_builder.capacity(path.len()));
self.inner_builder.build(path, &self.adapter, &mut segments);

offset_overlay.add_segments(&segments);
FillRule::Negative
};

if let Some(graph) = offset_overlay.build_graph_view(FillRule::Negative) {
graph.extract_contours_into(OverlayRule::Subject, &mut bool_buffer, &mut flat_buffer);
}
offset_overlay.add_segments(&segments);

if let Some(graph) = offset_overlay.build_graph_view(contour_fill_rule) {
graph.extract_contours_into(OverlayRule::Subject, &mut bool_buffer, &mut flat_buffer);
}

overlay.add_flat_buffer(&flat_buffer, Subject);
Expand Down Expand Up @@ -362,12 +359,17 @@ impl<P: FloatPointCompatible<T> + 'static, T: FloatNumber + 'static> OutlineSolv

#[cfg(test)]
mod tests {
use crate::core::fill_rule::FillRule;
use crate::float::simplify::SimplifyShape;
use crate::mesh::outline::offset::OutlineOffset;
use crate::mesh::style::{LineJoin, OutlineStyle};
use alloc::vec;
use alloc::vec::Vec;
use core::f32::consts::PI;
use i_shape::base::data::{Path, Shape};
use i_shape::flat::float::FloatFlatContoursBuffer;
use i_shape::float::area::Area;
use rand::RngExt;

#[test]
fn test_doc() {
Expand Down Expand Up @@ -887,6 +889,119 @@ mod tests {
};
}

#[test]
fn test_star_bevel_0() {
let r0 = 5.0;
let r1 = 20.0;
let pi = core::f64::consts::PI;
for count in 8..24 {
let mut angle = 0.0;
while angle < pi {
let shape = create_star(r0, r1, count, angle);
let mut offset = 0.0;
let mut prev_area = 0.0;
while offset < 10.0 {
let min_area = pi * (r0 + offset).powi(2);
let max_area = pi * (r1 + offset).powi(2);

let style = OutlineStyle::new(offset);
let outline_shapes = shape.outline(&style);
let area = outline_shapes.area();

assert!(area >= min_area);
assert!(area <= max_area);
assert!(prev_area < area);

offset += 0.5;
prev_area = area;
}
angle += 1.0;
}
}
}

#[test]
fn test_star_round_0() {
let r0 = 5.0;
let r1 = 20.0;
let pi = core::f64::consts::PI;
let join_angle = pi / 3.0;
for count in 8..24 {
let mut angle = 0.0;
while angle < pi {
let shape = create_star(r0, r1, count, angle);
let mut offset = 0.0;
let mut prev_area = 0.0;
while offset < 10.0 {
let min_area = pi * (r0 + offset).powi(2);
let max_area = pi * (r1 + offset).powi(2);
let style = OutlineStyle::new(offset).line_join(LineJoin::Round(join_angle));

let outline_shapes = shape.outline(&style);
let area = outline_shapes.area();

assert!(area >= min_area);
assert!(area <= max_area);
assert!(prev_area < area);

offset += 0.5;
prev_area = area;
}
angle += 1.0;
}
}
}

#[test]
fn test_random_0() {
let style = OutlineStyle::new(10.0);
for _ in 0..100 {
let shapes = random_float(100.0, 100).simplify_shape(FillRule::NonZero);
let base_area = shapes.area();
let outline_shapes = shapes.outline(&style);
let area = outline_shapes.area();
assert!(base_area < area);
}
}

#[test]
fn test_random_1() {
let join_angle = core::f64::consts::PI / 3.0;
let style = OutlineStyle::new(10.0).line_join(LineJoin::Round(join_angle));
for _ in 0..100 {
let shapes = random_float(100.0, 100).simplify_shape(FillRule::NonZero);
let base_area = shapes.area();
let outline_shapes = shapes.outline(&style);
let area = outline_shapes.area();
assert!(base_area < area);
}
}

#[test]
fn test_random_2() {
let style = OutlineStyle::new(-10.0);
for _ in 0..100 {
let shapes = random_float(100.0, 100).simplify_shape(FillRule::NonZero);
let base_area = shapes.area();
let outline_shapes = shapes.outline(&style);
let area = outline_shapes.area();
assert!(base_area >= area);
}
}

#[test]
fn test_random_3() {
let join_angle = core::f64::consts::PI / 3.0;
let style = OutlineStyle::new(-10.0).line_join(LineJoin::Round(join_angle));
for _ in 0..100 {
let shapes = random_float(100.0, 100).simplify_shape(FillRule::NonZero);
let base_area = shapes.area();
let outline_shapes = shapes.outline(&style);
let area = outline_shapes.area();
assert!(base_area >= area);
}
}

#[test]
fn test_real_case_0() {
let main = vec![
Expand Down Expand Up @@ -1097,4 +1212,45 @@ mod tests {
assert!(shape[0].len() < 1_000);
};
}

fn create_star(r0: f64, r1: f64, count: usize, angle: f64) -> Shape<[f64; 2]> {
let da = core::f64::consts::PI / count as f64;
let mut a = angle;

let mut points = Vec::new();

for _ in 0..count {
let (sn, cs) = a.sin_cos();

let xr0 = r0 * cs;
let yr0 = r0 * sn;

a += da;

let (sn, cs) = a.sin_cos();
let xr1 = r1 * cs;
let yr1 = r1 * sn;

a += da;

points.push([xr0, yr0]);
points.push([xr1, yr1]);
}

[points].to_vec()
}

fn random_float(radius: f64, n: usize) -> Path<[f64; 2]> {
let a = 0.5 * radius;
let range = -a..=a;
let mut points = Vec::with_capacity(n);
let mut rng = rand::rng();
for _ in 0..n {
let x = rng.random_range(range.clone());
let y = rng.random_range(range.clone());
points.push([x, y])
}

points
}
}
Loading