<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Fotonix &amp; Gizmos (Posts about numpy)</title><link>https://www.fotonixx.com/</link><description></description><atom:link href="https://www.fotonixx.com/categories/numpy.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2021 &lt;a href="mailto:ryan@fotonixx.com"&gt;Ryan Frazier&lt;/a&gt; </copyright><lastBuildDate>Sat, 10 Jul 2021 22:06:24 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>2D Binary Spacing Partitioning with Python and NetworkX</title><link>https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/bsp_2d/opengraph.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;I'm a sucker for tricks programmers use to get games to run efficiently, from the &lt;a class="reference external" href="https://www.pcgamer.com/heres-whats-happening-inside-fallout-3s-metro-train/"&gt;metro train in Fall Out 3&lt;/a&gt; that's actually a piece of armor, to the &lt;a class="reference external" href="https://kotaku.com/the-invisible-bunnies-that-power-world-of-warcraft-1791576630"&gt;spell casting bunnies&lt;/a&gt; in World of Warcraft that triggered world events. In a slightly different camp, however, are tricks that enabled game makers to do the impossible with the hardware they're given, and one name that's constantly tied to these feats is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/John_Carmack"&gt;John Carmack&lt;/a&gt;, cofounder of id software and pioneer of 3D computer games. His use of ray casting for Wolfenstein 3D and the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Fast_inverse_square_root"&gt;fast inverse square root algorithm&lt;/a&gt; alone are topics worthy of their own posts, but today I'm going to focus on the algorithm he used to enable real-time 3D level rendering in the original Doom: binary space partitioning (BSP).&lt;/p&gt;
&lt;p&gt;Carmack's vision for Doom was to have maps with varying heights, zone-specific lighting, and non-orthogonal walls. Unfortunately, the ray casting algorithm used in id's previous game, Wolfenstein 3D, couldn't render such complex scenes, so he was left looking for a new solution. David Kushner's &lt;a class="reference external" href="http://www.davidkushner.com/book/masters-of-doom/"&gt;Masters of Doom&lt;/a&gt;, as well as this post on &lt;a class="reference external" href="https://twobithistory.org/2019/11/06/doom-bsp.html"&gt;twobithistory&lt;/a&gt; have already covered this story wonderfully, so I won't go into excessive detail but instead focus on the solution. By representing levels as binary trees of line segments, the rendering engine could quickly decide which surfaces to draw and in what order. Despite being incredibly curious when I first read Masters of Doom, I wasn't working on anything related to computer graphics at the time, and filed BSP trees off for something to look at another day.&lt;/p&gt;
&lt;p&gt;Fast forward to now: I'm trying to incorporate OpenGL rendering into my &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;ray tracer&lt;/a&gt;, which requires &lt;a class="reference external" href="https://www.fotonixx.com/posts/efficient-csg/"&gt;constructive solid geometry&lt;/a&gt; performed on polygonal meshes. Turns out binary space partitioning is a core part of this algorithm too, so it's finally time to dive in and learn how to create and use BSP trees!&lt;/p&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#what-s-so-special-about-binary-space" id="id1"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#creating-a-bsp-tree" id="id2"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#the-bsp-algorithm" id="id3"&gt;The BSP algorithm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#bisecting-a-line" id="id4"&gt;Bisecting a Line&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#intersecting-two-lines" id="id5"&gt;Intersecting Two Lines&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#ahead-behind-bisected-or-colinear" id="id6"&gt;Ahead, Behind, Bisected or Colinear&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#putting-it-into-a-function" id="id7"&gt;Putting it Into a Function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#handling-bisected-lines" id="id8"&gt;Handling Bisected Lines&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#recursively-building-the-tree" id="id9"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#taking-it-for-a-test-drive" id="id10"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#what-s-so-special-about-binary-space" id="id11"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#creating-a-bsp-tree" id="id12"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#the-bsp-algorithm" id="id13"&gt;The BSP algorithm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#bisecting-a-line" id="id14"&gt;Bisecting a Line&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#recursively-building-the-tree" id="id15"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#taking-it-for-a-test-drive" id="id16"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-so-special-about-binary-space"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id11"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Binary space partitioning divides an n-dimensional space into segments that are entirely in front or behind a dividing &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hyperplane"&gt;hyperplane&lt;/a&gt;. For now we'll focus on the 2D case, which means the partition splits a group of line segments into sets that are either in front, behind, or colinear to a dividing line. Repeatedly performing this partitioning on the resulting sets creates a binary tree, where every node contains a dividing line and all colinear segments, and each edge connects to a child node whose segments are completely in front or behind the current node.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/tree_construction.gif" src="https://www.fotonixx.com/images/bsp_2d/tree_construction.gif" style="width: 600px;"&gt;
&lt;p class="caption"&gt;Construction of a BSP tree by repeatedly subdividing a collection of surfaces. Edges labeled + are in front of the previous node, and - are behind.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The most popular use for BSP trees is for &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hidden-surface_determination"&gt;visible surface determination&lt;/a&gt; in computer graphics, notably for the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Painter%27s_algorithm"&gt;painter's algorithm&lt;/a&gt;. As the painter's algorithm draws the entire set of surfaces from back to front, it needs to be able to quickly sort the surfaces from any viewpoint. BSP trees allow for the computationally expensive sorting to happen just once. When the scene needs to be rendered the tree is traversed in linear time from the current viewpoint and a sorted list of surfaces is generated. It's worth noting this only works for static surfaces, which made it ideal for rendering Doom's level maps. As soon as an individual surface is transformed, however, the entire tree needs to be regenerated.&lt;/p&gt;
&lt;p&gt;A less famous use of BSP trees is performing constructive solid geometry (CSG) operations on discrete polygonal meshes. This application uses two separate BSP trees, one for each object the operations are being applied to. The trees are then 'projected' onto each other, resulting in a set of surfaces from one object that are entirely in the other. Depending on the operation (union, intersection, or difference), certain surfaces are discarded, and the remaining ones make up the new shape. The actual CSG algorithm is beyond the scope of this post, but the documentation and code for the &lt;a class="reference external" href="http://evanw.github.io/csg.js/docs/"&gt;csg.js&lt;/a&gt; library provides a basic explanation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-bsp-tree"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id12"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to the standard library, we'll use &lt;a class="reference external" href="https://numpy.org"&gt;Numpy&lt;/a&gt; to calculate the line-segment intersections, and &lt;a class="reference external" href="https://networkx.org"&gt;NetworkX&lt;/a&gt; to create the tree. NetworkX is a general purpose graph library that prevents us from having to define our own nodes and edges, and includes extensive traversal algorithms and visualization features. Both packages are installable via pip.&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a name="rest_code_e00307786cce40c48ed3c2fb374ec514-1"&gt;&lt;/a&gt;py -m pip install numpy, networkx
&lt;/pre&gt;&lt;div class="section" id="the-bsp-algorithm"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id13"&gt;The BSP algorithm&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Below are the steps we'll be implementing to create our tree. Given a set of 2D line segments and a dividing line:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Calculate the intersection of the dividing line with all segments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split any segments that intersect the dividing line into two segments at the intersection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add colinear line segments to the current node of the tree.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new child node and repeat steps 1-6 for all segments in front of the dividing line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new child node and repeat steps 1-6 for all segments behind the dividing line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect the new nodes to the current node with an edge.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since tree generation is a recursive process, steps 4 and 5 repeat every step up to and &lt;em&gt;including&lt;/em&gt; themselves until every line segment can be described as colinear to a node. Also, the number of segments in the returned tree may drastically exceed the number of segments in the original set, depending on how many are bisected at each iteration. We'll break the algorithm down into two pieces: the code used to intersect and split a set of segments with a dividing line, and the recursive function that builds the tree.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bisecting-a-line"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id14"&gt;Bisecting a Line&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The bulk of the processing is determining where each line segment in a set intersects the dividing line. We'll make a function called &lt;code&gt;bisect&lt;/code&gt; that accepts an array of line segments and a dividing line, and returns three arrays, one each for segments ahead, behind, and colinear to the dividing line.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_b765b8eb07f34dbd885f911aaa8df24e-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a name="rest_code_b765b8eb07f34dbd885f911aaa8df24e-2"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# separates a set of line segments based on where they intercept a dividing line&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The segments array is an Nx2x2 array, where N is the number of segments being sorted. Each row of the array is a set of two xy-pairs representing the start and endpoints of the segment. For example, a square centered at the origin is composed of four segments:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-2"&gt;&lt;/a&gt;    &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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="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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-3"&gt;&lt;/a&gt;    &lt;span class="p"&gt;[[&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="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="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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-4"&gt;&lt;/a&gt;    &lt;span class="p"&gt;[[&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-5"&gt;&lt;/a&gt;    &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&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;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Similarly, a line is represented by a single 2x2 array of two XY-pairs. The advantage of using Numpy data structures instead of Python lists and tuples is that we can apply the intersection algorithm to the entire segment array at once, instead of iterating over each segment with a for loop. Since Numpy calls compiled C-code, there is almost always a performance gain when a loop can be replaced with array operations.&lt;/p&gt;
&lt;p&gt;The last decision we need to make before writing the bisect function is coming up with a consistent definition for what "in front" and "behind" mean in the context of our lines. An easy enough way to visualize it is this: If you take your right hand and orient it so that the pad of your hand is on top of the first point of the line, and your fingers point towards the second, everything on the left side of your hand (closest to the palm) is in front of the line, and everything on the right side (closes to the back of your hand) is behind the line. Later we'll define this more formally with projections onto a normal vector of the line. If the concept of a vector projection is new, I recommend reading up on dot and cross-products, or skipping straight to the &lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#implementation"&gt;implementation&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="/images/bsp_2d/line_ahead_behind.png" class="blog-figure align-center" src="https://www.fotonixx.com/images/bsp_2d/line_ahead_behind.png"&gt;
&lt;div class="section" id="intersecting-two-lines"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id5"&gt;Intersecting Two Lines&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To calculate the intercepts we'll describe the lines and segments as vectors with one independent variable.&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
\vec{l}(t) &amp;amp;= \vec{p}_0+(\vec{p}_1-\vec{p}_0)*t \\
&amp;amp;= \vec{p}_0 + \vec{v}*t
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Since segments have a defined start and end, t is valid over a closed range &lt;span class="math"&gt;\(t \in [0,1]\)&lt;/span&gt;. Lines on the other hand extend infinitely in all directions, so t can be any real number. For convenience I'm going to refer to our line segments as &lt;span class="math"&gt;\(\vec{g}(t) = \vec{p_0} + \vec{v}_0t\)&lt;/span&gt;, and the dividing line as &lt;span class="math"&gt;\(l(s) = \vec{p}_1 + \vec{v}_1 s\)&lt;/span&gt;. We're interested in the value t where the two lines meet.&lt;/p&gt;
&lt;div class="row docutils container"&gt;
&lt;div class="col-lg-5 col-md-12 my-auto docutils container"&gt;
&lt;div class="math"&gt;
\begin{align*}
\begin{aligned}
\vec{g}(t) &amp;amp;= \vec{l}(s) \\
\vec{p}_0 + \vec{v}_0 t &amp;amp;= \vec{p}_1 + \vec{v}_1 s \\
&amp;amp;...
\end{aligned}\\
t = \frac{(\vec{p}_1-\vec{p}_0) \times \vec{v}_1}{\vec{v}_0 \times \vec{v}_1}
\end{align*}
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="col-lg-7 col-md-12 my-auto docutils container"&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/line_intersection.png" src="https://www.fotonixx.com/images/bsp_2d/line_intersection.png"&gt;
&lt;p class="caption"&gt;intersection point shown in red&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I've skipped a few steps in the above equation because the &lt;a class="reference external" href="https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect"&gt;intercept of two vector equations&lt;/a&gt; is well understood. What we're more interested in is using the results to determine where the segments lie with respect to our line.&lt;/p&gt;
&lt;p&gt;Red flags might be going off because this equation is dividing two vectors, which is (1) an invalid operation and (2) can't result in a scaler. Since our lines and segments are all 2D, however, we're going to use the 2D definition of cross product: &lt;span class="math"&gt;\(\vec{A} \times \vec{B} = A_x*B_y-A_y*B_x\)&lt;/span&gt;. This scaler is the z-component of the cross product vector we &lt;em&gt;would&lt;/em&gt; have gotten had we taken a traditional 3D cross-product and set the z-values to zero for &lt;span class="math"&gt;\(\vec{A}\)&lt;/span&gt; &amp;amp; &lt;span class="math"&gt;\(\vec{B}\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ahead-behind-bisected-or-colinear"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id6"&gt;Ahead, Behind, Bisected or Colinear&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;It's easy to tell that the line bisects a given segment when the t is between 0 and 1. What about the other cases though: how do we determine if the segment is in front or behind the line, and what about cases where t is undefined? To get more insight into what's happening with the intersection equation, we'll look at the numerator and denominator separately.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Numerator&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;We already defined the two-dimensional vector cross product: &lt;span class="math"&gt;\(\vec{A} \times \vec{B} = A_x*B_y-A_y*B_x\)&lt;/span&gt;. Taking a closer look, it's identical to &lt;span class="math"&gt;\((A_x, A_y) \cdot (B_y, -B_x)\)&lt;/span&gt;, and &lt;span class="math"&gt;\((B_y, -B_x)\)&lt;/span&gt; is the same as rotating &lt;span class="math"&gt;\((B_x,B_y)\)&lt;/span&gt; clockwise 90 degrees, aka it's &lt;em&gt;proportional to the unit normal vector of the dividing line&lt;/em&gt;. The numerator can be rewritten as:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
((\vec{p}_0-\vec{p}_1) \cdot \hat{n})|\vec{v}_1|
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Meaning the numerator is taking the distance between a point on the segment and a point on the line, and projecting that vector onto the normal vector of the line (this is identical to calculating the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line"&gt;shortest distance between a point and a line&lt;/a&gt;). From our definition of "in front" and "behind", we can claim that a line segment is &lt;em&gt;partially&lt;/em&gt; in front of the line if the numerator is &amp;gt; 0, and &lt;em&gt;partially&lt;/em&gt; behind if the numerator is &amp;lt; 0. If the numerator is equal to zero, it means the first point of the segment is on the line, and we need to look at the denominator to define whether it's in front or behind.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;dl&gt;
&lt;dt&gt;Denominator&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Using the same logic as above, the denominator can be rewritten as a vector projection onto the normal vector:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
-(\vec{v}_0 \cdot \hat{n})|\vec{v}_1|
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This projection is a measure of how far &lt;span class="math"&gt;\(\vec{g}(t)\)&lt;/span&gt; travels towards line &lt;span class="math"&gt;\(\vec{l}(s)\)&lt;/span&gt; per interval t. Since the numerator is how far the lines are apart (projected onto the normal vector), and the denominator is how fast one line is approaching the second along that same normal, it makes sense that the ratio is the amount of time (t) until they intersect!&lt;/p&gt;
&lt;p&gt;If the denominator is equal to zero, the two lines are parallel, and the sign of the numerator determines where the segment lies relative to the line.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;With the exception of cases where t is between 0 &amp;amp; 1, the numerator and denominator must be considered when defining the segments location. A summary of all cases is given below.&lt;/p&gt;
&lt;table class="table table-striped table-sm blog-table bsp-cases w-85 mx-auto"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%"&gt;
&lt;col style="width: 17%"&gt;
&lt;col style="width: 21%"&gt;
&lt;col style="width: 37%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;t&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Numerator&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Denominator&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Line Segment Location&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;0 &amp;lt; t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Bisected by line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t &amp;gt; 0 or t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;gt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;In front of line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t &amp;gt; 0 or t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;lt;0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Behind line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t = 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;lt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;In front of line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t = 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;gt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Behind line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;= 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;= 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Colinear&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-it-into-a-function"&gt;
&lt;span id="implementation"&gt;&lt;/span&gt;&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id7"&gt;Putting it Into a Function&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Cases in hand we're ready to implement the first half of our bisect function!&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;segment_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;segment_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;v0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-9"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# need to solve for the intersection equation, first find the numerator and denominator&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-14"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# if the denominator is zero the lines are parallel&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;not_parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-17"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-18"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# the intersection time is the point along the line segment where the line bisects it&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-20"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;&amp;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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&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;denominator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&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;denominator&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-23"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-24"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# segments are colinear if they are parallel and the numerator is zero&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-25"&gt;&lt;/a&gt;    &lt;span class="n"&gt;colinear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-26"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-27"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# bisected segments are segments that aren't parallel and t is in (0,1)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-28"&gt;&lt;/a&gt;    &lt;span class="n"&gt;bisected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-29"&gt;&lt;/a&gt;        &lt;span class="n"&gt;not_parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-30"&gt;&lt;/a&gt;        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;&amp;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;intersection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-31"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Right now all it does is build a set of boolean masks used to index the segment array. Before returning the new segments we need to write some code to split bisected segments into two new segments. Notice how when calculating t the denominator has an extra component &lt;code&gt;parallel&lt;/code&gt;. This boolean value is cast as a 1 if the denominator would have been zero, and prevents Numpy from throwing a warning about divide by zero errors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="handling-bisected-lines"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id8"&gt;Handling Bisected Lines&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If a segment is bisected by the line at time t, we need to create two new segments, one from 0-t, and a second from t-1. Fancy indexing with Numpy can be slower than doing simple operations on an entire array (this is highly dependent on the application), so we'll create two new segments for every segment and then filter.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;intersection_points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;intersection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;
&lt;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;l_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:],&lt;/span&gt; &lt;span class="n"&gt;intersection_points&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&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;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;r_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;intersection_points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]),&lt;/span&gt; &lt;span class="n"&gt;axis&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;/pre&gt;&lt;p&gt;Notice how they're split into left and right segments, not 'in front' and 'behind'. That's because right now we don't know which segments are which, and need to filter them based on the numerator. If the numerator is &amp;gt;0, the first point of the segment is ahead of the line, making &lt;code&gt;l_segment&lt;/code&gt; ahead and &lt;code&gt;r_segment&lt;/code&gt; behind.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;bisected_ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l_segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_segments&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;bisected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;bisected_behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;l_segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_segments&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;bisected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/left_right_segments.png" src="https://www.fotonixx.com/images/bsp_2d/left_right_segments.png"&gt;
&lt;p class="caption"&gt;An example of two segments where the left and right splits are on opposite sides of the dividing line&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Once the bisected segments have been split, all that's left to do is combine them with their respective ahead/behind sets, and return the three sets of segments. The full function is found &lt;a class="reference external" href="https://github.com/rfrazier716/bsp/blob/12af4fba95cc594b2f3da604491de75d508d1d58/bsp.py#L9"&gt;here&lt;/a&gt;, with a set of if statements to catch cases where ahead/behind sets are empty arrays. The &lt;code&gt;Tuple&lt;/code&gt; Typehint is from the &lt;a class="reference external" href="https://docs.python.org/3/library/typing.html"&gt;typing&lt;/a&gt; module, and is useful for IDE's to infer what types the function will return.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="recursively-building-the-tree"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id15"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Compared to the amount of code required to partition our line segments, the function build the BSP tree is relatively simple.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-4"&gt;&lt;/a&gt;        &lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colinear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# get the bisected segments&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-5"&gt;&lt;/a&gt;        &lt;span class="n"&gt;node_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# make your line hashable so it's usable as a node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-6"&gt;&lt;/a&gt;        &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colinear_segments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;colinear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# add the node to the graph&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-7"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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="c1"&gt;# if there's any elements behind&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-8"&gt;&lt;/a&gt;            &lt;span class="n"&gt;node_behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# recursively call for all segments behind&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-9"&gt;&lt;/a&gt;            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&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="c1"&gt;# add an edge from this node to the behind node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-10"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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;a name="rest_code_6dad8242166e408980a244b8403ef030-11"&gt;&lt;/a&gt;            &lt;span class="n"&gt;node_ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# recursively call for all segments in front&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-12"&gt;&lt;/a&gt;            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&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="c1"&gt;# add an edge from this node to the front node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-13"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node_id&lt;/span&gt;  &lt;span class="c1"&gt;# return the hashed id&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-14"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# make a new directed graph&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-16"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-17"&gt;&lt;/a&gt;        &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-19"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# run the recursive helper function, which should add all nodes and edges&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-20"&gt;&lt;/a&gt;    &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-21"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert_node_labels_to_integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Let's pick it apart, starting with the function signature. The function expects a nx2x2 array of line segments, and an optional starting segment that it used as the first dividing line. The output will be a NetworkX directed graph object where every node is a dividing line holding the colinear segments for that line.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1e4373d6ca744ec48bd82049dcd09156-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Next is the initialization block, here we create the actual graph object and check if the user provided a starting segment. If no segment was given, the first segment of the array is used instead. The function then calls the bsp_helper function that fills the graph with divided segments, and then relabels all nodes sequentially before returning the graph.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# make a new directed graph&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-5"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# run the recursive helper function, which should add all nodes and edges&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert_node_labels_to_integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The final piece is the nested bsp_helper function. Defining bsp_helper within build_tree allows it to access the graph variable without explicitly passing it (this is part of the &lt;a class="reference external" href="https://realpython.com/python-namespaces-scope/#variable-scope"&gt;enclosing scope&lt;/a&gt; in an LEBG model). It also keeps bsp_helper out of the module namespace. The function calculates where each segment in a set falls relative to the dividing line. It then creates a new node with two attributes: the dividing line of the node, and any segments that are colinear to that dividing line. If there's any segments in front of the line, the helper function recursively calls itself with only the set of segments ahead of current dividing line, returning the id of the created node. The returned node is connected to the current node with an edge, whose position attribute is +1 (meaning it's ahead of the current node). The same is then done for segments behind the current line. If recursive functions are a new concept, RealPython has a &lt;a class="reference external" href="https://realpython.com/python-thinking-recursively/"&gt;great tutorial&lt;/a&gt; on understanding and implementing them, as well as introduces the &lt;a class="reference external" href="https://docs.python.org/3/library/functools.html#functools.lru_cache"&gt;@lru_cache&lt;/a&gt; decorator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="taking-it-for-a-test-drive"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id16"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a last step we'll create a tree from scratch and plot the results. First lets make a function to draw the segments stored in a BSP tree, with markers showing where the the segments are divided. The drawing itself will be handled by &lt;a class="reference external" href="https://matplotlib.org"&gt;matplotlib&lt;/a&gt;, which can also be installed from pip.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-3"&gt;&lt;/a&gt;        &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;all_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concatenate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_node_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"colinear_segments"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-6"&gt;&lt;/a&gt;        &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Next we'll import some &lt;a class="reference external" href="https://www.fotonixx.com/bsp_2d/points.csv"&gt;prebuilt line segments&lt;/a&gt; and reshape the list of points into an appropriate shape. Then it's as simple as creating the bsp tree and plotting the results!&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loadtxt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"points.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;draw_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/doom_tree.png" src="https://www.fotonixx.com/images/bsp_2d/doom_tree.png" style="width: 800px;"&gt;
&lt;p class="caption"&gt;The fruit of our labor, bisected points are shown in red&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Despite creating an impressive BSP tree, at the end of the day it's still just a graph, and could use a killer application to make the power of BSP shine! &lt;a class="reference external" href="https://github.com/rfrazier716/bsp/blob/master/bsp.py"&gt;The full code&lt;/a&gt; has additional functions for performing operations on trees as well as implementing CSG operations. Alternatively, pair BSP with &lt;a class="reference external" href="http://pyglet.org"&gt;Pyglet&lt;/a&gt; you're half way towards a working 2.5D game engine.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>graphics</category><category>numpy</category><category>python</category><guid>https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/</guid><pubDate>Tue, 30 Mar 2021 00:20:56 GMT</pubDate></item><item><title>Rendering Constructive Solid Geometry With Python</title><link>https://www.fotonixx.com/posts/efficient-csg/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/efficient_csg/preview_image.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;I recently hit a road block with my &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;ray tracer&lt;/a&gt;: cubes, cylinders, and spheres rendered fine, but there wasn't an easy way to create arbitrary shapes whose intersection and normal functions I hadn't already hard coded. Since &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;PyRayT's&lt;/a&gt; end use is for optical design, at the bare minimum it needed a flexible way to create lenses and mirrors. Flipping through Jamis Bucks' &lt;a class="reference external" href="https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/"&gt;The Ray Tracer Challenge&lt;/a&gt;, it turns out the last chapter &lt;em&gt;Constructive Solid Geometry&lt;/em&gt; (CSG) addressed my needs perfectly! However, Buck's equations for CSG did not blend well with PyRayTs flow of rendering multiple rays at once. Today I'll be covering my own algorithm for adding constructive solid geometry to a ray tracer, as well as its implementation in Python using &lt;a class="reference external" href="https://numpy.org"&gt;NumPy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.fotonixx.com/posts/efficient-csg/"&gt;Read more…&lt;/a&gt; (14 min remaining to read)&lt;/p&gt;&lt;/div&gt;</description><category>constructive solid geometry</category><category>csg</category><category>numpy</category><category>pyrayt</category><category>python</category><category>ray tracing</category><category>rendering</category><guid>https://www.fotonixx.com/posts/efficient-csg/</guid><pubDate>Mon, 15 Mar 2021 11:35:26 GMT</pubDate></item></channel></rss>