<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nail Sharipov</title>
    <description>The latest articles on DEV Community by Nail Sharipov (@nail_sharipov_5d810d8cf71).</description>
    <link>https://dev.to/nail_sharipov_5d810d8cf71</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2117690%2F886eb41f-cbc6-4bd0-99c1-eee763bd88b5.png</url>
      <title>DEV Community: Nail Sharipov</title>
      <link>https://dev.to/nail_sharipov_5d810d8cf71</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nail_sharipov_5d810d8cf71"/>
    <language>en</language>
    <item>
      <title>Earcut64: Zero-Allocation Triangulation for Tiny Polygons</title>
      <dc:creator>Nail Sharipov</dc:creator>
      <pubDate>Mon, 16 Jun 2025 12:24:41 +0000</pubDate>
      <link>https://dev.to/nail_sharipov_5d810d8cf71/earcut64-zero-allocation-triangulation-for-tiny-polygons-511j</link>
      <guid>https://dev.to/nail_sharipov_5d810d8cf71/earcut64-zero-allocation-triangulation-for-tiny-polygons-511j</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/R3dVTMPa028"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Earcut&lt;/strong&gt; looks like a textbook triangulation algorithm, yet it can outperform more complex methods on small, simple polygons.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By limiting ourselves to ≤ &lt;code&gt;64&lt;/code&gt; vertices we can keep everything on the stack, encode the contour in a single &lt;code&gt;u64&lt;/code&gt; to get maximum performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Earcut at All?
&lt;/h2&gt;

&lt;p&gt;Earcut is almost a teaching example: pick any &lt;strong&gt;non-self-intersecting&lt;/strong&gt; polygon, find an "ear" (three consecutive vertices that form an empty inner triangle), cut it off, repeat.&lt;br&gt;&lt;br&gt;
Simple? Yes. Slow? Sometimes. But on contours with few points Earcut can be the fastest tool in the shed, as my &lt;a href="https://ishape-rust.github.io/iShape-js/triangle/performance/performance.html" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; against &lt;a href="https://github.com/iShape-Rust/iTriangle" rel="noopener noreferrer"&gt;iTriangle&lt;/a&gt; and &lt;a href="https://github.com/mapbox/earcut.hpp" rel="noopener noreferrer"&gt;Mapbox&lt;/a&gt; implementations have shown.&lt;/p&gt;

&lt;p&gt;That observation sparked an idea: &lt;em&gt;what if we crafted a micro-optimized version of Earcut specialised for single-contour shapes with ≤ 64 vertices?&lt;/em&gt; No heap, one bit-mask, maximum speed.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. How Classic Earcut Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Walk through every triple of consecutive vertices.
&lt;/li&gt;
&lt;li&gt;For each triple check:

&lt;ul&gt;
&lt;li&gt;The triangle is oriented CCW.
&lt;/li&gt;
&lt;li&gt;It contains &lt;em&gt;no other vertices&lt;/em&gt; inside.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;If both hold, you found an ear—remove its middle vertex and continue.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Worst-case complexity: &lt;strong&gt;O(N³)&lt;/strong&gt;. In practice most polygons behave better (closer to &lt;strong&gt;O(N²)&lt;/strong&gt;) because ears appear quickly.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. "Bigger-Ear" Variant
&lt;/h2&gt;

&lt;p&gt;Instead of cutting one triangle at a time, we try to grow a larger convex "ear" that can be triangulated in bulk.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z2t1rh3frvz1ivpfndu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4z2t1rh3frvz1ivpfndu.png" alt="Bigger-Ear"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why convex?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triangulating a convex polygon is trivial.
&lt;/li&gt;
&lt;li&gt;Checking "is point inside?" is cheaper.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39xsz7ef6o51tn6eky3p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39xsz7ef6o51tn6eky3p.png" alt="Inner points"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Empty-check methods
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Even-Odd ray&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-product sign&lt;/strong&gt; — faster because you can abort on the first sign flip.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih41d6lhnxsfa6bm7t0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fih41d6lhnxsfa6bm7t0s.png" alt="Cross-product sign"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All cross-products same sign - &lt;em&gt;point is inside&lt;/em&gt;&lt;br&gt;
Sign flips - &lt;em&gt;point is outside&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Finding the Maximum Ear
&lt;/h2&gt;

&lt;p&gt;If the convex ear still hides internal points, rewind from the tail until they disappear.&lt;br&gt;
Key trick: keep the first vertex fixed—the main sweep will revisit it anyway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k3rp9u7dut2x34q0e6q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k3rp9u7dut2x34q0e6q.png" alt="Maximum Ear"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To pick the "nearest " internal point:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build vectors from the first vertex to every candidate.&lt;/li&gt;
&lt;li&gt;Compare them pair-wise via cross-product (&lt;strong&gt;arccos&lt;/strong&gt; is both slow and imprecise).&lt;/li&gt;
&lt;li&gt;The vector with the smallest positive angle to the first edge defines the limit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cut everything up to that limit and triangulate locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxxomctsmazs0yaat6ck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcxxomctsmazs0yaat6ck.png" alt="Nearest vector"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Speed Hacks
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Bounding-box filter&lt;/strong&gt; — drop points obviously outside the ear’s AABB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early rejection&lt;/strong&gt; — if the first triangle is already dirty, skip the ear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap of nearest points&lt;/strong&gt; — we only care about the closest violators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental testing&lt;/strong&gt; — check the next point starting from the last valid triangle.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Optimized pass — step‑by‑step
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxb3s0x3lj5napbozjw2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxb3s0x3lj5napbozjw2j.png" alt="Optimized algorithm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above illustrates how the incremental filter + heap strategy works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Gray points&lt;/strong&gt; — vertices discarded by the ear’s axis‑aligned bounding box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orange points&lt;/strong&gt; — remaining candidates sorted by angle to the first edge(red).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Green segments&lt;/strong&gt; — final ear triangles kept in the mesh.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Candidate 1&lt;/strong&gt;: its vector falls inside &lt;strong&gt;triangle 4&lt;/strong&gt;, yet the point itself lies outside the ear - keep scanning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Candidate 2&lt;/strong&gt;: tested starting from &lt;strong&gt;triangle 4&lt;/strong&gt;, ends up in &lt;strong&gt;triangle 5&lt;/strong&gt; and is still outside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Candidate 3&lt;/strong&gt;: tested from &lt;strong&gt;triangle 5&lt;/strong&gt;, reaches &lt;strong&gt;triangle 6&lt;/strong&gt; and finally lands inside — search stops.&lt;/p&gt;

&lt;p&gt;Result: the ear is triangulated using &lt;strong&gt;triangles 1 – 5&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  No internal point found?
&lt;/h3&gt;

&lt;p&gt;Two scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All candidates examined&lt;/strong&gt; - no obstructing vertices; cut the ear entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap overflow&lt;/strong&gt; - only part of the candidates checked; seen points are outside but we lack guarantees for the rest. Cut only up to the last verified triangle — safe but maybe sub‑optimal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This compromise preserves speed without sacrificing robustness. Experiments showed that a heap of 1–5 nearest points offers the best trade-off; larger heaps slow things down without benefit.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Earcut64: Stack-Only, Bit-Mask Navigation
&lt;/h2&gt;

&lt;p&gt;For contours of ≤ 64 points, store presence bits in a single &lt;code&gt;u64&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy3rrp79zycmwve2rbwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxy3rrp79zycmwve2rbwi.png" alt="0b110111"&gt;&lt;/a&gt;&lt;br&gt;
&lt;code&gt;0b110111&lt;/code&gt; - &lt;em&gt;vertex 3 was removed&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bit math for Navigation:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;BitMaskNav&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[inline(always)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ones_index_to_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;debug_assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nn"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;MAX&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;shift&lt;/span&gt;   &lt;span class="c1"&gt;// 1-bits from index (inclusive) to the end&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/// Finds the next available bit (index of vertex) after `after`, wrapping if needed&lt;/span&gt;
    &lt;span class="nd"&gt;#[inline(always)]&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;next_wrapped_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nd"&gt;debug_assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;63&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;front&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nn"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;ones_index_to_end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;front&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="nf"&gt;.trailing_zeros&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="nf"&gt;.trailing_zeros&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Filtering internal points&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ear_indices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cm"&gt;/*
available   – all remaining vertices
ear_indices – vertices currently forming the ear
candidates  – potential internal points
*/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Zero allocations, zero copies&lt;/strong&gt; - everything lives in registers or on the stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Benchmarks
&lt;/h2&gt;

&lt;p&gt;Below are average runtimes (µs) from the first two public tests on my &lt;a href="https://ishape-rust.github.io/iShape-js/triangle/performance/performance.html" rel="noopener noreferrer"&gt;benchmark page&lt;/a&gt;. Hardware: 3 GHz 6‑core Intel i5‑8500.&lt;/p&gt;

&lt;h3&gt;
  
  
  Star
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yom5kmt5888m1slfbow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yom5kmt5888m1slfbow.png" alt="Star"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Earcut64&lt;/th&gt;
&lt;th&gt;Monotone&lt;/th&gt;
&lt;th&gt;Earcut Rust&lt;/th&gt;
&lt;th&gt;Earcut C++&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.28&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;0.73&lt;/td&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0.64&lt;/td&gt;
&lt;td&gt;1.6&lt;/td&gt;
&lt;td&gt;1.23&lt;/td&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;1.61&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;2.6&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;4.45&lt;/td&gt;
&lt;td&gt;8.35&lt;/td&gt;
&lt;td&gt;5.6&lt;/td&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;17.8&lt;/td&gt;
&lt;td&gt;12.6&lt;/td&gt;
&lt;td&gt;8.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;37.5&lt;/td&gt;
&lt;td&gt;29.1&lt;/td&gt;
&lt;td&gt;22.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;79.7&lt;/td&gt;
&lt;td&gt;80.7&lt;/td&gt;
&lt;td&gt;72.7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;172&lt;/td&gt;
&lt;td&gt;259&lt;/td&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;388&lt;/td&gt;
&lt;td&gt;736&lt;/td&gt;
&lt;td&gt;641&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;898&lt;/td&gt;
&lt;td&gt;3158&lt;/td&gt;
&lt;td&gt;2804&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1824&lt;/td&gt;
&lt;td&gt;13435&lt;/td&gt;
&lt;td&gt;11479&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16384&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;3846&lt;/td&gt;
&lt;td&gt;51688&lt;/td&gt;
&lt;td&gt;44017&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Spiral
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdxeg6358q1421smknk8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdxeg6358q1421smknk8.png" alt="Spiral"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Earcut64&lt;/th&gt;
&lt;th&gt;Monotone&lt;/th&gt;
&lt;th&gt;Earcut Rust&lt;/th&gt;
&lt;th&gt;Earcut C++&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;0.35&lt;/td&gt;
&lt;td&gt;0.7&lt;/td&gt;
&lt;td&gt;0.77&lt;/td&gt;
&lt;td&gt;0.42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;1.66&lt;/td&gt;
&lt;td&gt;0.77&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;4.2&lt;/td&gt;
&lt;td&gt;3.0&lt;/td&gt;
&lt;td&gt;6.25&lt;/td&gt;
&lt;td&gt;3.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;16.1&lt;/td&gt;
&lt;td&gt;6.2&lt;/td&gt;
&lt;td&gt;18.6&lt;/td&gt;
&lt;td&gt;19.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;12.8&lt;/td&gt;
&lt;td&gt;71.6&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;256&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;26.7&lt;/td&gt;
&lt;td&gt;295&lt;/td&gt;
&lt;td&gt;306&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;55.5&lt;/td&gt;
&lt;td&gt;1230&lt;/td&gt;
&lt;td&gt;1438&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;5301&lt;/td&gt;
&lt;td&gt;7595&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;279&lt;/td&gt;
&lt;td&gt;22682&lt;/td&gt;
&lt;td&gt;50140&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4096&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;685&lt;/td&gt;
&lt;td&gt;96933&lt;/td&gt;
&lt;td&gt;376060&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8192&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1435&lt;/td&gt;
&lt;td&gt;416943&lt;/td&gt;
&lt;td&gt;3.7kk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16384&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;3080&lt;/td&gt;
&lt;td&gt;1812147&lt;/td&gt;
&lt;td&gt;43.4kk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: &lt;strong&gt;Earcut64&lt;/strong&gt; and &lt;strong&gt;Monotone&lt;/strong&gt; in these tables are both algorithms from my open‑source Rust library &lt;a href="https://github.com/iShape-Rust/iTriangle" rel="noopener noreferrer"&gt;iTriangle&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark takeaway
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ear-based&lt;/strong&gt; triangulators shine on simple shapes and quickly fall off on pathological ones — exactly what theory predicts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mapbox Earcut (C++)&lt;/strong&gt; is the fastest in the 16 – 512‑point range for simple contours, which makes sense given its map‑rendering focus.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Earcut64&lt;/strong&gt; holds its own and even outperforms the professional library in several small‑polygon cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;pure‑Rust Earcut&lt;/strong&gt; port stays surprisingly strong on very complex contours; within the Earcut family.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Why It Matters — and What’s Next?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Earcut64&lt;/strong&gt; it’s fully stack-allocated, zero-copy, and designed for maximum inlining by the compiler.&lt;/p&gt;

&lt;p&gt;That makes it a perfect candidate for &lt;strong&gt;GPU&lt;/strong&gt; porting.&lt;/p&gt;

&lt;p&gt;But more importantly — it's part of &lt;strong&gt;iTriangle&lt;/strong&gt;, an integer-based triangulation library validated against billions of randomized tests, from clean stars to extreme degeneracies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iTriangle&lt;/strong&gt; guarantees watertight, non-overlapping triangle meshes — no crashes, no invalid output, no surprises and it's a perfect solution for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;embedded systems&lt;/li&gt;
&lt;li&gt;real-time rendering&lt;/li&gt;
&lt;li&gt;EDA/CAD/GIS pipelines&lt;/li&gt;
&lt;li&gt;and WebAssembly-based tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Try it today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iShape-Rust/iTriangle" rel="noopener noreferrer"&gt;iTriangle on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ishape-rust.github.io/iShape-js/triangle/triangulation.html" rel="noopener noreferrer"&gt;Live Triangulation Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ishape-rust.github.io/iShape-js/triangle/performance/performance.html" rel="noopener noreferrer"&gt;Full Performance Benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💬 &lt;em&gt;Got questions or feedback? Drop a comment below or open an issue.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>rust</category>
      <category>showdev</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Monotone Triangulation. Practical Advice.</title>
      <dc:creator>Nail Sharipov</dc:creator>
      <pubDate>Sat, 10 May 2025 13:11:37 +0000</pubDate>
      <link>https://dev.to/nail_sharipov_5d810d8cf71/monotone-triangulation-practical-advice-1k4j</link>
      <guid>https://dev.to/nail_sharipov_5d810d8cf71/monotone-triangulation-practical-advice-1k4j</guid>
      <description>&lt;p&gt;It all started innocently enough.&lt;/p&gt;

&lt;p&gt;Back in 2009, I just wanted to port Earcut to Flash for a mini-game I was working on. And honestly? It worked — for a while. But over time, I realized that simple solutions tend to fall apart the moment you try to push them to their limits.&lt;/p&gt;

&lt;p&gt;That’s when I dove into the theory - digging through research papers, YouTube tutorials, and whatever else I could find. One of the most helpful resources was a book by A.V. Skvortsov. Eventually, I landed on the classic approach: decomposing a polygon into monotone pieces. It seemed obvious. And oh - I hit every possible wall while trying to make it work.&lt;/p&gt;

&lt;p&gt;The first big insight? Replace &lt;code&gt;float&lt;/code&gt; with &lt;code&gt;int&lt;/code&gt;. That alone got me almost there. But the algorithm was still fragile. Really fragile. Self-intersections, collinear edges, holes touching the outer contour, degenerate points — any of these could break it at any moment. It was functional, but I didn’t like it.&lt;/p&gt;

&lt;p&gt;Years passed. I wrote &lt;a href="https://github.com/iShape-Rust/iOverlay" rel="noopener noreferrer"&gt;iOverlay&lt;/a&gt;, a Boolean operations library that can clean up self-intersections and prepare input data properly. And with that tool in hand, I went back to my old triangulator — this time, with the intent to rebuild it from the ground up.&lt;/p&gt;

&lt;p&gt;Everything went smoother. I knew what to expect. And once you can trust your input data, it's like a rainbow lights up in the sky.&lt;/p&gt;

&lt;p&gt;Eventually, I realized that monotone partitioning is just an abstraction — and I could drop it entirely. I also polished the Delaunay condition until it shined.&lt;/p&gt;

&lt;p&gt;That’s how my custom sweep-line triangulation was born. It's not only fast and simple — it’s incredibly stable. I’ve run over a billion randomized tests. And the output? Bit-for-bit consistent.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/8Nvh3axkPdQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Let’s Get to It
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, input format is critical for this kind of algorithm. So let’s lock down the strict requirements right away:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No self-intersections&lt;/li&gt;
&lt;li&gt;Contours may only touch each other at a single point&lt;/li&gt;
&lt;li&gt;Outer contours must be counter-clockwise&lt;/li&gt;
&lt;li&gt;Holes must be clockwise&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are non-negotiable. If your input doesn’t follow these rules, fix it first - &lt;strong&gt;iOverlay&lt;/strong&gt; can help with that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Pick a Sweep Direction
&lt;/h2&gt;

&lt;p&gt;Since this is a sweep-line algorithm, the first thing we need is to decide the sweep direction. I prefer a left-to-right sweep - moving the line along the X-axis.&lt;/p&gt;

&lt;p&gt;We sort all vertices based on their coordinates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First by &lt;code&gt;x&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;x&lt;/code&gt; is the same, then by &lt;code&gt;y&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Edge Case: Touching Points
&lt;/h3&gt;

&lt;p&gt;One subtle problem arises when multiple contours converge at a single point - say, an outer contour and one or more holes. There can be an infinite number of variations, but they all have something in common:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of edges at such a point is always even,&lt;/li&gt;
&lt;li&gt;If you walk around the point, the edges will alternate: &lt;em&gt;incoming / outgoing / incoming / outgoing...&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy64to7xpnl373ug6coah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy64to7xpnl373ug6coah.png" alt="Touch cases"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Vertex Structure
&lt;/h2&gt;

&lt;p&gt;Each vertex needs to "know" its immediate neighbors along the contour. So we store both &lt;code&gt;prev&lt;/code&gt; and &lt;code&gt;next&lt;/code&gt; references.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct ChainVertex {
    index: usize,     // ID of the logical point
    this: Point,      // Coordinates of the current vertex
    next: Point,      // Next point in the contour
    prev: Point,      // Previous point in the contour
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;index&lt;/code&gt;: the unique ID for the logical point. Identical points across contours (e.g., touching corners) share the same index.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;this&lt;/code&gt;: coordinates of the current vertex.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prev&lt;/code&gt; / &lt;code&gt;next&lt;/code&gt;: neighbor vertices in the same contour (either outer or hole).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: Vertex Classification
&lt;/h2&gt;

&lt;p&gt;Now comes a key part: classifying each vertex.&lt;br&gt;
Sweep-line algorithms rely heavily on this step to decide how each vertex should be handled.&lt;/p&gt;

&lt;p&gt;Classification depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The internal angle at the vertex&lt;/li&gt;
&lt;li&gt;The relative vertical position of neighbors&lt;/li&gt;
&lt;li&gt;The winding direction of the contour&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the all types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start&lt;/strong&gt; - The beginning of a monotone polygon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End&lt;/strong&gt; - The end of a monotone polygon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split&lt;/strong&gt; - A vertex that splits the monotone polygon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge&lt;/strong&gt; - A vertex where two monotone polygons merge back together&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join&lt;/strong&gt; - A neutral vertex — equal to prev or next&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjftlr4jsye9ghqibdfu4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjftlr4jsye9ghqibdfu4.png" alt="Vertex classification"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4: Sweep and On-the-Fly Triangulation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Tracking Active Sections
&lt;/h3&gt;

&lt;p&gt;We represent a monotone polygon as a section, which can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;just a point (newly started polygon)&lt;/li&gt;
&lt;li&gt;a chain of edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each section has a support edge (drawn in blue), used as a key in a balanced tree. This edge is either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the next contour edge, or&lt;/li&gt;
&lt;li&gt;helper edge (bridge between polygon parts)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Section structure in code:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;enum Content {
    Point(IndexPoint),
    Edges(Vec&amp;lt;TriangleEdge&amp;gt;),
}

struct Section {
    sort: VSegment,     // Sorting key in the tree
    content: Content,
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We keep all sections in a balanced tree ordered vertically by their support edge. I’ve already explained how to sort vertical segments in the &lt;a href="https://dev.to/nail_sharipov_5d810d8cf71/2d-polygon-boolean-operations-4df7"&gt;article&lt;/a&gt;. I won’t repeat it here.&lt;/p&gt;

&lt;p&gt;Every time we pop a vertex from the sorted list, we must decide:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Which section does it belong to?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To answer, we search the tree for the closest section below the vertex.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example: Sections A, B, and C are active. P belongs to B.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4tt8ra6ajfgbqf4u1u3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4tt8ra6ajfgbqf4u1u3.png" alt="Find a right section"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Triangle Construction
&lt;/h3&gt;

&lt;p&gt;Once we find the section, we try to form new triangles. Sections keep their edge chains in counter-clockwise order. For each edge, we test if a new triangle (with the current point) forms a counter-clockwise triangle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if yes, we add the triangle&lt;/li&gt;
&lt;li&gt;then update the edge chain&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Example: Section has edges AC and CD. P forms triangle CDP, but not ACP. The new section contains edges AC and CP, with PE as a new support edge.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jni9hcnoakt8199n7w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm8jni9hcnoakt8199n7w.png" alt="Section process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Edge structure which also retain triangle info:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct TriangleEdge {
    a: IndexPoint,
    b: IndexPoint,
    kind: EdgeType,  // needed to find triangle neighbors
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;We will collect our triangles in a structure like:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;struct Triangle {
    vertices: [IndexPoint; 3],
    neighbors: [usize; 3],
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each triangle is stored with its vertices in counter-clockwise order. We also track neighboring triangles — one per edge.&lt;br&gt;
We store neighbors by index, in the slot opposite to the corresponding edge.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example: Triangle ABC has neighbors [2, None, 1]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- neighbor across edge (A)BC → triangle 2&lt;/em&gt;&lt;br&gt;
&lt;em&gt;- neighbor across edge (B)CA → none&lt;/em&gt;&lt;br&gt;
&lt;em&gt;- neighbor across edge (C)AB → triangle 0&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41y0ige9fchae0hvkuay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41y0ige9fchae0hvkuay.png" alt="Triangle"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phantom edges
&lt;/h3&gt;

&lt;p&gt;Sometimes, while building triangles, an internal edge is added before it has a neighbor.&lt;br&gt;
We call this a phantom edge — it temporarily "hangs" in the air.&lt;/p&gt;

&lt;p&gt;On the first visit, such an edge is converted into a normal internal edge by connecting it to the new triangle.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example. Edge 3–5 was added as a phantom.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lfy27td3z0zdk3846t0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6lfy27td3z0zdk3846t0.png" alt="Phantom edge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 (optional): Introduction to Delaunay
&lt;/h2&gt;

&lt;p&gt;At this point, we already have a valid triangulation. But if we want to improve triangle quality, we can enforce the Delaunay condition.&lt;/p&gt;

&lt;p&gt;The Delaunay condition ensures that no vertex lies inside the circumcircle of any triangle.&lt;/p&gt;

&lt;p&gt;The process is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;for each edge shared by two adjacent triangles, we check the Delaunay condition.&lt;/li&gt;
&lt;li&gt;if the condition is violated, we remove the shared edge.&lt;/li&gt;
&lt;li&gt;we then re-triangulate the four involved points by connecting the two opposite vertices (edge flip).&lt;/li&gt;
&lt;li&gt;this process is repeated until no violations remain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t go into the geometric predicate here — I’ve already covered it in a dedicated &lt;a href="https://ishape-rust.github.io/iShape-js/triangle/delaunay.html" rel="noopener noreferrer"&gt;article with animations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example Non-Delaunay triangles must be flipped into Delaunay triangles&lt;/em&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjbotdyietv3pkbt7gae.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjbotdyietv3pkbt7gae.png" alt="Non-Delaunay triangles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does it work?
&lt;/h3&gt;

&lt;p&gt;Each flip reduces the largest (often obtuse) angle between the triangle pair.&lt;br&gt;
This makes the process monotonically convergent — it’s guaranteed to terminate.&lt;/p&gt;

&lt;p&gt;In floating-point implementations, this is where bugs can appear.&lt;br&gt;
Due to rounding errors, some flips can alternate endlessly. This is a classic issue, often referred to as the "regular hexagon problem".&lt;/p&gt;

&lt;p&gt;But with &lt;code&gt;int&lt;/code&gt; or fixed-point logic, the situation improves dramatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Delaunay check becomes strict and exact&lt;/li&gt;
&lt;li&gt;all operations are deterministic&lt;/li&gt;
&lt;li&gt;no infinite loops — ever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the power of integer-based geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'll Take It
&lt;/h2&gt;

&lt;p&gt;If you’re interested in trying this in action, check out &lt;a href="https://github.com/iShape-Rust/iTriangle" rel="noopener noreferrer"&gt;iTriangle&lt;/a&gt; — the Rust library where all of this comes together.&lt;/p&gt;

&lt;p&gt;Beyond what I’ve covered here, it includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tessellation&lt;/li&gt;
&lt;li&gt;Convex polygon partitioning&lt;/li&gt;
&lt;li&gt;Centroid construction&lt;/li&gt;
&lt;li&gt;Steiner points&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can experiment with interactive demos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ishape-rust.github.io/iShape-js/triangle/triangulation.html" rel="noopener noreferrer"&gt;Triangulation Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ishape-rust.github.io/iShape-js/triangle/tessellation.html" rel="noopener noreferrer"&gt;Tessellation Demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And if you're curious about performance, take a look at the &lt;a href="https://ishape-rust.github.io/iShape-js/triangle/performance/performance.html" rel="noopener noreferrer"&gt;benchmarks&lt;/a&gt; results — compared against other popular triangulation libraries.&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>rust</category>
      <category>webassembly</category>
      <category>opensource</category>
    </item>
    <item>
      <title>2D Polygon Boolean Operations</title>
      <dc:creator>Nail Sharipov</dc:creator>
      <pubDate>Tue, 24 Sep 2024 10:27:26 +0000</pubDate>
      <link>https://dev.to/nail_sharipov_5d810d8cf71/2d-polygon-boolean-operations-4df7</link>
      <guid>https://dev.to/nail_sharipov_5d810d8cf71/2d-polygon-boolean-operations-4df7</guid>
      <description>&lt;p&gt;Boolean operations on 2D polygons — union, intersection, difference, and XOR — are crucial for applications in computer graphics, CAD, and GIS. These operations allow us to manipulate complex shapes, but the challenge is implementing them efficiently while maintaining stability and accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Arithmetic
&lt;/h2&gt;

&lt;p&gt;Selecting the right arithmetic is key to balancing performance and precision in geometry algorithms. After considering the pros and cons of floating-point and fixed-point arithmetic, I opted for &lt;code&gt;i32&lt;/code&gt; for point coordinates. Here's why:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixed-Point (i32) Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No rounding errors, ensuring consistent and exact values.&lt;/li&gt;
&lt;li&gt;Simplifies comparisons like determining if a point lies on a line or if two lines are parallel.&lt;/li&gt;
&lt;li&gt;Results are deterministic across platforms, making debugging easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;i32&lt;/code&gt; strikes a balance between memory efficiency and precision, while still allowing dot and cross products to fit comfortably into &lt;code&gt;i64&lt;/code&gt;, avoiding overflow concerns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Imagine we have two bodies: &lt;strong&gt;A&lt;/strong&gt; (red) and &lt;strong&gt;B&lt;/strong&gt; (blue). Each body is defined by a group of contours - a list of successively connected vertices. These contours are always closed and can allow self-intersections.&lt;/p&gt;

&lt;p&gt;Our goal is to compute a new body &lt;strong&gt;C&lt;/strong&gt; (green), which represents one of the following Boolean combinations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Union&lt;/strong&gt;: C = A ∪ B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intersection&lt;/strong&gt;: C = A ∩ B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exclusion&lt;/strong&gt;: C = A ⊕ B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difference&lt;/strong&gt;: C = A - B or B - A&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06bn4nru4ojlewfmss16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F06bn4nru4ojlewfmss16.png" alt="Boolean operations" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Overlay Graph
&lt;/h2&gt;

&lt;p&gt;Let's start by manipulating bodies &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt; to create an &lt;strong&gt;overlay graph&lt;/strong&gt; that will allow us to perform Boolean operations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5mkwteglhwp2bp4x8t5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5mkwteglhwp2bp4x8t5.png" alt="A,B overlay" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The steps to construct this graph are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Split segments:&lt;/strong&gt;&lt;br&gt;
Break down all the segments of both bodies A and B so that there are no self-intersections. This ensures each contour is composed of non-intersecting segments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tag segments with metadata:&lt;/strong&gt;&lt;br&gt;
For each segment, store additional information indicating whether it belongs to body &lt;strong&gt;A&lt;/strong&gt;, body &lt;strong&gt;B&lt;/strong&gt;, or both, and record which side of the segment is part of the body.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combine everything into a graph structure:&lt;/strong&gt;&lt;br&gt;
This graph consists of &lt;strong&gt;nodes&lt;/strong&gt; and &lt;strong&gt;links&lt;/strong&gt;. Each node represents a point (an intersection or endpoint), and each link represents a segment between two points, along with metadata about which body the segment belongs to.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;OverlayGraph&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// A list of nodes (points) in the graph&lt;/span&gt;
    &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Link&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;      &lt;span class="c1"&gt;// A list of links (edges) connecting the nodes&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   &lt;span class="c1"&gt;// Indices pointing to links connected to this node&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Link&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IdPoint&lt;/span&gt;            &lt;span class="c1"&gt;// The start point of the segment&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IdPoint&lt;/span&gt;            &lt;span class="c1"&gt;// The end point of the segment&lt;/span&gt;
    &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SegmentFill&lt;/span&gt;     &lt;span class="c1"&gt;// Metadata about which sides belong to body A or B&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;SegmentFill&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nodes&lt;/strong&gt; represent the points of intersection or the endpoints of segments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Links&lt;/strong&gt; represent the segments themselves, connecting two nodes and carrying information about whether they belong to &lt;strong&gt;A&lt;/strong&gt;, &lt;strong&gt;B&lt;/strong&gt;, or both.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By organizing the segments and their metadata into an overlay graph, we can now efficiently &lt;strong&gt;extract contours&lt;/strong&gt; based on Boolean filters (union, intersection, difference, etc.).&lt;/p&gt;

&lt;p&gt;You can explore this concept with an example in the &lt;a href="https://ishape-rust.github.io/iShape-js/overlay/shapes_editor.html" rel="noopener noreferrer"&gt;online editor&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Boolean Filter
&lt;/h2&gt;

&lt;p&gt;Now that we've constructed the &lt;strong&gt;overlay graph&lt;/strong&gt;, let's see how different Boolean operations are applied to bodies &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt;. We will extract the resulting body &lt;strong&gt;C&lt;/strong&gt; for each operation based on segment properties within the &lt;strong&gt;overlay graph&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Difference, C = A - B
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The resulting segments of &lt;strong&gt;C&lt;/strong&gt; must not be inside body &lt;strong&gt;B&lt;/strong&gt; and must belong to body &lt;strong&gt;A&lt;/strong&gt; on one side.&lt;/li&gt;
&lt;li&gt;The side associated solely with body &lt;strong&gt;A&lt;/strong&gt; will represent the inner part of the resulting shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop09qrke41j6qxcza14t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fop09qrke41j6qxcza14t.png" alt="C = A - B" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Difference, C = B - A
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The resulting segments of &lt;strong&gt;C&lt;/strong&gt; must not be inside body &lt;strong&gt;A&lt;/strong&gt; and must belong to body &lt;strong&gt;B&lt;/strong&gt; on one side.&lt;/li&gt;
&lt;li&gt;The side associated solely with body &lt;strong&gt;B&lt;/strong&gt; will represent the inner part of the resulting shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8h9blm4jogxabh21zs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftd8h9blm4jogxabh21zs.png" alt="C = B - A" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Union, C = A or B
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The resulting segments of &lt;strong&gt;C&lt;/strong&gt; must belong to either body &lt;strong&gt;A&lt;/strong&gt; or body &lt;strong&gt;B&lt;/strong&gt;, or to both.&lt;/li&gt;
&lt;li&gt;The opposite side of each segment must not belong to anybody.&lt;/li&gt;
&lt;li&gt;The side associated with one of the bodies will represent the inner part of the resulting shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ljxnml1caliz02nhdtp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ljxnml1caliz02nhdtp.png" alt="C = A or B" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Intersection, C = A and B
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The resulting segments of &lt;strong&gt;C&lt;/strong&gt; must belong to both bodies &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The opposite side of each segment must not belong to both bodies simultaneously.&lt;/li&gt;
&lt;li&gt;The side associated with both bodies &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt; will represent the inner part of the resulting shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F652dz3zfjyji2b7eafcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F652dz3zfjyji2b7eafcu.png" alt="C = A and B" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Exclusion, C = A xor B
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The resulting segments of &lt;strong&gt;C&lt;/strong&gt; must belong to either body &lt;strong&gt;A&lt;/strong&gt; or body &lt;strong&gt;B&lt;/strong&gt;, but not to both simultaneously.&lt;/li&gt;
&lt;li&gt;The opposite side of each segment must either belong to both bodies or to neither.&lt;/li&gt;
&lt;li&gt;The side associated with one of the bodies (&lt;strong&gt;A&lt;/strong&gt; or &lt;strong&gt;B&lt;/strong&gt;) will represent the inner part of the resulting shape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F337jihduw8wojqdsksy8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F337jihduw8wojqdsksy8.png" alt="C = A xor B" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Extract Shapes
&lt;/h2&gt;

&lt;p&gt;Once we apply the desired Boolean filter to the &lt;strong&gt;Overlay Graph&lt;/strong&gt;, we can begin &lt;strong&gt;extracting contours&lt;/strong&gt; for the resulting shape.&lt;/p&gt;
&lt;h3&gt;
  
  
  Building a Contour
&lt;/h3&gt;

&lt;p&gt;The algorithm follows a systematic process to build both outer and inner contours from the graph:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with the leftmost node:&lt;/strong&gt;&lt;br&gt;
The algorithm begins by selecting the &lt;strong&gt;leftmost node&lt;/strong&gt; in the graph. From there, it looks for the &lt;strong&gt;topmost segment&lt;/strong&gt; connected to this node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Traverse along segments:&lt;/strong&gt;&lt;br&gt;
The algorithm proceeds by traversing the selected segment to the next node. At each node, the next segment is selected by rotating around the current node in a &lt;strong&gt;counterclockwise direction&lt;/strong&gt;, choosing the &lt;strong&gt;nearest segment&lt;/strong&gt; that hasn't been visited yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mark segments as visited:&lt;/strong&gt;&lt;br&gt;
To prevent revisiting segments, each one is marked as &lt;strong&gt;visited&lt;/strong&gt; when traversed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complete the contour:&lt;/strong&gt;&lt;br&gt;
This process continues until a full loop is formed, completing the contour. Depending on the orientation of the segments, the contour will be classified as either &lt;strong&gt;outer&lt;/strong&gt; or &lt;strong&gt;inner&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Outer contours&lt;/strong&gt; are extracted in a &lt;strong&gt;clockwise&lt;/strong&gt; direction.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnczawj9mm4zhluiazlmv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnczawj9mm4zhluiazlmv.png" alt="Outer contour" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inner contours&lt;/strong&gt; are extracted in a &lt;strong&gt;counterclockwise&lt;/strong&gt; direction.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx7ahqjuv4pd0yl8flj3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcx7ahqjuv4pd0yl8flj3.png" alt="Inner contour" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Define Contour
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;shape&lt;/strong&gt; is defined as a group of contours, with the first contour always being an &lt;strong&gt;outer contour&lt;/strong&gt;. Any subsequent contours within the shape are classified as &lt;strong&gt;inner contours&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93k001u1oo2w0pbygvrr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93k001u1oo2w0pbygvrr.png" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To define a contour, the algorithm begins by identifying the leftmost and topmost segment in the contour. The classification of the contour is determined as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the left-top side of the segment is classified as the &lt;strong&gt;outer&lt;/strong&gt; side, then the contour is an &lt;strong&gt;outer&lt;/strong&gt; contour.&lt;/li&gt;
&lt;li&gt;If the left-top side of the segment is classified as the &lt;strong&gt;inner&lt;/strong&gt; side, then the contour is an &lt;strong&gt;inner&lt;/strong&gt; contour.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This method ensures each contour is correctly classified based on its orientation in 2D space.&lt;/p&gt;
&lt;h3&gt;
  
  
  Define Shape
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm6xog92bohp8klthccv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flm6xog92bohp8klthccv.png" alt="Define a shape" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A shape is defined as a group of contours, where the first contour is always an &lt;strong&gt;outer&lt;/strong&gt; contour, and the subsequent contours (if any) are &lt;strong&gt;inner&lt;/strong&gt; contours.&lt;/p&gt;
&lt;h3&gt;
  
  
  Matching Contours
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c1k8t7h8iznhtlgokps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5c1k8t7h8iznhtlgokps.png" alt="Matching contours" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To match &lt;strong&gt;inner contours&lt;/strong&gt; to their corresponding &lt;strong&gt;outer contours&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Draw a line &lt;strong&gt;downward&lt;/strong&gt; from any point on the &lt;strong&gt;inner&lt;/strong&gt; (&lt;strong&gt;target&lt;/strong&gt;) contour.&lt;/li&gt;
&lt;li&gt;Identify the first segment encountered along the line that does not belong to the &lt;strong&gt;target&lt;/strong&gt; contour.&lt;/li&gt;
&lt;li&gt;If the segment belongs to an &lt;strong&gt;outer&lt;/strong&gt; contour, that contour is the container for the &lt;strong&gt;target&lt;/strong&gt; contour.&lt;/li&gt;
&lt;li&gt;If the segment belongs to another &lt;strong&gt;inner&lt;/strong&gt; contour, the container of that &lt;strong&gt;inner&lt;/strong&gt; contour is also the container for the &lt;strong&gt;target&lt;/strong&gt; contour.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Define Segment under Point
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0a8myv8m8uh2jchf149.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0a8myv8m8uh2jchf149.png" alt="Segment under Point" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To determine whether a segment &lt;strong&gt;AB&lt;/strong&gt; is below a point &lt;strong&gt;P&lt;/strong&gt;, one may be tempted to compute the value of ym at the point of intersection &lt;strong&gt;M&lt;/strong&gt;, where a vertical line is dropped from &lt;strong&gt;P&lt;/strong&gt; onto &lt;strong&gt;AB&lt;/strong&gt; (i.e., xp = xm):&lt;br&gt;


&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;ym=ya−ybxa−xb⋅(xm−xa)+yay_{m} = \frac{y_{a} - y_{b}}{x_{a} - x_{b}}\cdot(x_{m} - x_{a}) + y_{a} &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;b&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;m&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;+&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;y&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;a&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;&lt;br&gt;&lt;br&gt;
However, this method can introduce &lt;strong&gt;precision issues&lt;/strong&gt; due to the division operation involved.&lt;/p&gt;

&lt;p&gt;A more reliable method involves using the order of traversal around the vertices of the triangle &lt;strong&gt;APB&lt;/strong&gt;. If segment &lt;strong&gt;AB&lt;/strong&gt; is below point &lt;strong&gt;P&lt;/strong&gt;, the vertices &lt;strong&gt;A&lt;/strong&gt;, &lt;strong&gt;P&lt;/strong&gt;, and &lt;strong&gt;B&lt;/strong&gt; will appear in a clockwise order.&lt;/p&gt;

&lt;p&gt;This method uses the cross product of vectors &lt;strong&gt;PA&lt;/strong&gt; and &lt;strong&gt;PB&lt;/strong&gt;:&lt;br&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;a×b=axby−aybxa \times b = a_x b_y - a_y b_x &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;×&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;y&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;b&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;x&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;Since this method avoids division, it eliminates precision issues, making it stable for determining whether a segment is below a point.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selecting the Closest Segment under Point
&lt;/h3&gt;

&lt;p&gt;When multiple segments are positioned below point &lt;strong&gt;P&lt;/strong&gt;, the next challenge is to identify which segment is &lt;strong&gt;closest&lt;/strong&gt; to point &lt;strong&gt;P&lt;/strong&gt;. This scenario can be categorized into three cases based on the relative configuration of the segments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Left Case&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyk16o5ijzc5kzzebg95.png" alt="Left Case" width="800" height="800"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When both segments share a common left vertex &lt;strong&gt;A&lt;/strong&gt;, we compare the positions of their right endpoints. If the vertices &lt;strong&gt;B0&lt;/strong&gt;, &lt;strong&gt;B1&lt;/strong&gt;, and &lt;strong&gt;A&lt;/strong&gt; form a clockwise pattern, then &lt;strong&gt;AB0&lt;/strong&gt; is closer to &lt;strong&gt;P&lt;/strong&gt; than &lt;strong&gt;AB1&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Right Case&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgcddmuvm6zek4qn7r4g5.png" alt="Right Case" width="800" height="800"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When both segments share a common right vertex &lt;strong&gt;B&lt;/strong&gt;, we check the positions of their left endpoints. If the vertices &lt;strong&gt;A0&lt;/strong&gt;, &lt;strong&gt;A1&lt;/strong&gt;, and &lt;strong&gt;B&lt;/strong&gt; form a clockwise pattern, then &lt;strong&gt;A1B&lt;/strong&gt; is closer to &lt;strong&gt;P&lt;/strong&gt; than &lt;strong&gt;A0B&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Middle Case&lt;/strong&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqogdodumrz2rezaxnvb.png" alt="Middle Case" width="800" height="800"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, one of the vertices (e.g., &lt;strong&gt;A1&lt;/strong&gt; or &lt;strong&gt;B0&lt;/strong&gt;) lies inside the opposite segment. We use the point-segment comparison method to determine which of the segments is closer to &lt;strong&gt;P&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Solution
&lt;/h3&gt;

&lt;p&gt;The final result of polygon Boolean operations is typically grouped by &lt;strong&gt;outer contours&lt;/strong&gt;. One outer contour may contain several &lt;strong&gt;holes&lt;/strong&gt;, and it is essential to match these correctly. In many cases, the &lt;strong&gt;direction&lt;/strong&gt; of the vertices in outer and inner contours is opposite: the outer contour is listed in a &lt;strong&gt;clockwise&lt;/strong&gt; direction, while the inner contours (holes) are listed &lt;strong&gt;counterclockwise&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This opposite orientation is especially useful in polygon processing algorithms like &lt;strong&gt;triangulation&lt;/strong&gt; or &lt;strong&gt;bottleneck detection&lt;/strong&gt;, as it simplifies the logic by removing the need to distinguish between segments belonging to outer or inner contours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Was Left Behind
&lt;/h2&gt;

&lt;p&gt;Some important aspects were only briefly mentioned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Constructing an Overlay Graph&lt;/strong&gt;: The process of reliably building the graph, handling intersections, and splitting segments is complex and deserves a deeper dive in a separate article.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph Convergence&lt;/strong&gt;: Ensuring the overlay graph converges correctly requires careful data handling to avoid errors during Boolean operations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filling Rules (EvenOdd/NonZero)&lt;/strong&gt;: The filling rule can significantly affect the outcome of polygon operations, especially in complex or overlapping shapes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing in iOverlay
&lt;/h2&gt;

&lt;p&gt;These concepts are fully implemented in iOverlay, a library designed for efficient 2D polygon Boolean operations. iOverlay delivers stable, deterministic results and is available under the MIT license across multiple platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/iShape-Rust/iOverlay" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iShape-Swift/iOverlay" rel="noopener noreferrer"&gt;Swift&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/iShape-Rust/iShape-js" rel="noopener noreferrer"&gt;JavaScript(Wasm)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>algorithms</category>
      <category>computerscience</category>
      <category>rust</category>
      <category>geometry</category>
    </item>
  </channel>
</rss>
