{
static constexpr bool has_triangle_base_case = false;
};
```
---
### Explicit vs convex: a U-shape
- Both policies return the same **2 pieces** here: areas 4 and 3
- The difference is the *stopping rule*, not the geometric criterion
- Explicit is useful as an alternative partition, not as a rectangularizer
---
class: nord-light, middle, center
## Part 4 · Implicit cut
---
### ImplicitCutPolicy
.pull-left[
The implicit cut works directly on **stored pairs**:
- corner under test: the implicit vertex (move x first)
- sign test:
$$
-(y_{i+1}-y_i)(x_{i+1}-x_i) \gtrless 0
$$
- cut from that corner to the nearest edge
- recurse first on the **nearest-edge** side, then on the new node
]
.pull-right[
```cpp
auto d = -(p2.y - p1.y) * (p2.x - p1.x);
if (cmp(d)) return vcurr; // reflex corner
```
- No direction-reversal guard: the implicit vertex is always a 90° corner
- Only **implicit** reflex corners can be found
]
---
### Implicit cut: worked example
- L-shape stored as `(0,0) (2,1) (1,2)`
- The implicit corner `(1,1)` is reflex
- Cut horizontally to the nearest edge → **2 rectangles**
- Areas: **2 + 1 = 3** ✓
---
### Implicit cut: the blind spot
- A reflex corner can be a **stored** vertex, not an implicit one
- The implicit policy never tests stored corners
- On this area-9 convex piece it returns an **8-area 4-point piece**
(an octagon after expansion, not a rectangle) plus a 1-area rectangle
- Area is preserved, but the output is **not** a rectangle decomposition
---
class: nord-light, middle, center
## Part 5 · Rectangle cut
---
### The corrected pipeline
```cpp
rpolygon_cut_rectangle(P):
C = rpolygon_cut_convex(P) // 1. convex pieces
out = []
for piece in C: // 2. rectangularize
out += cut_convex_into_rectangles(piece)
return out
cut_convex_into_rectangles(piece):
xs = sorted(unique(x-coordinates of piece))
for each slab [xs[k], xs[k+1]]:
ys = y of horizontal edges spanning the slab
emit rectangle [xs[k], xs[k+1]] x [ys[0], ys[1]]
```
- A convex piece is **x-monotone** ⇒ one rectangle per slab
- Slabs are aligned with the piece's own vertices
---
### Slab decomposition of a convex piece
.pull-left[
Take the area-9 convex piece:
- $x$-monotone ⇒ every vertical slab meets it in one interval
- Slab $[3,4]$ → height $0 \ldots 4$
- Slab $[4,5]$ → height $1 \ldots 5$
- Slab $[5,6]$ → height $1 \ldots 2$
Result: **3 rectangles**, areas $4+4+1 = 9$ ✓
]
.pull-right[
]
---
### Rectangle cut: the twelve-point fixture
.pull-left[
]
.pull-right[
.font-sm[
| piece | stored corners | area |
|---|---|---|
| R1 | `(1,-1) → (3,2)` | 6 |
| R2 | `(0,0) → (1,2)` | 2 |
| R3 | `(1,2) → (2,3)` | 1 |
| R4 | `(-1,1) → (0,3)` | 2 |
| R5 | `(3,0) → (4,4)` | 4 |
| R6 | `(4,1) → (5,5)` | 4 |
| R7 | `(5,1) → (6,2)` | 1 |
]
**7 rectangles · area 20** ✓
]
---
### Before vs after
- **Old** `implicit → explicit`: 6 pieces, including a **zero-width** `(0,2)-(0,3)`
and a **5-point, area-14 piece** that is not a rectangle
- **New** `convex → slabs`: 7 proper rectangles, same total area 20
- Area preservation alone would have accepted the old output
---
### Why the old composition failed
1. **Wrong domain** — implicit cut was applied to a possibly non-convex polygon
2. **Blind spot** — stored reflex corners are invisible to the implicit test
3. **No rectangularization** — the explicit policy does not turn convex pieces into rectangles
4. **Degenerate output** — a zero-width 2-point piece slipped through area checks
5. **Weak invariant** — "convex pieces + area sum" does not imply "rectangles"
> The fix is structural: convex cut first, then a slab rectangularization.
---
### Invariants and complexity
**Invariants of a correct rectangle decomposition**
- every piece is stored as **2 points** with both coordinates distinct
- pieces are **disjoint** interiors
- the sum of the piece areas equals the original area
- orientation is preserved: signed piece areas keep the input's sign
**Cost**
- nearest-edge scan is $O(n)$ per cut; recursion depth $O(n)$
- worst case $O(n^2)$ for the cut stage
- slab stage: sort $k$ x-coordinates, scan $k$ stored edges → $O(k^2)$ per convex piece
---
### Tests that would have caught it
```cpp
const auto pieces = rpolygon_cut_rectangle(P, is_anticlockwise);
REQUIRE_EQ(pieces.size(), 7U);
for (const auto& piece : pieces) {
REQUIRE_EQ(piece.size(), 2U); // compressed rectangle
CHECK_NE(piece[0].xcoord(), piece[1].xcoord());
CHECK_NE(piece[0].ycoord(), piece[1].ycoord());
CHECK_GT(RPolygon(piece).signed_area(), 0);
}
CHECK_EQ(total_area, original_area); // necessary, not sufficient
```
- Fixture: `twelve_point_even_index_fixture()`, area 20
- Assert the **output class**, not just the area sum
---
### Takeaways
- The **compressed representation** drives every cut: stored corners vs implicit corners
- **Convex ≠ rectangle**; a second rectangularization stage is mandatory
- **Explicit** is an alternative convex decomposition, not a rectangle algorithm
- **Implicit** only sees implicit reflex corners — a real blind spot
- **Rectangle = convex cut + vertical slabs** (for x-monotone convex pieces)
- Tests must assert **2-point pieces and positive area**, not just area preservation
---
count: false
class: nord-dark, middle, center
# Q & A
### Thank you
`include/recti/rpolygon_cut.hpp` · `source/rpolygon_cut.cpp`