Skip to content
Merged
Show file tree
Hide file tree
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
13 changes: 11 additions & 2 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ fn main() {
.help("Change the color of the window's background")
.takes_value(true),
)
.arg(
Arg::with_name("ASSUME_NO_INTERSECTION")
.long("assume-no-intersection")
.help("Run the tessellation algorithm without intersection checks")
.required(false),
)
)
.subcommand(
declare_tess_params(SubCommand::with_name("reduce"), true)
Expand Down Expand Up @@ -384,7 +390,7 @@ fn get_path(matches: &ArgMatches) -> Option<Path> {

if let Err(e) = parser.parse(&options, &mut Source::new(path_str.chars()), &mut builder) {
println!("Error while parsing path: {}", path_str);
println!("{:?}", e);
println!("{}", e);
}

Some(builder.build())
Expand Down Expand Up @@ -417,12 +423,15 @@ fn get_tess_command(command: &ArgMatches, need_path: bool) -> TessellateCmd {
let dots = get_dots(command);
let fill_rule = get_fill_rule(command);
let orientation = get_orientation(command);
let assume_no_intersection = command.is_present("ASSUME_NO_INTERSECTION");

let fill =
if command.is_present("FILL") || (stroke.is_none() && hatch.is_none() && dots.is_none()) {
Some(
FillOptions::tolerance(get_tolerance(command))
.with_fill_rule(fill_rule)
.with_sweep_orientation(orientation),
.with_sweep_orientation(orientation)
.with_intersections(!assume_no_intersection)
)
} else {
None
Expand Down
9 changes: 9 additions & 0 deletions crates/tessellation/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,18 @@ pub enum InternalError {
InsufficientNumberOfEdges,
MergeVertexOutside,
InvalidNumberOfEdgesBelowVertex,
// TODO: ErrorCode(1) is used to signal that something unexpected happened
// while tessellating with handle_intersections = false. Add a proper error
// variant for this for the next major release.
ErrorCode(i16),
}

impl InternalError {
pub(crate) fn intersections_disabled() -> Self {
InternalError::ErrorCode(1)
}
}

#[cfg(feature = "std")]
impl core::fmt::Display for InternalError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Expand Down
38 changes: 30 additions & 8 deletions crates/tessellation/src/fill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,14 @@ impl FillTessellator {

self.fill_rule = options.fill_rule;
self.orientation = options.sweep_orientation;
self.tolerance = options.tolerance * 0.5;
self.assume_no_intersection = !options.handle_intersections;
self.tolerance = if true || options.handle_intersections {
options.tolerance * 0.5
} else {
// The tolerance theshold allows the tessellator to simplify geometry by collapsing
// nearby vertices. This can cause a non-self-intersecting path to self-intersect.
f32::EPSILON
};

builder.begin_geometry();

Expand All @@ -791,6 +797,13 @@ impl FillTessellator {
debug_assert!(self.fill.spans.is_empty());
}

if self.assume_no_intersection && (!self.active.edges.is_empty() | !self.fill.spans.is_empty()) {
tess_log!(self, "Tessellation failed with TessellatorOptions::handle_intersections = false. ");
builder.abort_geometry();

return Err(InternalError::intersections_disabled().into());
}

// There shouldn't be any span left after the tessellation ends.
// If for whatever reason (bug) there are, flush them so that we don't
// miss the triangles they contain.
Expand Down Expand Up @@ -987,6 +1000,13 @@ impl FillTessellator {

#[cfg(debug_assertions)]
fn check_active_edges(&self) {
if self.assume_no_intersection {
// Invariants can break in an uncontrollable ways if we try to tessellate
// a self-intersecting polygon without intersection checks, so we only
// try to avoid panicking in this case.
return;
}

let mut winding = WindingState::new();
for (idx, edge) in self.active.edges.iter().enumerate() {
winding.update(self.fill_rule, edge.winding);
Expand Down Expand Up @@ -1917,7 +1937,7 @@ impl FillTessellator {
let mut w = winding_number;
tess_log!(self, "Fixing up merge vertex after sort.");
let mut idx = i;
loop {
while idx > 0 {
// Roll back previous edge winding and swap.
w -= self.active.edges[idx - 1].winding;
self.active.edges.swap(idx, idx - 1);
Expand All @@ -1938,12 +1958,14 @@ impl FillTessellator {

self.sort_active_edges();

debug_assert!(self
.active
.edges
.first()
.map(|e| !e.is_merge)
.unwrap_or(true));
if !self.assume_no_intersection {
debug_assert!(self
.active
.edges
.first()
.map(|e| !e.is_merge)
.unwrap_or(true));
}
// This can only happen if we ignore self-intersections,
// so we are in a pretty broken state already.
// There isn't a fully correct solution for this (other
Expand Down
Loading