Skip to content
This repository was archived by the owner on Jan 25, 2026. It is now read-only.
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
57 changes: 55 additions & 2 deletions src/topology.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1686,9 +1686,58 @@ impl Default for Contour {
}
}

/// A collection of [`Contour`]s.
/// A collection of [`Contour`]s, representing a set.
///
/// Can be indexed with a [`ContourIdx`].
///
/// A `Contour` represents a set as a hierarchical collection of closed paths, where
/// each path has the set on its left (in a Y-down coordinate system). A very simple
/// set is represented as just a single closed path:
///
/// ```text
/// ╭───<───╮
/// │xxxxxxx│
/// │xxxxxxx│
/// ╰───>───╯
/// ```
///
/// (The `x`s represent the interior of the set, and the arrows show the orientation
/// of the curve.)
///
/// Two disjoint sets are represented as two unrelated closed paths:
///
/// ```text
/// ╭───<───╮
/// │xxxxxxx│
/// │xxxxxxx│ ╭───<───╮
/// ╰───>───╯ │xxxxxxx│
/// │xxxxxxx│
/// ╰───>───╯
/// ```
///
/// The hierarchical structure appears when you have sets with holes: a set with a single
/// hole is represented as a contour (the outer boundary) with a child contour (the inner
/// boundary). Notice how the curves are oriented so that the set is always on the left.
///
/// ```text
/// ╭───<──────╮
/// │xxxxxxxxxx│
/// │xxx╭>─╮xxx│
/// │xxx│ │xxx│
/// │xxx╰─<╯xxx│
/// │xxxxxxxxxx│
/// ╰──────>───╯
/// ```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I do notice how the curves are oriented so that the set is always on the left. This also looks like a familiar illustration of how the nonzero fill rule works (in SVG, for example).

Isn't it the case that nonzero means that inside vs. outside is determined by the relative winding direction of these two curves (clockwise vs. counter-clockwise)? So, in this case, the hole is formed in the center (under nonzero fill rule) because the inner curve moves in the opposite direction (clockwise) of the outer curve (counter-clockwise).

In my understanding, with the evenodd fill rule winding directions don't matter.

Does this mean that the nonzero fill rule is baked in to how these Contours are built?

If so, would it be accurate for me to think of it like this: "Contours returned from binary_op() are guaranteed to follow the nonzero fill rule, and that's why they can be directly translated into SVG paths, where the nonzero fill rule is the default?"

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's basically it. But to be more precise, the contours returned from binary_op always produce winding numbers of either 0 or 1, so it doesn't actually matter which fill rule you use for them.

In the example with a hole, if both of the contours were counter-clockwise then the inner hole would have a winding number of 2 and so it would be unfilled with an evenodd fill rule but filled with a nonzero fill rule. But because our inner contour is clockwise, the inner hole has a winding number of 0.

///
/// A set can have multiple holes (and so a contour can have multiple children),
/// and those holes can contain more parts of the set. So in general, the
/// collection of contours forms a forest.
///
/// Because of the way we organize the contour directions, the set described by
/// our contours has a winding number of 1 and its complement has a winding
/// number of 0. In particular, if you gather up all the contours and put them
/// in an SVG, it won't make a difference whether you fill them with a non-zero
/// or an even-odd fill rule.
#[cfg_attr(test, derive(serde::Serialize))]
#[derive(Clone, Debug, Default)]
pub struct Contours {
Expand Down Expand Up @@ -1728,7 +1777,11 @@ impl Contours {
ret
}

/// Iterates over all of the contours.
/// Iterates over all of the contours, ignoring the hierarchical structure.
///
/// For example, if you're creating an SVG path out of all these contours then
/// you don't need the hierarchical structure: the SVG renderer can figure that
/// out by itself.
pub fn contours(&self) -> impl Iterator<Item = &Contour> + '_ {
self.contours.iter()
}
Expand Down