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
1 change: 1 addition & 0 deletions iOverlay/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/AGENTS.md
20 changes: 20 additions & 0 deletions iOverlay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ For specialized geometry, see [iCurve](https://github.com/iShape-Rust/iCurve) fo
- [Offsetting a Polygon](#offsetting-a-polygon)
- [LineCap](#linecap)
- [LineJoin](#linejoin)
- [Integer Coordinate Limits](#integer-coordinate-limits)
- [FAQ](#faq)
- [License](#license)

Expand Down Expand Up @@ -583,6 +584,25 @@ println!("shapes: {:?}", &shapes);

 

## Integer Coordinate Limits

For an `N`-bit engine, keep each input coordinate within
`-2^(N - 2)..=2^(N - 2) - 1` (inclusive):

| Engine | Minimum x or y | Maximum x or y |
| --- | ---: | ---: |
| `i16` | -16,384 | 16,383 |
| `i32` | -1,073,741,824 | 1,073,741,823 |
| `i64` | -4,611,686,018,427,387,904 | 4,611,686,018,427,387,903 |

These limits leave room for coordinate differences and their products. They apply
to all inputs and solver strategies. Integer APIs do not check them; exceeding
these bounds can cause overflow or incorrect results. Use a wider engine or rescale
larger inputs. See the [range derivation and arithmetic audit](readme/integer_range.md) for details.

For float APIs, the limits apply after conversion. An explicit conservative budget
is `FloatPointAdapter::with_coordinate_bits(rect, I::BITS - 3)`.

## FAQ
### 1. When should I use `FloatOverlay`, `SingleFloatOverlay`, or `FloatOverlayGraph`?

Expand Down
70 changes: 70 additions & 0 deletions iOverlay/readme/integer_range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Integer coordinate range audit

The coordinate interval is `-2^(N - 2)..=2^(N - 2) - 1` for an `N`-bit
engine. Both endpoints are included. Let `D = 2^(N - 1) - 1 = I::MAX`.
Every coordinate difference then has magnitude at most `D`.

This is a bound on coordinate arithmetic for polygon, string, predicate, and
vector overlays. It does not bound allocation sizes, winding counts, or
arithmetic in user-provided edge-data callbacks. Floating-point inputs must
respect the interval **after** conversion; custom scales are the caller's
responsibility.

## Point and vector operations

Point subtraction widens to `I::Wide`. Each product of differences has magnitude
at most `D^2`; a dot product, cross product, or squared distance is bounded by
`2 * D^2`. For all three engines:

```text
2 * D^2 = 2^(2*N - 1) - 2^(N + 1) + 2 < 2^(2*N - 1)
```

Thus these expressions, including negation of cross products, fit in the signed
wide type (`i32`, `i64`, or `i128`). The same calculation covers input collinearity
filtering, segment ordering, hole binding, point queries, and snapping distances.

## Intersection coordinates

`CrossSolver::cross_point` translates endpoints relative to one endpoint. The
translated coordinates and the other segment's direction still have magnitude
at most `D`: subtracting two translated coordinates cancels the common offset.
The determinants `xy_b` and `div` are bounded by `2 * D^2`.

The general intersection numerator multiplies a determinant by a direction
component. It uses `UIntProduct`, with **4*N bits**, rather than `I::Wide`:
its magnitude is at most `2 * D^3 < 2^(3*N - 2)`. The unsigned divisor is
positive and below `2^(2*N - 1)`, satisfying `divide_with_rounding`'s precondition.
The quotient is an intersection displacement and has magnitude at most `D`.

A true segment intersection is within both segments' coordinate bounds.
Rounding or truncating a coordinate relative to an integer endpoint cannot
move it outside those integer bounds. Snapping selects an existing endpoint.
Consequently, every repair iteration preserves the global input coordinate box.

## Area accumulation

Twice the final area of an output contour is at most `2 * D^2`. A partial
shoelace sum has no such bound: a simple spiral can wind around the origin many
times before the return path cancels most of the accumulated area.

Both ordinary and vector contours therefore accumulate area with wrapping
arithmetic. This computes the exact sum modulo `2^(2*N)`; since the final signed
area fits, its final representation is exact. Reducing the coordinate range by
a fixed number of bits would not solve arbitrary partial-sum overflow.

## Boundary coverage

- All engines and solver strategies: inclusive endpoints, one-unit features,
holes, steep edges, collinear overlaps, exact and rounded intersections.
- A simple spiral: vector area filtering at its exact area and one unit above.
- Growing and oversized snapping thresholds, including shift and addition limits.
- Boundary intersection coordinates checked against an independent `i128`
rational calculation, including `i64` intersections using extended products.
- Fragment enclosures checked against exact rational segment heights for steep,
shallow, increasing, and decreasing edges at several grid resolutions.
- Seeded `i16` and `i32` overlays compared with the same input in the `i64` engine.

Adding one more unit to the interval allows a positive difference `D + 1`
that does not fit in `I`, and `2 * (D + 1)^2` does not fit in `I::Wide`.
Larger intervals therefore need a different arithmetic contract.
4 changes: 4 additions & 0 deletions iOverlay/src/core/edge_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ pub struct InputEdge<I: IntNumber, D> {
pub data: D,
}

/// Integer overlay builder with per-edge data.
///
/// Edge endpoints must satisfy the [integer coordinate range](crate::core::integer).
/// Bounds are not checked.
pub struct EdgeOverlay<I: OverlayInt, D: OverlayEdgeData> {
pub solver: Solver,
pub options: IntOverlayOptions<I::WideUInt>,
Expand Down
27 changes: 27 additions & 0 deletions iOverlay/src/core/integer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,30 @@
//! Integer types supported by the overlay engine.
//!
//! # Coordinate range
//!
//! For an `N`-bit engine, keep **every x and y coordinate**, across all inputs to
//! an operation, in the inclusive range `-2^(N - 2)..=2^(N - 2) - 1`:
//!
//! | Engine | Minimum | Maximum |
//! | --- | ---: | ---: |
//! | `i16` | -16,384 | 16,383 |
//! | `i32` | -1,073,741,824 | 1,073,741,823 |
//! | `i64` | -4,611,686,018,427,387,904 | 4,611,686,018,427,387,903 |
//!
//! The maximum coordinate difference is `D = I::MAX`; sums of two products
//! fit in `I::Wide` because `2 * D^2 < 2^(2*N - 1)`. Intersection numerators
//! use extended-width products. Area accumulation must allow partial sums to
//! wrap even though the final contour area fits. Integer input bounds are not
//! checked at runtime.
//!
//! ## Floating-point conversion
//!
//! These limits concern the integer coordinates after conversion, not the
//! original floating-point coordinates. For an explicit conservative bound,
//! use [`FloatPointAdapter::with_coordinate_bits`](i_float::adapter::FloatPointAdapter::with_coordinate_bits)
//! with `coordinate_bits = I::BITS - 3`. This bounds the converted magnitude by
//! `2^(N - 3)` (8,192 for `i16`), with both endpoints included. A custom unchecked
//! scale must respect the integer range too.

use i_float::int::number::int::IntNumber;
use i_key_sort::sort::key::SortKey;
Expand All @@ -18,6 +44,7 @@ mod private {
///
/// This trait is sealed. The supported integer engines are [`i16`], [`i32`],
/// and [`i64`].
/// See the [coordinate range](self) required by the integer APIs.
pub trait OverlayInt: private::OverlayIntSealed {}

impl OverlayInt for i16 {}
Expand Down
3 changes: 3 additions & 0 deletions iOverlay/src/core/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub enum ContourDirection {
}

/// This struct is essential for describing and uploading the geometry or shapes required to construct an `OverlayGraph`. It prepares the necessary data for boolean operations.
///
/// All input coordinates must satisfy the [integer coordinate range](crate::core::integer).
/// Bounds are not checked when adding geometry.
pub struct Overlay<I: OverlayInt> {
pub solver: Solver,
pub options: IntOverlayOptions<I::WideUInt>,
Expand Down
3 changes: 3 additions & 0 deletions iOverlay/src/core/relate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use i_shape::int::shape::{IntContour, IntShape};

/// Overlay structure optimized for spatial predicate evaluation.
///
/// All input coordinates must satisfy the [integer coordinate range](crate::core::integer).
/// Bounds are not checked when adding geometry.
///
/// `PredicateOverlay` provides efficient spatial relationship testing between
/// two polygon sets without computing full boolean operation results. It is
/// designed for cases where you only need to know *whether* shapes intersect,
Expand Down
20 changes: 12 additions & 8 deletions iOverlay/src/core/solver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,54 +21,58 @@ pub enum Strategy {
/// Represents the precision level used by the solver to determine
/// the tolerance for snapping to the nearest edge ends.
///
/// The precision determines a radius calculated as `2^value`,
/// The precision determines a squared radius calculated as `2^value`,
/// where `value` starts at `start` and increases in increments
/// defined by `progression` in each iteration.
/// The exponent is capped at `2 * (I::BITS - 4)` for the selected integer engine,
/// limiting the linear radius to `2^(I::BITS - 4)`.
/// This threshold is compared directly with squared distances; the corresponding
/// linear radius is `sqrt(2^value)`.
///
/// - `start`: The initial exponent value.
/// - `progression`: The step size for incrementing the exponent
/// in each iteration.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Precision {
/// The initial exponent value for the radius calculation.
/// The initial exponent value for the squared-radius calculation.
pub start: usize,
/// The amount by which the exponent increases in each iteration.
pub progression: usize,
}

impl Precision {
/// Absolute precision with no progression.
/// (Radius remains at `2^0 = 1`)
/// (Squared radius remains at `2^0 = 1`)
pub const ABSOLUTE: Precision = Self {
start: 0,
progression: 0,
};

/// High precision, starting at `2^0 = 1` and doubling every loop.
/// High precision, with squared radius starting at `2^0 = 1` and doubling every loop.
pub const HIGH: Precision = Self {
start: 0,
progression: 1,
};

/// Medium-high precision, starting at `2^1 = 2` and doubling every loop.
/// Medium-high precision, with squared radius starting at `2^1 = 2` and doubling every loop.
pub const MEDIUM_HIGH: Precision = Self {
start: 1,
progression: 1,
};

/// Medium precision, starting at `2^0 = 1` and quadrupling every loop.
/// Medium precision, with squared radius starting at `2^0 = 1` and quadrupling every loop.
pub const MEDIUM: Precision = Self {
start: 0,
progression: 2,
};

/// Medium-low precision, starting at `2^2 = 4` and quadrupling every loop.
/// Medium-low precision, with squared radius starting at `2^2 = 4` and quadrupling every loop.
pub const MEDIUM_LOW: Precision = Self {
start: 2,
progression: 2,
};

/// Low precision, starting at `2^2 = 4` and increasing by a factor of 8 every loop.
/// Low precision, with squared radius starting at `2^2 = 4` and increasing by a factor of 8 every loop.
pub const LOW: Precision = Self {
start: 2,
progression: 3,
Expand Down
6 changes: 6 additions & 0 deletions iOverlay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
//! - **Fill Rules**: even-odd, non-zero, positive and negative.
//! - **Data Types**: Supports `i16`/`i32`/`i64` integer APIs and `f32`/`f64` floating-point APIs.
//!
//! ## Integer coordinate limits
//!
//! Integer inputs must stay within `-2^(N - 2)..=2^(N - 2) - 1` for an `N`-bit
//! engine; for example, `-16_384..=16_383` for `i16`. The full storage-type range
//! is not supported. See [coordinate ranges and their derivation](core::integer).
//!
//! ## Simple Example
//! ![Simple Example](https://raw.githubusercontent.com/iShape-Rust/iOverlay/main/readme/example_union.svg)
//! Here's an example of performing a union operation between two polygons:
Expand Down
Loading
Loading