Jekyll2023-09-13T12:11:26+00:00http://www.evryway.com/feed.xmlEvrywayShiny things for your delectationWTAF, Unity?2023-09-13T00:00:00+00:002023-09-13T00:00:00+00:00http://www.evryway.com/wtaf-unity<h2 id="or-a-slightly-more-nuanced-take-on-unitys-latest-pricing-model">Or, a slightly more nuanced take on Unity’s latest pricing model</h2>
<h3 id="unity-is-great">Unity is great.</h3>
<p>The <a href="https://unity.com/developer-tools">product</a>, that is. The Editor and runtime. It is one of the most flexible and performant game engine solutions out there. It targets a huge number of platforms, from consoles to the web. It’s also more than just an engine - much of the functionality present in the current Unity versions is the kind of thing that I’ve spent years of my life creating at other companies, and it’s available to developers - for free! (more on that in a minute).</p>
<p>I’ve been a Unity user since 2009 (version 2.x) and have used Unity commercially since 2010 (Version 3, and every version after that). I have recommended Unity as a development environment to multiple companies. When we set up <a href="https://bossalien.com/">BossAlien</a>, Bangs, George and I went all-in on Unity as the engine of choice for creating our products, leading to <a href="https://en.wikipedia.org/wiki/CSR_Racing">CSR Racing</a> and the resulting sequels.</p>
<p>Since moving on to found <a href="https://evryway.com">Evryway</a>, I have used Unity for <strong>every single project</strong> apart from two (which are using web technologies directly) - literally dozens of different applications covering AR, VR, mobile, web, desktop, with clients ranging from government agencies to huge international corporations, down to tiny prototypes for personal use.</p>
<p>I’m a big fan.</p>
<h3 id="unity-is-schizophrenic">Unity is schizophrenic.</h3>
<p>The company, that is. Over the years (certainly while I’ve been using it) the company has shifted from being funded by developer licencing fees to - well, currently who the hell knows where the revenue is coming from? digging into their <a href="https://investors.unity.com/overview/default.aspx">latest quarterly report</a> isn’t exactly crystal clear, there’s a whole slew of different revenue streams grouped under somewhat opaque terms such as Create and <a href="https://duckduckgo.com/?q=unity+grow">Grow</a> and Muse and Sentis.</p>
<p>There was a period right at the start where the priorities of the company matched those of the average development team. Making great tools enables great products, developers pay a fee to use the tools, everyone wins.
Of course, it’s hard to sell something more than once, so after a few years, Unity moved from a <strong>perpetual licence</strong> model (which I have, personally, and it cost me <strong>thousands of dollars</strong>) to a <strong>subscription model</strong> - pay yearly. Pay by the seat. Give us more money every year please.</p>
<p>One can’t be too hard on this model - after all, if you can get away with it, why not? All of the good companies are doing it (Adobe, Autodesk, Oracle, etc). And supporting a product as complicated and wide ranging as Unity does require investment. Without some form of revenue share, developers can be making huge amounts of money off the products created with the tools, and Unity doesn’t get a sniff of that.</p>
<p><strong>Can you imagine if you wrote a great book in Microsoft Office and they started demanding a cut of the revenue? Hold that thought.</strong></p>
<p>Right about this time, it also became clear that actually, this wasn’t really their primary revenue stream. The primary revenue stream was Enterprise licences. Charge large teams large amounts of money for support, and ka-ching.</p>
<p>Of course, this introduces a fundamental tension into the product. Simply put, you are better off <strong>not fixing problems as they occur, because you can bill someone for support to fix them later</strong>.</p>
<p>As someone who paid for this (and then stopped paying for this, because the help wasn’t worth the cost), it became very clear, very fast, that if you were <strong>not</strong> paying for support, you were going to get <strong>very little help</strong> with your issues and problems. I’m incredibly lucky to know lots of people who are working for, or who have worked at, Unity, and often I could go via back channels to discuss an issue or get something fixed, but if you go in via the front door - good luck with that. Forums that go nowhere. Help pages that have broken links. Features that are released half-baked, and get worse as time goes on, but are intended to replace some other broken system which is now officially deprecated.</p>
<p>The stability of Unity (the product) has <strong>hugely improved</strong> over the last few years, but from version 5 to version 2018 it was a totally different story, and supporting something like CSR Racing across multiple platforms, utilizing lots of the built-in technology - suffice to say, I’m bald and grey for a reason.</p>
<p>But that revenue stream still wasn’t enough. <strong>Unity wanted a cut of your revenue</strong>.</p>
<p><a href="https://www.unrealengine.com/en-US">Unreal engine</a> already decided to do just that. Then <a href="https://store.epicgames.com/en-US/p/fortnite">Fortnite</a> happened, and suddenly Epic had literally billions of dollars to throw at engine development for Unreal, and at this point, <strong>Unity is fucked</strong>. Because they can’t compete in R&D budget, and they can’t compete on looks, and they already have a perception (right or wrong) for being the platform you go to if you want to make free to play shovelware using asset flips and kitbashing from the <a href="https://assetstore.unity.com/">Unity Asset Store</a> (another revenue stream that brings tension to developers).</p>
<p>Now, as soon as <a href="https://en.wikipedia.org/wiki/John_Riccitiello">John Riccitello</a> took over as CEO, anyone with a view to his history knew the road we were all about to travel. I was working at EA when he was president, and his deep knowledge of baked goods informed the strategy of one of the largest games publishers in the world. He’s a smart guy, for sure. And he wants to pump this company, goddamnit, and sell it for the absolute maximum he can achieve, and move on to the next big thing after that.</p>
<p>Looking for other revenue streams, Unity has branched out into <a href="https://unity.com/solutions/gaming-services">cloud services, advertising, enterprise tools and technologies</a>, and all sorts of additional services that have very little to do with just making good games. And they want developers to pay for all of this stuff, somehow, somewhere (especially if it’s “free”).</p>
<p>A massive acquisition spree later, and we’re looking at a company with about a zillion different divisions, targets, goals and client bases, perfectly timed for the end of the cheapest debt market in history and the impending inflation apocalypse.</p>
<h2 id="so-where-do-unity-the-company-go-next"><a href="https://media.tenor.co/images/8922d2193965b055e35ece0c6dca4c42/tenor.gif">So where do Unity (the company) go next?</a></h2>
<p><a href="https://blog.unity.com/news/plan-pricing-and-packaging-updates">They change their pricing plan, of course.</a></p>
<p>This is filled with so much WTF that my WTF meter is spinning backwards.</p>
<p>I get it, I really do. Unity wants more money, and they can only rinse developers so hard up front, and they want the tools to be available to as many people as possible - and Unreal’s up one end squeezing, and tother end is being pushed by <a href="https://godotengine.org/">Godot</a> and the like.</p>
<p>But <strong>installs</strong>? Are you kidding me?</p>
<p><strong>Who the hell thought this was a good idea?</strong></p>
<p>Much of the scurrilous gossip I’ve been bantering around today has been about how this has to be a PR Bomb - deliberately ridiculous news intended to allow for a few backwards steps, to the point everyone says “well, those changes aren’t so bad compared to that ridiculous bullshit a few days ago”. One has to hope, I guess.</p>
<p>On the plus side, if these changes do go ahead as advertised, it might kill the free-to-play shovelware market - at least, the slab that’s actually making money. So, I guess not most of it.</p>
<p>But, back to revenue share. I can certainly see how, if each player of a game is actively costing Unity money (for example, if they’re using leaderboards, or cloud storage, or other online services that have a tangible per-user cost), that Unity can and should feel justified in charging some per-user fee at some point.</p>
<p>And of course, if the other players in the market are selling their product “free up front, pay later when you get rich”, why wouldn’t you want a slice of that too?</p>
<p>But this sort of nonsense only flies when there’s really no other alternative out there. Just because it’s becoming normalized by everyone who can do it, doesn’t mean recurring subscriptions and revenue share and other blood-from-a-stone mechanisms are good, especially not for the developers (and ultimately players).</p>
<p>Blender is a thing, now. Remember Lightwave, then 3DS Max, then Maya, then what the hell Autodesk? Software changes, and alternatives appear, and then they git gud, and then your market is <strong>gone</strong>. Because you killed it.</p>
<p>I really hope that Unity backpedals on this. Just go for a revenue share model, figure out something that balances the books so we can all carry on using the <em>*actual tool we want to use (because it’s great)</em> and stop making lives harder for everyone.</p>
<p>And to all my mates still working at Unity - please don’t take any of this as a personal attack, you know I still love you ;)</p>
<h2 id="side-note">Side note</h2>
<p>what does 3 Billion in goodwill buy you, exactly?</p>Or, a slightly more nuanced take on Unity’s latest pricing modelFinding the Axis Aligned Largest Interior Rectangle in a simple polygon2020-09-15T00:00:00+00:002020-09-15T00:00:00+00:00http://www.evryway.com/largest-interior<p>This article presents a solution to the problem of finding the axis aligned largest interior rectangle inside a simple polygon.
The solution is implemented using C# in Unity 3D. There’s some mathematics, some pictures, and a link to a fully
working implementation.</p>
<h2 id="abstract">Abstract</h2>
<p>The largest interior rectangle in a simple polygon is a useful piece of information in many fields and applications.
While I was looking for solutions to the problem, I found articles relating to meat factories and t-shirt designs, as
well as the particular problem I was trying to solve - the largest rectangular space inside my VR room boundary.
As far as I’m aware, there is no trivial, fast solution to this problem, so
<a href="#step0">this implementation</a> might be useful. If you
do find it useful, and you improve upon it, please share the improvements!</p>
<p><img src="/assets/images/lir/lir_example.png" alt="LIR" title="Largest Interior Rectangle" width="320px" height="320px" class="imgcenter" /></p>
<h2 id="1-introduction">1. Introduction</h2>
<p>One of the largest recent changes in Unity has been a move away from built-in code that talks to specific hardware
(for example, Oculus headsets) to using an “XR Plugin” architecture, that allows manufacturers to create, and update,
their own code without the need for Unity to release a new version of the Unity Editor.</p>
<p>The motivation behind this new architecture is good - and the implementation is almost on par now with the previous
(legacy, and now deprecated) way of doing things. One area that has caused me problems, however, is the Boundary Data.</p>
<p>When a headset user sets up a Boundary (or Guardian) area, they are drawing out a region in space that’s safe for them
to play inside. This normally helps the player avoid moving into unsafe areas - for example, running face first into
a wall, or down a staircase, or smashing their TV with their controller. This Boundary area typically comes in
two flavours; the raw “boundary data” (a wobbly line the user draws around the outside of the safe region) and
the interior “play data” (typically, a rectangular area inside the boundary).</p>
<p>Some of my prototypes use the rectangular area for specific reasons (for example, locking menus to the walls).
Unfortunately, the latest XR Plugins do not give me that data any more - I can only access the full
boundary data.</p>
<p>This article details the algorithm and implementation specifics. Parallel to this, I’ve also written an article
about the <a href="/a-tale-of-two-problems/">process of solving this problem</a>, which you may find interesting.</p>
<h2 id="2-background">2. Background</h2>
<p>The problem I’m aiming to solve is to take a set of points in a 2D plane which describe the a <em>simple polygon</em>,
and from those points, calculate the <em>largest interior rectangle</em> that is contained by that simple polygon.
It’s a subclass of the <a href="https://en.wikipedia.org/wiki/Largest_empty_rectangle">Largest Empty Rectangle</a>
problem.</p>
<p>A <a href="https://en.wikipedia.org/wiki/Simple_polygon">simple polygon</a> is a polygon which may be convex or concave,
does not intersect itself, and has no holes.</p>
<p>An axis-aligned rectangle is a four sided shape, with two pairs of parallel sides, each pair being the same length.
one of the pairs is parallel to the x-axis, and the other is parallel to the y-axis. (This
may seem obvious, but it’s worth being clear what everything means!)</p>
<p><img src="/assets/images/lir/lir_1.png" alt="Figure 1" title="Figure 1: A simple (concave) polygon" width="640px" /></p>
<p>Figure 1: the largest interior rectangle in a concave polygon.</p>
<p>Figure 1 shows a concave polygon - a set of points (shown as white dots) connected via edges (shown as
red lines) to form a closed simple concave polygon. Inside the polygon, there is a rectangular region
showing the largest axis-aligned interior rectangle that does not cross a polygon edge or contain
any of the polygon points.</p>
<p>There appear to be a variety of different algorithms that solve variants of this problem, with related works
going back to the 1970s that I’ve found. One of the best papers covering the problem is written by Karen Daniels,
Victor Milenkovic and Dan Roth, titled <a href="https://dash.harvard.edu/bitstream/handle/1/27030936/tr-22-95.pdf">Finding the Largest Rectangle in Several Classes of Polygons</a>.
The level of understanding required to implement their solutions is currently beyond me, however. I’ve read
this paper through a few times now, and the necessary understanding still has not yet sunk in.</p>
<p>A brute-force solution is possible, but that process would be terribly slow (as it would involve testing
every vertex against every polygon edge for every possible variation of certain properties). One assumes that
if it was a viable solution, it would be documented and implemented - and the papers I have digested all discuss
the brute-force solution in terms of O(n^4) or O(n^5) - in other words, with the number of vertices I’m hoping
to work with (up to 1000) I would be looking at billions or trillions of calculations.</p>
<p>A paper that I did manage to understand, and the one I used as a basis for this implementation, was written
by Zahraa Marzeh, Maryam Tahmasbi and Narges Mirehi, entitled <a href="https://journals.ut.ac.ir/article_71280.html">Algorithm for finding the largest inscribed
rectangle in polygon</a>. Taking this as a basis, I created a working implementation in Unity using C#.</p>
<h2 id="3-implementation">3. Implementation</h2>
<p>One approach to solving the problem is to divide the space covered by the polygon into an axis-aligned grid
of rectangles, and then find the largest rectangle formed from these sub-rectangles.</p>
<p><a href="#step1">Step 1</a> requires finding the smallest area rectangle that contains the simple polygon.<br />
<a href="#step2">Step 2</a> constructs the grid of rectangles (cells), using each vertex of the polygon as the x and y coordinates for
the grid lines.<br />
<a href="#step3">Step 3</a> optionally refines these rectangles (adding more rectangles, and hence more granularity to the resulting grid).<br />
<a href="#step4">Step 4</a> scans this rectangle grid, and determines whether a rectangle is inside or outside the polygon.<br />
<a href="#step5">Step 5</a> determines which rectangles are linked to (adjacent) other rectangles, in the horizonal and vertical directions.<br />
<a href="#step6">Step 6</a> uses this adjacency information to calculate, for each rectangle, the largest area rectangle that can be
constructed by moving up and to the right.<br />
<a href="#step7">Step 7</a> or, iterate all interior rectangles, calculating the areas for the adjoining rectangle regions, and tracking the best one.</p>
<p>Each of these steps is simple to understand, and all steps can be solved in a linear fashion, feeding the results
from each step into the next.</p>
<h2 id="step0">3.0 preliminaries and code repository</h2>
<p>To best facilitate visibility of the implementation, I’ve made it available on Github at
<a href="https://github.com/Evryway/lir">https://github.com/Evryway/lir</a>. Feel free to read along with the code on there.
The documentation on the repo is fairly sparse, but the code itself (along with this article) should be enough to
get you going with your own implementation.</p>
<p>The majority of the code is contained in
<a href="https://github.com/Evryway/lir/blob/master/LargestInteriorRectangle.cs">LargestInteriorRectangle.cs</a>,
but there’s a few helper extensions in
<a href="https://github.com/Evryway/lir/blob/master/Extensions.cs">Extensions.cs</a>
and a Bound2D class (representing a non-axis-aligned 2D bound) in
<a href="https://github.com/Evryway/lir/blob/master/Bound2D.cs">Bound2D.cs</a>.</p>
<p>You can find some tests for expected pass and fail cases in
<a href="https://github.com/Evryway/lir/blob/master/LargestInteriorRectangleTests.cs">LargestInteriorRectangleTests.cs</a>.
If you’re not sure how a function should work, try looking in there for an example.</p>
<h2 id="step1">3.1 Finding the smallest area rectangle</h2>
<p>The first process is to take all the vertices of the polygon (in this case, the vs) and work out the list of
unique x and y values. The simple solution is to use LINQ, like so:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">vs</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">[]</span> <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
<span class="n">xs</span> <span class="p">=</span> <span class="n">vs</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">v</span> <span class="p">=></span> <span class="n">v</span><span class="p">.</span><span class="n">x</span><span class="p">).</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">x</span> <span class="p">=></span> <span class="n">x</span><span class="p">).</span><span class="nf">Distinct</span><span class="p">().</span><span class="nf">ToArray</span><span class="p">();</span>
<span class="n">ys</span> <span class="p">=</span> <span class="n">vs</span><span class="p">.</span><span class="nf">Select</span><span class="p">(</span><span class="n">v</span> <span class="p">=></span> <span class="n">v</span><span class="p">.</span><span class="n">y</span><span class="p">).</span><span class="nf">OrderBy</span><span class="p">(</span><span class="n">y</span> <span class="p">=></span> <span class="n">y</span><span class="p">).</span><span class="nf">Distinct</span><span class="p">().</span><span class="nf">ToArray</span><span class="p">();</span>
</code></pre></div></div>
<p>but that isn’t the fastest method (we’re iterating the vs array twice, for starters) and, while “Distinct” does
indeed ensure uniqueness, it’s actually preferable to use an epsilon-check (two values within a small range)
instead of equality. The actual code I’m using is slightly more complex. First, duplicate the xs and ys
into two lists, and sort them.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">var</span> <span class="n">xsl</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">float</span><span class="p">>(</span><span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">ysl</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">float</span><span class="p">>(</span><span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">v</span> <span class="p">=</span> <span class="n">vs</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="n">xsl</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
<span class="n">ysl</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">xsl</span><span class="p">.</span><span class="nf">Sort</span><span class="p">();</span>
<span class="n">ysl</span><span class="p">.</span><span class="nf">Sort</span><span class="p">();</span>
</code></pre></div></div>
<p>Then, work out the largest range (either in the X or Y) and use that to determine an epsilon that is
scale relative - small enough to not alias the values arbitrarily, large enough to cater for whatever
range of input we’re working with. Uniqueness is ensured by testing values as they come in with the
previous value - as the list is sorted, this is a fairly fast test. Using something like a HashSet to
ensure uniqueness would mean testing every item on insert, which ends up fairly slow with hundreds of items
being tested each insert.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">float</span> <span class="n">xmin</span> <span class="p">=</span> <span class="n">xsl</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">xmax</span> <span class="p">=</span> <span class="n">xsl</span><span class="p">[</span><span class="n">xsl</span><span class="p">.</span><span class="n">Count</span> <span class="p">-</span> <span class="m">1</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">ymin</span> <span class="p">=</span> <span class="n">ysl</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">ymax</span> <span class="p">=</span> <span class="n">ysl</span><span class="p">[</span><span class="n">ysl</span><span class="p">.</span><span class="n">Count</span> <span class="p">-</span> <span class="m">1</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">mmin</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Min</span><span class="p">(</span><span class="n">xmin</span><span class="p">,</span> <span class="n">ymin</span><span class="p">);</span>
<span class="kt">float</span> <span class="n">mmax</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Max</span><span class="p">(</span><span class="n">xmax</span><span class="p">,</span> <span class="n">ymax</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">xsd</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">float</span><span class="p">>(</span><span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span> <span class="p">{</span> <span class="n">xsl</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">};</span>
<span class="kt">var</span> <span class="n">ysd</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="kt">float</span><span class="p">>(</span><span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span> <span class="p">{</span> <span class="n">ysl</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">};</span>
<span class="kt">float</span> <span class="n">epsilon</span> <span class="p">=</span> <span class="p">(</span><span class="n">mmax</span> <span class="p">-</span> <span class="n">mmin</span><span class="p">)</span> <span class="p">/</span> <span class="p">(</span><span class="m">1024</span> <span class="p">*</span> <span class="m">1024</span><span class="p">);</span> <span class="c1">// 1 millionth of the span.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">vs</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">xsl</span><span class="p">[</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">]</span> <span class="p">-</span> <span class="n">xsl</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">></span> <span class="n">epsilon</span><span class="p">)</span> <span class="n">xsd</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">xsl</span><span class="p">[</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ysl</span><span class="p">[</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">]</span> <span class="p">-</span> <span class="n">ysl</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">></span> <span class="n">epsilon</span><span class="p">)</span> <span class="n">ysd</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">ysl</span><span class="p">[</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">]);</span>
<span class="p">}</span>
<span class="n">xs</span> <span class="p">=</span> <span class="n">xsd</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">();</span>
<span class="n">ys</span> <span class="p">=</span> <span class="n">ysd</span><span class="p">.</span><span class="nf">ToArray</span><span class="p">();</span>
</code></pre></div></div>
<p>At this point, we’ve got our xs and ys, and our axis-aligned bounds (the min and max x and y values,
respectively). Optionally, you could explicitly search for the minimum bounding rectangle prior to
this step. The bounding rectangle may not be axis aligned, which would require a rotation of
the polygon points to achieve alignment with an axis-aligned bound. This is
likely, but not guaranteed, to help give a close to optimal LIR. More tests are needed here to determine
if the cost is worth it.</p>
<h3 id="step2">3.2 Construct the Cell grid</h3>
<p>Given an array of xs, and an array of ys, step 2 involves creating the cell grid. In actuality, this
is pretty simple. For every pair of entries in xs (x[i] to x[i+1]) and ys (y[j] to y[j+1]) you have a
cell cell[i,j] that is a rectangle. And we already have the xs and ys, so no need to duplicate those;
simply create an array of cells, ready to work with. Each cell in cells[i,j] represents the rectangle
from x[i] to x[i+1] in the horizontal axis, and from y[j] to y[j+1] in the vertical axis.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">var</span> <span class="n">xc</span> <span class="p">=</span> <span class="n">xs</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">yc</span> <span class="p">=</span> <span class="n">ys</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span>
<span class="n">cells</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="n">xc</span><span class="p">,</span><span class="n">yc</span><span class="p">];</span>
</code></pre></div></div>
<p>One thing to note is that, in C#, 2D arrays can perform slower than 1D arrays. The example code uses
2D arrays, but it’s fairly simple to convert the 2D indices to a 1D index - instead of using</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">var</span> <span class="n">q</span> <span class="p">=</span> <span class="n">cells</span><span class="p">[</span><span class="n">i</span><span class="p">,</span><span class="n">j</span><span class="p">];</span>
</code></pre></div></div>
<p>you could instead index into a 1D array like so:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">var</span> <span class="n">q</span> <span class="p">=</span> <span class="n">cells</span><span class="p">[(</span><span class="n">j</span><span class="p">*</span><span class="n">xc</span><span class="p">)</span> <span class="p">+</span> <span class="n">i</span><span class="p">];</span>
</code></pre></div></div>
<p>Slightly harder to read, slightly faster performance, possibly worth the trade-off.</p>
<p>The resulting cells are shown here - you can easily see how each vertex results in an x-line and
a y-line, extending to the bounding box of the polygon. In this example case, some of the vertices
share lines in X and Y with other vertices - and that’s ok.</p>
<p><img src="/assets/images/lir/lir_2.png" alt="Figure 2" title="Figure 2: cell grid lines" width="640px" /></p>
<h3 id="step3">3.3 (optionally) Refine the grid</h3>
<p>If there are not enough vertices in your simple polygon, you may end up with large cells that don’t come close
to the boundary of the polygon. One option here is to detect every intersection between a polygon edge and
the X/Y lines from each vertex, and then insert these new points into the vertex array (requiring a re-sort,
and then a regeneration of the cells). I’ve found that with enough points, this isn’t really necessary,
but it will give more accurate results (at the cost of potentially much more work). Note figure 4 for an
example where it’s clearly not refined enough.</p>
<p>Another alternative is to downsize the number of cells (by reducing the xs and ys arrays to fewer values,
for example). Again, I’ve not used this method - I found simply reducing the number of points in my original
polygon achieved perfectly adequate results for my use case. If your source polygon is heavily biased in
a particular direction in terms of vertex density, you might find cell reduction preferable to simply
stripping vertices from the polygon.</p>
<h3 id="step4">3.4 Scan the grid for interior and exterior cells</h3>
<p>Next, we have to look at every cell, to see if it’s inside the polygon (interior) or outside the polygon
(exterior). Any cell that is crossed by a polygon edge is classed as exterior.</p>
<p>The naive approach is to find the xs and ys indices for the start vertex and the end vertex of each edge.
Then, for every cell in that span, test the cell to see if it’s inside, crossed by, or outside, the
edge.</p>
<p>My implementation makes a few performance improvements. Firstly, because I have (potentially) aliased
some of the x and y values in xs and ys, not every vertex in the polygon will have an exact match - but
I know that if I iterate the edges, I’ll always start at the end of the previous edge (so I know the indices
into xs and ys for that vertex already). Secondly, I can use the direction the edge moves in the X and Y
axes to determine which direction I need to search in the xs and ys arrays, so be able to find the correct
indices - and as soon as I’m equal to (or greater than!) the vertex coordinates, I’ve got a span match.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">var</span> <span class="n">v0</span> <span class="p">=</span> <span class="n">vs</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">six</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">siy</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">eix</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="n">xs</span><span class="p">[</span><span class="n">eix</span><span class="p">]</span> <span class="p"><</span> <span class="n">v0</span><span class="p">.</span><span class="n">x</span> <span class="p">&&</span> <span class="n">eix</span> <span class="p"><</span> <span class="n">xs</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">)</span> <span class="n">eix</span><span class="p">++;</span>
<span class="kt">var</span> <span class="n">eiy</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="k">while</span> <span class="p">(</span><span class="n">ys</span><span class="p">[</span><span class="n">eiy</span><span class="p">]</span> <span class="p"><</span> <span class="n">v0</span><span class="p">.</span><span class="n">y</span> <span class="p">&&</span> <span class="n">eiy</span> <span class="p"><</span> <span class="n">ys</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">)</span> <span class="n">eiy</span><span class="p">++;</span>
</code></pre></div></div>
<p>The original implementation simply found the start and end indices by using xs.IndexOf(v0.x) and
ys.IndexOf(v0.y). This is no slower in the initial case (finding eix and eiy above), but because
I’m potentially skipping values in xs and ys due to the epsilon check, there would be cases where
the exact x or y coordinate would never be found. the while loops ensure we get close (close enough)
by simply taking the next larger value if the exact value isn’t found.</p>
<p>The same process is used to find the end indices for each subseqent edge - however, the edge could be
moving to the left, or down, inside the polygon, in which case we need to search backwards.</p>
<p>This optimisation made a big difference to the running time of the algorithm.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">vc</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">s</span> <span class="p">=</span> <span class="n">vs</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">e</span> <span class="p">=</span> <span class="n">vs</span><span class="p">[(</span><span class="n">i</span> <span class="p">+</span> <span class="m">1</span><span class="p">)</span> <span class="p">%</span> <span class="n">vc</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">edge</span> <span class="p">=</span> <span class="n">e</span> <span class="p">-</span> <span class="n">s</span><span class="p">;</span>
<span class="c1">// get the indices for the start - it should be the end vertex of the previous edge.</span>
<span class="n">six</span> <span class="p">=</span> <span class="n">eix</span><span class="p">;</span>
<span class="n">siy</span> <span class="p">=</span> <span class="n">eiy</span><span class="p">;</span>
<span class="c1">// could possibly binary search, but if the edge lengths are short, a linear scan</span>
<span class="c1">// should be fairly fast anyway.</span>
<span class="kt">int</span> <span class="n">tx</span> <span class="p">=</span> <span class="n">edge</span><span class="p">.</span><span class="n">x</span> <span class="p">>=</span> <span class="m">0</span> <span class="p">?</span> <span class="m">1</span> <span class="p">:</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="c1">// -1 or 1</span>
<span class="kt">int</span> <span class="n">ty</span> <span class="p">=</span> <span class="n">edge</span><span class="p">.</span><span class="n">y</span> <span class="p">>=</span> <span class="m">0</span> <span class="p">?</span> <span class="m">1</span> <span class="p">:</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="c1">// -1 or 1</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tx</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span><span class="n">xs</span><span class="p">[</span><span class="n">eix</span><span class="p">]</span> <span class="p"><</span> <span class="n">e</span><span class="p">.</span><span class="n">x</span> <span class="p">&&</span> <span class="n">eix</span> <span class="p"><</span> <span class="n">xs</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">)</span> <span class="n">eix</span><span class="p">++;</span> <span class="p">}</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span><span class="n">xs</span><span class="p">[</span><span class="n">eix</span><span class="p">]</span> <span class="p">></span> <span class="n">e</span><span class="p">.</span><span class="n">x</span> <span class="p">&&</span> <span class="n">eix</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="n">eix</span> <span class="p">-=</span> <span class="m">1</span><span class="p">;</span> <span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ty</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span><span class="n">ys</span><span class="p">[</span><span class="n">eiy</span><span class="p">]</span> <span class="p"><</span> <span class="n">e</span><span class="p">.</span><span class="n">y</span> <span class="p">&&</span> <span class="n">eiy</span> <span class="p"><</span> <span class="n">ys</span><span class="p">.</span><span class="n">Length</span><span class="p">-</span><span class="m">1</span><span class="p">)</span> <span class="n">eiy</span><span class="p">++;</span> <span class="p">}</span>
<span class="k">else</span> <span class="p">{</span> <span class="k">while</span> <span class="p">(</span><span class="n">ys</span><span class="p">[</span><span class="n">eiy</span><span class="p">]</span> <span class="p">></span> <span class="n">e</span><span class="p">.</span><span class="n">y</span> <span class="p">&&</span> <span class="n">eiy</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="n">eiy</span> <span class="p">-=</span> <span class="m">1</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// we now have a span.</span>
<span class="kt">var</span> <span class="n">span_x_start</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Min</span><span class="p">(</span><span class="n">six</span><span class="p">,</span> <span class="n">eix</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">span_y_start</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Min</span><span class="p">(</span><span class="n">siy</span><span class="p">,</span> <span class="n">eiy</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">span_x_end</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Max</span><span class="p">(</span><span class="n">six</span><span class="p">,</span> <span class="n">eix</span><span class="p">);</span>
<span class="kt">var</span> <span class="n">span_y_end</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Max</span><span class="p">(</span><span class="n">siy</span><span class="p">,</span> <span class="n">eiy</span><span class="p">);</span>
</code></pre></div></div>
<p>Now, given my spans, I know which cells come into contact (or proximity) of that particular edge.</p>
<p><img src="/assets/images/lir/lir_3.png" alt="Figure 3" title="Figure 3: Iterior and exterior cells for an edge" width="640px" /></p>
<p>Figure 3 shows the cells spanned by the lower rightmost edge (drawn in white). Exterior, or crossing, cells are marked as red.
Interior cells are marked as green. The bottom left two cells are marked as red - they are interior to the edge
we’re currently testing, but they have already been marked as exterior because they are crossed by a previous edge.</p>
<p>The interior / exterior check is fairly simple - work out the edge “into” direction (which is basically the edge normal,
in the direction of the polygon interior). We then pick the “best” cell corner to test against - if the “best” corner is
inside the polygon, the other three corners will be, too.</p>
<p>Horizontal and vertical edges are special cases, as they don’t actually cross any cells (so the span is zero length in
that axis). In this case, we simply identify the interior side, and mark the relevant polygons directly.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">var</span> <span class="k">into</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(-</span><span class="n">edge</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">edge</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">rx</span> <span class="p">=</span> <span class="k">into</span><span class="p">.</span><span class="n">x</span> <span class="p">>=</span> <span class="m">0</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ry</span> <span class="p">=</span> <span class="k">into</span><span class="p">.</span><span class="n">y</span> <span class="p">>=</span> <span class="m">0</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="c1">// for -x,-y edges, the into direction is +X, -Y - use the TL cell vertex to test. (0,1)</span>
<span class="c1">// for +x,-y edges, the into direction is +X, +Y - use the BL cell vertex to test. (0,0)</span>
<span class="c1">// for -x,+y edges, the into direction is -X, -Y - use the TR cell vertex to test. (1,1)</span>
<span class="c1">// for +x,+y edges, the into direction is -X, +Y - use the BR cell vertex to test. (1,0)</span>
<span class="c1">// it is possible for two edges to span the same cell, so we check to see if a cell</span>
<span class="c1">// has already been marked exterior - and if so, continue.</span>
<span class="c1">// this covers all of the cells that the edge crosses.</span>
<span class="c1">// if the edge is vertical or horizontal, the span width in that axis should be 0.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">span_x_end</span> <span class="p">-</span> <span class="n">span_x_start</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// vertical edge. pick the cells on the interior side.</span>
<span class="kt">var</span> <span class="n">p</span> <span class="p">=</span> <span class="n">span_x_start</span> <span class="p">-</span> <span class="n">rx</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="n">span_y_start</span><span class="p">;</span> <span class="n">q</span> <span class="p"><</span> <span class="n">span_y_end</span><span class="p">;</span> <span class="n">q</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">]</span> <span class="p"><</span> <span class="m">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">span_y_end</span> <span class="p">-</span> <span class="n">span_y_start</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// horizontal edge. pick the cells on the interior side.</span>
<span class="kt">var</span> <span class="n">q</span> <span class="p">=</span> <span class="n">span_y_start</span> <span class="p">-</span> <span class="n">ry</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="n">span_x_start</span><span class="p">;</span> <span class="n">p</span> <span class="p"><</span> <span class="n">span_x_end</span><span class="p">;</span> <span class="n">p</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">]</span> <span class="p"><</span> <span class="m">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="n">span_y_start</span><span class="p">;</span> <span class="n">q</span> <span class="p"><</span> <span class="n">span_y_end</span><span class="p">;</span> <span class="n">q</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="n">span_x_start</span><span class="p">;</span> <span class="n">p</span> <span class="p"><</span> <span class="n">span_x_end</span><span class="p">;</span> <span class="n">p</span><span class="p">++)</span>
<span class="p">{</span>
<span class="c1">// if we've already marked this as exterior, then skip it.</span>
<span class="c1">// it's possible to be interior to another edge, but still exterior</span>
<span class="c1">// to this one, so continue the check in that case.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">]</span> <span class="p"><</span> <span class="m">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="c1">// based on the edge direction, pick the correct corner to test against.</span>
<span class="kt">var</span> <span class="n">v</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="n">xs</span><span class="p">[</span><span class="n">p</span> <span class="p">+</span> <span class="n">rx</span><span class="p">],</span> <span class="n">ys</span><span class="p">[</span><span class="n">q</span> <span class="p">+</span> <span class="n">ry</span><span class="p">]);</span>
<span class="kt">var</span> <span class="n">sv</span> <span class="p">=</span> <span class="n">v</span> <span class="p">-</span> <span class="n">s</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">d</span> <span class="p">=</span> <span class="n">Vector2</span><span class="p">.</span><span class="nf">Dot</span><span class="p">(</span><span class="n">sv</span><span class="p">,</span> <span class="k">into</span><span class="p">);</span>
<span class="c1">// mark the cell either exterior (-1) or interior (1)</span>
<span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">=</span> <span class="n">d</span> <span class="p"><</span> <span class="m">0</span> <span class="p">?</span> <span class="p">-</span><span class="m">1</span> <span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We’ve now tested every cell that intersects an edge, or is adjacent to a horizontal or vertical edge.
That’s not all of the cells, however - we need a method to mark all of the exterior cells, and a method
to mark all of the interior cells.</p>
<p>Here, we start at the outside edges of every row (and column) of cells, and head inwards until we
find a previously marked cell. Anything that’s not already marked is exterior, right up to the point
we hit an interior cell.</p>
<p>Next, we mark everything that’s remaining as interior. <strong>this is not correct behaviour, as there are
cases which should be exterior which will be marked as interior.</strong> I only realised this as I was writing
this article, which goes to show that code review is a wonderful process. The correct behaviour could
be achieved by counting edge crossings, which would be more complex.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// outside region sweep.</span>
<span class="c1">// some cells may not be spanned by edges.</span>
<span class="c1">// start on the outside of the region, and mark everything as exterior, until</span>
<span class="c1">// we come across a cell that has been explicitly marked.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">q</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">q</span><span class="p">++)</span>
<span class="p">{</span>
<span class="c1">// from the left edge.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">x</span><span class="p">++</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// from the right edge.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="n">xc</span><span class="p">-</span><span class="m">1</span><span class="p">;</span> <span class="n">x</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span><span class="p">--)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">p</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">p</span><span class="p">++)</span>
<span class="p">{</span>
<span class="c1">// from the bottom edge.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">y</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// from the top edge.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="n">yc</span><span class="p">-</span><span class="m">1</span><span class="p">;</span> <span class="n">y</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span><span class="p">--)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// sweep for interior (untested) cells.</span>
<span class="c1">// in fact, mark everything.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">j</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="c1">// anything that was -1 goes to 0.</span>
<span class="c1">// anything that was 0 or 1 goes to 1.</span>
<span class="c1">// this ensures any un-tested cells are classed as interior.</span>
<span class="n">cells</span><span class="p">[</span><span class="n">i</span><span class="p">,</span><span class="n">j</span><span class="p">]</span> <span class="p">=</span> <span class="n">cells</span><span class="p">[</span><span class="n">i</span><span class="p">,</span><span class="n">j</span><span class="p">]</span> <span class="p"><</span> <span class="m">0</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="m">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The result of this sweep should be that every cell is marked interior or exterior. Figure 4
shows this for our sample polygon.</p>
<p><img src="/assets/images/lir/lir_4.png" alt="Figure 4" title="Figure 4: All Interior and Exterior cells" width="640px" /></p>
<p>You can clearly see in this image that the top row of the interior cells does not actually touch the
top edges of the polygon, and could be moved upwards. <strong>My implementation doesn’t perform this adjustment,
and it would likely be a worthwhile improvement</strong>. However, the more vertices there are in the polygon,
the more accurate (or close to the edge) the contacts become.</p>
<h3 id="step5">3.5 Determining cell adjacency</h3>
<p>The paper I’m using as a reference next describes determining cell adjacency.
I’ll describe the process, as I have implemented it - but I’ve found a slightly
faster mechanism which I’m using to help calculate the areas which I’ll describe next.</p>
<p>The first action is to create two adjacency arrays - one for horizontal adjacency, and one for
vertical adjacency. Next, every row (and every column) is scanned to calculate the maximum
adjacency length for each cell.</p>
<p>Every cell knows how many cells can be traversed going to the right (+X) - and how many cells can
be traversed going up (+Y). If a cell has no interior neighbours, it will have no adjacency.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">var</span> <span class="n">adjacency_horizontal</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="n">xc</span><span class="p">,</span> <span class="n">yc</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">adjacency_vertical</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">int</span><span class="p">[</span><span class="n">xc</span><span class="p">,</span> <span class="n">yc</span><span class="p">];</span>
<span class="c1">// calculate horizontal adjacency, row by row</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">y</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="n">xc</span><span class="p">-</span><span class="m">1</span><span class="p">;</span> <span class="n">x</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span><span class="p">--)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="n">span</span><span class="p">++;</span> <span class="k">else</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">adjacency_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// calculate vertical adjacency, column by column.</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">x</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="n">yc</span><span class="p">-</span><span class="m">1</span><span class="p">;</span> <span class="n">y</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span><span class="p">--)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">></span> <span class="m">0</span><span class="p">)</span> <span class="n">span</span><span class="p">++;</span> <span class="k">else</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">adjacency_vertical</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The next step in the algorithm is to examine every cell, and create two vectors (called H and V)
that describe how many cells can be traversed horizontally by each cell vertically above the
root cell (these values go into the H vector); and how many cells can be traversed vertically
by each cell to the right of the root cell (these values go into the V vector).</p>
<p>It took me a little while to understand the value of these two vectors, so here’s an image
that shows the numbers for a specific cell.</p>
<p><img src="/assets/images/lir/lir_5.png" alt="Figure 5" title="Figure 5: H and V Vectors" width="640px" /></p>
<p>Figure 5 shows an example cell as a root. The H vector describes how many cells can
be traversed horizontally, each step up from the root cell -
in this case, 3, then 2, then 1, 1, 1, then 3. the V vector describes how
many cells can be traversed vertically, each step to the right from the root cell - in this case,
6, then 2, then 1.</p>
<p>this gives us an H of [3,2,1,1,1,3] and a V of [6,2,1].</p>
<p>However, if we’re trying to make the largest rectangle possible, we are limited by the maximum
traversal of all of the rows underneath the relevant row, and all the columns to the left of
the relevant column - which means the very top value of the H vector needs to be clamped to a 1,
giving the H vector as [3,2,1,1,1,1].</p>
<p><img src="/assets/images/lir/lir_6.png" alt="Figure 6" title="Figure 6: Valid large rectangles" width="640px" /></p>
<p>The result of combining the V and H vectors in this example gives the three possible large
rectangles that can extend from the cell - one that is (1 by 6), one that is (2 by 2) and one that
is (3 by 1). In Figure 6, these are shown as yellow, cyan and magenta outlines.</p>
<p>The clamped cells on the top row are shown in red - it’s clear that you can’t create a rectangle
containing only interior cells that includes them, because the rows below constrain movement
to the right.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// generate H vector - this is horizontal adjacency for each step up.</span>
<span class="n">clir_hvec</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// look at horizontal adjacency.</span>
<span class="c1">// step up from our initial cell, and look right.</span>
<span class="kt">var</span> <span class="n">h</span> <span class="p">=</span> <span class="n">adjacency_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">];</span>
<span class="n">clir_hvec</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">h</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="n">y</span><span class="p">+</span><span class="m">1</span><span class="p">;</span> <span class="n">q</span> <span class="p"><</span> <span class="n">ayc</span><span class="p">;</span> <span class="n">q</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">q</span><span class="p">]</span> <span class="p">!=</span> <span class="m">1</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="c1">// each row can only be as large as the previous - a rectangle cannot push</span>
<span class="c1">// further out than a lower row.</span>
<span class="n">h</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Min</span><span class="p">(</span><span class="n">adjacency_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">q</span><span class="p">],</span> <span class="n">h</span><span class="p">);</span>
<span class="n">clir_hvec</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">h</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// generate V vector. This is vertical adjacency for each step right.</span>
<span class="n">clir_vvec</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// look at vertical adjacency.</span>
<span class="c1">// step right from our initial cell, and look up.</span>
<span class="kt">var</span> <span class="n">v</span> <span class="p">=</span> <span class="n">adjacency_vertical</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">];</span>
<span class="n">clir_vvec</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="n">x</span><span class="p">+</span><span class="m">1</span><span class="p">;</span> <span class="n">p</span> <span class="p"><</span> <span class="n">axc</span><span class="p">;</span> <span class="n">p</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="p">!=</span> <span class="m">1</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="c1">// each column can only be as large as the previous - a rectangle cannot push</span>
<span class="c1">// further up than a previous column.</span>
<span class="n">v</span> <span class="p">=</span> <span class="n">Mathf</span><span class="p">.</span><span class="nf">Min</span><span class="p">(</span><span class="n">adjacency_vertical</span><span class="p">[</span><span class="n">p</span><span class="p">,</span> <span class="n">y</span><span class="p">],</span> <span class="n">v</span><span class="p">);</span>
<span class="n">clir_vvec</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="step6">3.6 Determine the largest potential spanned area for each cell</h3>
<p>You can also see that every time you “step” in one axis, you have to “step” in the other axis
at the same time. It’s possible to jump more than one cell in each direction, but you have to
move at least one cell (because, for example, if the H vector was [3,3,1,1,1,1], then the equivalent
V vector would be [6,2] - and the resulting span rectangles would be (1 by 6) and (3 by 2).</p>
<p>This insight means you’re actually working with only the unique values in either case - so
going back to our original H vector, it results in [3,2,1] against the V of [6,2,1].
Reversing either vector lets you create the spans by zipping the vectors, so for example reversing
V to [1,2,6] gives a set of spans of (3 by 1), (2 by 2) and (1 by 6). That’s our spans set!</p>
<p>This code does not reduce the H vector to only the unique values - but it does check that the
spans themselves are unique, which actually gives the same results.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">spans</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// generate the set of valid spans.</span>
<span class="n">int2</span> <span class="n">span_last</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">int2</span><span class="p">(-</span><span class="m">1</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">clir_hvec</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="n">hvec</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="n">vvec</span><span class="p">[</span><span class="n">p</span><span class="p">-</span><span class="m">1</span><span class="p">];</span>
<span class="n">int2</span> <span class="n">span</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">int2</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">span</span><span class="p">.</span><span class="n">x</span> <span class="p">!=</span> <span class="n">span_last</span><span class="p">.</span><span class="n">x</span> <span class="p">&&</span> <span class="n">span</span><span class="p">.</span><span class="n">y</span> <span class="p">!=</span> <span class="n">span_last</span><span class="p">.</span><span class="n">y</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">spans</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">span</span><span class="p">);</span>
<span class="n">span_last</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Given our spans set, we can trivially work out the area for each span, because we know the
dimensions for each of the rectangles. Keeping track of the best area allows us to return the
best found area in the whole cell set.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">spans</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">span</span> <span class="p">=</span> <span class="n">clir_spans</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">xstart</span> <span class="p">=</span> <span class="n">xs</span><span class="p">[</span><span class="n">x</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">xend</span> <span class="p">=</span> <span class="n">xs</span><span class="p">[</span><span class="n">x</span> <span class="p">+</span> <span class="n">span</span><span class="p">.</span><span class="n">x</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">ystart</span> <span class="p">=</span> <span class="n">ys</span><span class="p">[</span><span class="n">y</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">yend</span> <span class="p">=</span> <span class="n">ys</span><span class="p">[</span><span class="n">y</span> <span class="p">+</span> <span class="n">span</span><span class="p">.</span><span class="n">y</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">xsize</span> <span class="p">=</span> <span class="n">xend</span> <span class="p">-</span> <span class="n">xstart</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">ysize</span> <span class="p">=</span> <span class="n">yend</span> <span class="p">-</span> <span class="n">ystart</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">area</span> <span class="p">=</span> <span class="n">xsize</span> <span class="p">*</span> <span class="n">ysize</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">area</span> <span class="p">></span> <span class="n">best_area</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">best_area</span> <span class="p">=</span> <span class="n">area</span><span class="p">;</span>
<span class="n">best_span</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="n">best_origin</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">int2</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="step7">3.7 Iterate and calculate areas directly from cell side lengths, rather than spans</h3>
<p>Noting that all potential largest interior rectangles result in equal length H and V vectors,
and all distances are known before we generate the spans, means we can actually work directly with
the span lengths (instead of calculating spans and then working back to the lengths).</p>
<p>The code is very similar to the span sweep code in 3.5 and 3.6. First, calculate the
lengths of spans (instead of adjacency).</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kt">var</span> <span class="n">lengths_horizontal</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">float</span><span class="p">[</span><span class="n">xc</span><span class="p">,</span><span class="n">yc</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">lengths_vertical</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">float</span><span class="p">[</span><span class="n">xc</span><span class="p">,</span><span class="n">yc</span><span class="p">];</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">y</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">float</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="n">xc</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">x</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span><span class="p">--)</span>
<span class="p">{</span>
<span class="n">span</span> <span class="p">=</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="p"><=</span> <span class="m">0</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="n">span</span> <span class="p">+</span> <span class="n">xs</span><span class="p">[</span><span class="n">x</span> <span class="p">+</span> <span class="m">1</span><span class="p">]</span> <span class="p">-</span> <span class="n">xs</span><span class="p">[</span><span class="n">x</span><span class="p">];</span>
<span class="n">lengths_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">x</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">float</span> <span class="n">span</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="n">yc</span> <span class="p">-</span> <span class="m">1</span><span class="p">;</span> <span class="n">y</span> <span class="p">>=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span><span class="p">--)</span>
<span class="p">{</span>
<span class="n">span</span> <span class="p">=</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">]</span> <span class="p"><=</span> <span class="m">0</span><span class="p">)</span> <span class="p">?</span> <span class="m">0</span> <span class="p">:</span> <span class="n">span</span> <span class="p">+</span> <span class="n">ys</span><span class="p">[</span><span class="n">y</span> <span class="p">+</span> <span class="m">1</span><span class="p">]</span> <span class="p">-</span> <span class="n">ys</span><span class="p">[</span><span class="n">y</span><span class="p">];</span>
<span class="n">lengths_vertical</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">=</span> <span class="n">span</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then, iterate every cell, using the vertical and horizonal lengths directly.</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">y</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">y</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">y</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">x</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">x</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">x</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">iv</span> <span class="p">=</span> <span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iv</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">h</span> <span class="p">=</span> <span class="n">lengths_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">];</span>
<span class="kt">var</span> <span class="n">v</span> <span class="p">=</span> <span class="n">lengths_vertical</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">];</span>
<span class="c1">// if the best POSSIBLE area (which may not be valid!)</span>
<span class="c1">// is smaller than the best area, then we don't need to run any further tests.</span>
<span class="k">if</span> <span class="p">(</span><span class="n">h</span> <span class="p">*</span> <span class="n">v</span> <span class="p"><</span> <span class="n">best_area</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="c1">// generate H vector - this is horizontal spans for each step up.</span>
<span class="n">hspans</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// look at horizontal spans.</span>
<span class="c1">// step up from our initial cell, and look right.</span>
<span class="n">hspans</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">h</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">q</span> <span class="p">=</span> <span class="n">y</span><span class="p">+</span><span class="m">1</span><span class="p">;</span> <span class="n">q</span> <span class="p"><</span> <span class="n">yc</span><span class="p">;</span> <span class="n">q</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">]</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">h2</span> <span class="p">=</span> <span class="n">lengths_horizontal</span><span class="p">[</span><span class="n">x</span><span class="p">,</span><span class="n">q</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">h2</span> <span class="p">>=</span> <span class="n">h</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="n">h</span> <span class="p">=</span> <span class="n">h2</span><span class="p">;</span>
<span class="n">hspans</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">h</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// generate V vector. This is vertical spans for each step right.</span>
<span class="n">vspans</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// look at vertical spans.</span>
<span class="c1">// step right from our initial cell, and look up.</span>
<span class="n">vspans</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">p</span> <span class="p">=</span> <span class="n">x</span><span class="p">+</span><span class="m">1</span><span class="p">;</span> <span class="n">p</span> <span class="p"><</span> <span class="n">xc</span><span class="p">;</span> <span class="n">p</span><span class="p">++)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cells</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">]</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="kt">var</span> <span class="n">v2</span> <span class="p">=</span> <span class="n">lengths_vertical</span><span class="p">[</span><span class="n">p</span><span class="p">,</span><span class="n">y</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">v2</span> <span class="p">>=</span> <span class="n">v</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
<span class="n">v</span> <span class="p">=</span> <span class="n">v2</span><span class="p">;</span>
<span class="n">vspans</span><span class="p">.</span><span class="nf">Add</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// reverse the v spans list - this lets us trivially combine the correct</span>
<span class="c1">// spans for each rectangle combination with the same list index.</span>
<span class="n">vspans</span><span class="p">.</span><span class="nf">Reverse</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p"><</span> <span class="n">hspans</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
<span class="p">{</span>
<span class="kt">float</span> <span class="n">hl</span> <span class="p">=</span> <span class="n">hspans</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">vl</span> <span class="p">=</span> <span class="n">vspans</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">float</span> <span class="n">area</span> <span class="p">=</span> <span class="n">hl</span> <span class="p">*</span> <span class="n">vl</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">area</span> <span class="p">></span> <span class="n">best_area</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">best_area</span> <span class="p">=</span> <span class="n">area</span><span class="p">;</span>
<span class="n">best_origin</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="n">xs</span><span class="p">[</span><span class="n">x</span><span class="p">],</span> <span class="n">ys</span><span class="p">[</span><span class="n">y</span><span class="p">]);</span>
<span class="n">best_span</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">Vector2</span><span class="p">(</span><span class="n">hl</span><span class="p">,</span> <span class="n">vl</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>One issue I found was generating the H and V vectors occasionally gave results where the vector
lengths were mismatched (for example H would contain 16 values and V would contain 15). After matching
up the results to the Adjacency Span version, I found this was due to numerical inaccuracies, and
introduced the epsilon check when generating the initial xs and ys arrays. Values in there that are
incredibly close together can result in identical lengths in the scan code above. In the case where
the vectors come through mismatched, using the adjacency data instead is an option.</p>
<p>Finally, the check to see if the largest possible area was already smaller than the best area made
a huge difference to the runtime of the algorithm. There may be other good optimizations sitting in
there still.</p>
<h1 id="4-conclusion">4. Conclusion</h1>
<p>And there you have it - <a href="https://github.com/Evryway/lir">Axis Aligned Largest Interior Rectangle</a>. I hope someone finds this as useful
as I will, and I’d love any feedback on the code and writeup.</p>
<h1 id="references">References</h1>
<p><a href="https://dash.harvard.edu/bitstream/handle/1/27030936/tr-22-95.pdf">Ref 1: Finding the Largest Rectangle in Several Classes of Polygons (Karen Daniels, Victor Milenkovic, Dan Roth)</a>
<a href="https://journals.ut.ac.ir/article_71280.html">Ref 2: Algorithm for finding the largest inscribed rectangle in polygon (Zahraa Marzeh, Maryam Tahmasbi, Narges Mirehi)</a></p>This article presents a solution to the problem of finding the axis aligned largest interior rectangle inside a simple polygon. The solution is implemented using C# in Unity 3D. There’s some mathematics, some pictures, and a link to a fully working implementation.A tale of two problems - Solving the problem of solving the problem2020-09-14T00:00:00+00:002020-09-14T00:00:00+00:00http://www.evryway.com/a-tale-of-two-problems<p>Often, a solution to a problem is out there - but a working implementation needs to be constructed to meet the needs of
the user. Taking that solution and making it useable can be problematic itself. This article details the process
involved in solving such a problem on the road to creating a working implementation.</p>
<h2 id="abstract">Abstract</h2>
<p>Finding the solution to a software problem is often as simple as downloading a package, or cloning a repository.
Using someone else’s hard work is a very effective way to solve some of the hardest problems. But what do you do
if you can’t find a solution that already exists?</p>
<p>Sometimes, the answer is to create the software. This article talks about that process - researching existing
solutions, understanding details found in research papers, creating a working implementation, and finally sharing
that solution.</p>
<p>I’m writing this article in the style of a journal entry. The reasons for this will hopefully become clear to you
as the article progresses. I will be discussing some of the issues I’ve found while creating both the
<a href="/largest-interior/">implementation</a> and the documentation for that implementation.</p>
<h2 id="1-introduction">1. Introduction</h2>
<p>Solving a problem can be hard. Using someone else’s solution can be a problem in itself. In this article I’ll
discuss a specific problem I’ve recently tried to solve, and the challenges that process brings.</p>
<p>When I first started working full-time on VR applications in Unity back in 2016, many of the tools (both hardware and
software) were in a state of flux. Over time, the tools have somewhat stabilised, but there are still moments when
a change breaks something fairly fundamental, and needs a proper solution. Unity’s XR Plugin architecture changes
brought me one of those breaking changes - namely, losing access to some of the boundary data.</p>
<p>When the boundary data information coming back from the Unity API changed, and I no longer had access to the
“Play Area” rectangle, my first instinct was to say “<em>That’s fine - I’ll work out the rectangular area myself.
How hard can it be?</em>”</p>
<p>It turns out - pretty hard. Non-trivial, in fact.</p>
<p>As I went through the process of trying to find a simple solution, I also went through the process of researching
how other people have solved this problem, and spent a fair few days looking at journal papers describing
those solutions. For many of the problems I’ve been trying to solve over the last few years, this is a fairly
standard process for me. I try and clarify to myself the problem I’m attempting to solve. I then try and learn
what it’s actually called by everyone else in the world (which may or not bear a small resemblance to the words
and terms I am already aware of). Then I try and find working code examples (in C# first, as it’s my current
favourite language - then in Python, then in any other C-like language, then in any other programming language,
then finally in some human language like English, often with mathematical equations).</p>
<p>This has lead me to a startling insight:</p>
<h4 id="the-best-documentation-for-a-solved-software-problem-is-a-working-implementation-with-source-code">The best documentation for a solved software problem is a working implementation with source code.</h4>
<p>There are <em>many</em> kinds of documents describing methods to solve mathematical and numerical problems, but
a working implementation not only tells you how to solve it - it does solve it.</p>
<p>On the other hand, a badly scanned PDF of a journal paper detailing an abstract solution does not solve the problem.
In fact, it often introduces more problems along the way. And that is, of course, if you’re lucky enough to
have access to the paper in the first place.</p>
<p>The problem of solving a problem is tractable, but (as far as I’m aware) not discussed as much as
I believe it should be.</p>
<h2 id="2-background">2. Background</h2>
<p><a href="/largest-interior/">The specific problem I was aiming to solve</a> over the last month (August 2020) was to take a set of points in
a 2D plane which describe the boundary of a simple polygon, and from those points, calculate the <em>Largest
interior rectangle (LIR)</em> that is contained by that <em>simple polygon</em>. It’s a subset of the
<a href="https://en.wikipedia.org/wiki/Largest_empty_rectangle">Largest Empty Rectangle</a> problem.</p>
<p>The first port of call to solve all of these problems is an internet search. You might find a repository
on Github that has a working implementation of a solution to the problem in a language you can use. If there’s
not a source option available, you might find a library somewhere that solves it. After that, you’re probably going to
need to look at papers, other people’s research, and actually roll up some sleeves and get to work.</p>
<p>Even if you do manage to find relevant research, though - the act of reading, understanding and using that
research is, in itself, a non-trivial process.</p>
<h3 id="21-finding-the-correct-research">2.1. Finding the correct research</h3>
<p><img src="/assets/images/lir/prob_pdf_link.png" alt="find a pdf" class="imgcenter" width="640px" /></p>
<p>Most of the papers I’ve tried to read (relating to the LIR problem) <a href="https://dash.harvard.edu/bitstream/handle/1/27030936/tr-22-95.pdf">such as this one</a> are found as a PDF
of a journal paper. Fortunately, in this case it’s a Harvard paper, and there is a direct link available.
Many of the papers that it references are not directly available. If you’re lucky, there will be a PDF
freely available at a site such as
<a href="https://www.researchgate.net/about">ResearchGate</a> or <a href="https://www.sciencedirect.com/">ScienceDirect</a>.</p>
<p><img src="/assets/images/lir/purchase_article.png" alt="purchase article" class="imgcenter" width="640px" /></p>
<p>If you’re unlucky, you’ll run into a referenced paper that you need to pay for. Prices vary, but I’ve seen
as high as $40 before. I’m sure that some of these articles are worth the money, but hitting that paywall is
a huge roadblock when you’re trying to explore a subject.</p>
<p>This chain of references is necessary to build the accumulation of knowledge required to understand the contents
of the paper. If you can’t follow the reference chain, you’re unlikely to be able to accumulate the knowledge.
I’ve always thought of this accumulated knowledge as a “base” or “foundation”, but that’s a terrible metaphor for
accumulated knowledge. It’s really much closer to a tree, with some form of a shared trunk of knowledge
and many twisting branches, often interpenetrating, growing and spreading from that shared trunk.</p>
<p>Some shared basis is always required (for example, algebra, or calculus, or physics - and more specifically,
some subset of that domain, for example 3D geometry, or matrix mathematics). Often, this domain knowledge
is assumed (rather than explicitly stated). If you don’t have a solid understanding of a domain, then you
may need to dive off elsewhere and learn about that domain - which in iself takes time.</p>
<p>For the LIR problem, I needed to refresh my understanding of
<a href="https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors">Eigenvectors, Eigenspaces and Eigenvalues.</a>
I vaguely remembered covering these in sixth form, and all that knowledge had subsequently dropped out of my brain.
That refresh process involved repeated viewings of <a href="https://www.youtube.com/watch?v=pZ6mMVEE89g">Khan Academy videos</a>,
other web pages with examples, and coding up test cases in Unity. I found the linked Wikipedia article
incredibly dense, and mostly useless as a learning tool.</p>
<p>Finding and exploring the tree of knowledge relevant to your problem space is just the very start.</p>
<h3 id="22-finding-the-correct-terminology">2.2. Finding the correct terminology</h3>
<p><img src="/assets/images/lir/interior_image.png" alt="find a pdf" class="imgcenter" width="480px" /></p>
<p>If you don’t manage to find a good starting point on your first (or second, or tenth) attempt, you may need
to change the terms you’re searching for. Largest Interior Rectangle? Maximum Empty Rectangle? Biggest Interior
Polygon? Contained Bound In Polygon? Some or all of these may get you on to a branch of the research tree. If
you don’t luck out on search terms, you might find yourself dangling on a tiny branch, hunting for a solution
that’s just out of your grasp.</p>
<p>Once you’re on the tree, you might find that the examples you use make specific assumptions about the terminology
that you’re unaware of. For example, all of the related links might happen to use the same names, but not
the (slightly different) naming structure used in a different industry. Or the order of operations might be
implied to be a certain way round (where everyone else does it the other way around).</p>
<p>Baked into the domain may be a set of assumptions about terms and terminology that make absolutely no sense
when examined from the outside. For example, why are loops in computer programs often using the “i” variable?
It could be because it’s the first letter of “index”. It could be because mathematical notation often uses
i in formulas. It’s certainly never been clearly explained to me, but it is incredibly pervasive. As you
begin to uncover the terminology of a problem domain, these assumptions about terms and terminology crop
up everywhere, and finding a clear understanding of those most basic parts of a problem description is often
in itself a voyage of discovery.</p>
<p>I used the term “<a href="https://en.wikipedia.org/wiki/Sixth_form">sixth form</a>” above - a trivial example
of a domain assumption. If you are a reader from a country outside of the UK, you may have no idea what it
means, or how it relates to your normal educational age ranges. As a UK reader, you probably saw the words,
implicitly understood “the point when he was studying A-Levels”, and carried on with the article.</p>
<h3 id="23-errors-and-omissions">2.3. Errors and omissions</h3>
<p><img src="/assets/images/lir/fig5fig6.png" alt="fig5fig6" title="A human error" class="imgcenter" width="480px" /></p>
<p>If you do manage to climb the knowledge tree to the point where you understand what you’re trying to achieve,
understand the method described in the paper, and feel as though you are ready to progress to an implementation,
you’re about to hit yet another human part of the process - errors and omissions. Pretty much every paper
I’ve read over the last month contains typical human errors. Spelling mistakes. Mis-labelled images.
Out-of-order steps and figures. Examples using one way of doing things (for example “Left then Right”) in one
paragraph, then followed by another way (“Right then Left”) in the next paragraph.</p>
<p>Human error is both understandable and inevitable. One way to mitigate errors is to have someone else
look over your work (which is an assumed part of the academic journal process). It’s unlikely that you
will catch all errors. A better way is to be able to share a document (or an implementation) and allow
others the ability to change it directly (for example, a pull request on a repository, or a wikipedia edit).</p>
<p>Similarly, domain knowledge may simply be elided or ignored entirely. “Left as an exercise for the reader”
crops up occasionally, which may be helpful in a text book, but certainly doesn’t make constructing an
implementation any easier. “This is simple and needs no more explanation” also crops up occasionally,
which may be the case for the authors, but is rarely the case for a reader. (If it were that simple, a comment
to that effect would be unnecessary).</p>
<h3 id="24-scans-cut-and-paste-mathematical-equations-and-symbols">2.4. Scans, Cut-and-paste, Mathematical equations and symbols</h3>
<p><img src="/assets/images/lir/stonehenge_p.png" alt="stonehenge_p" title="Is i going into that tunnel?" class="imgcenter" width="480px" /></p>
<p>On the off chance you’ve found the perfect paper, you decide to follow the chain of references, and you click
on the link. Nothing happens - because it’s not a link. It’s a line of text, in a non-hypertext document.
If the paper was written in 1970, this can be excused. If it was written post 1990, then it really should be
a document leveraging the best display and linkage options available.</p>
<p>Many of the papers I’ve read over the last month are in a format such that you cannot cut and paste from the document.
Many of the papers don’t have hyperlinks for their references. Often, the papers contain mathematical equations,
and if those equations contain certain (domain-typical) symbols, finding what that symbol actually means is,
itself, a voyage of discovery. Even if you can understand the symbols, understanding the equations themselves
may require specific domain knowledge which can take time and effort to acquire.</p>
<p>I’m not arguing that a mathematical equation is a bad way to represent mathematics. I do believe, though,
that examples can be shown in the specific, which (for me at least) give some kind of context to the
equation, and possibly more insight into the meaning of operator symbols or naming symbols.</p>
<h2 id="3-how-to-make-your-research-more-accessible">3. How to make your research more accessible</h2>
<p>As a person creating some documented research, you are undoubtedly under a variety of pressures - usually with
the need to justify the research money way up at the top, possibly in tandem with the need to fight for
a grant, or a job role, or many other things not directly connected to the actual research itself. These
pressures are expected, and I have no silver bullet to help you relieve them. If you want your research to
really have an impact, though, here’s some steps you can endeavour to take which will help make your research
useable by future generations. If you can do any of these things, you’re helping.</p>
<ol>
<li>Make your research freely available.</li>
<li>Where you can, make it searchable and selectable (including equations).</li>
<li>Provide specific examples alongside general formulae.</li>
<li>Provide a code repository with a working implementation.</li>
<li>Provide links to other resources where possible (not just references).</li>
<li>Make your research a living document (and open it to collaborators).</li>
</ol>
<p>What’s the payback, you ask? Immortality. Betterment of the human condition. And possibly, fame and fortune.
What’s not to like?</p>
<h2 id="4-conclusion">4. Conclusion</h2>
<p>If you’re making a paper detailing a solution to a problem, the two core factors are these:</p>
<h3 id="you-are-advancing-human-understanding-by-solving-a-problem">You are advancing human understanding by solving a problem</h3>
<p>and,</p>
<h3 id="you-wish-to-share-that-solution-with-others">You wish to share that solution with others.</h3>
<p>There may be a hundred other competing priorities, but you should try not to let them stand in the way of
the best possible version of your work that satisfies these core factors. Once you have made your work
as available and user-friendly as possible, people will be able to carry that work forward and use it
solving problems you might never have expected.</p>
<p>I finally struggled through the knowledge tree to the point of being able to construct a working implementation,
and in the spirit of this whole post, I’ve also made as much effort as I reasonably can to make it useful
and useable by any interested party.</p>
<p><a href="/largest-interior/">If you’re interested in the implementation details for my solution, please take a look here.</a></p>
<h1 id="references">References</h1>Often, a solution to a problem is out there - but a working implementation needs to be constructed to meet the needs of the user. Taking that solution and making it useable can be problematic itself. This article details the process involved in solving such a problem on the road to creating a working implementation.Oculus officially transitions to Facebook2020-08-20T00:00:00+00:002020-08-20T00:00:00+00:00http://www.evryway.com/Oculus<h2 id="that-fateful-hateful-moment-is-here-oculus-is-now-facebook">That fateful, hateful moment is here. Oculus is now Facebook.</h2>
<p>Yeah, I’m pissed off. And on the face of it, I’m being unreasonably angry.</p>
<p>Hey, I know. It was obvious all along, right? Oculus really is just Facebook by another brand, has been for years.
Expecting Oculus <em>not</em> to use a Facebook ID to log in to your developer account, or
have access to multiplayer, is just plain crazy talk. Why so serious, Tim?</p>
<p>Except it wasn’t always that way.</p>
<p>Let’s go on a quick Trip down Memory Lane, and look at what the world of VR was like when I first got properly interested.
Maybe that’ll explain some of the (I believe justified) horror at the moment finally arriving. Buckle in!</p>
<h2 id="oculus-rift---the-first-one">Oculus Rift - the first one.</h2>
<p>In 2012, There was a Kickstarter for a device called the Oculus Rift. A lot of the good folks at BossAlien were, at the time,
super-stoked with the idea of actually playing around with a proper VR headset.</p>
<p>So, we backed the Kickstarter (to the tune of four headsets in total, if I recall correctly), sat back, and waited.
Around the end of August 2012, monies were taken from accounts, grins were fixed in huge smiles, and we all sat back and
waited a little more.</p>
<p>And waited.</p>
<p>But not too long, by Kickstarter standards - because on March 29th, 2013, the Devkits started shipping.
That’s the <a href="https://en.wikipedia.org/wiki/Oculus_Rift#Development_Kit_1">Oculus Rift DK1</a>, to anyone in the 2020s - but of course at the time, like those in the Great War - there
was only one.</p>
<p>and it was INCREDIBLE.</p>
<p><a href="https://xinreality.com/wiki/Oculus_Rift_DK1"><img src="/assets/2020_oculus/dk1.jpg" alt="Rift DK1" /></a></p>
<p>640*800 per eye resolution.
3DOF tracking.
Looks like a brick under non-linear transformation.</p>
<p>The best thing, though?</p>
<p>It worked. It actually worked. It was a VR headset, it cost under $400 per unit, it did the stereo display thing,
and you could write software that would actually run on it.</p>
<p>Sure, it wasn’t 6DOF. There were no controllers (unless/until you plumped for hydras, which were also INCREDIBLE).</p>
<p>And the day those devkits arrived was the day I realised that VR was actually going to happen (soon) and it was going to be awesome.
Because up until that point, it had all been personal science fiction - but it was now actually science fact. And I had one of them,
sitting in my lap.</p>
<p>2012 and 2013 were hella years for me, btw - I’m having to look this stuff up, because more important things than this
were making the daily rounds, but looking back on it, it’s pretty pivotal none-the-less.</p>
<p>This is truly the stuff that dreams are made of, because while the functionality of that headset was terribad in just about
every way that mattered (FOV, framerate, tracking, input, latency, pipeline, platform support, controllers, ergonomics,
oh my god the list is so long …) It gave everyone a reference point and said “go.” And all of those points have since
been addressed, in spades, in under a decade.</p>
<p>Crucially though - it had been financed by US. It almost felt as though it had been built by US. The collective US
that backed the kickstarter. WE, the people of VR-land. The ones who dreamed, and took a punt, and got lucky.</p>
<p>Carmack joined. “Crystal Cove” was discussed. Prototypes were prototyped. Eve Valkyrie was announced.</p>
<h2 id="oculus-rift-two---electric-boogaloo">Oculus Rift two - electric boogaloo</h2>
<p>And then 2014 happened. Two things in VERY quick succession, in fact.</p>
<p><strong>March 19th 2014</strong>, we got to see this :</p>
<iframe width="560" height="315" src="//www.youtube.com/embed/OlXrjTh7vHc" frameborder="0" allowfullscreen=""></iframe>
<p>That’s right. DK2, my peeps. Oh man - the first, proper, awesome, 6DOF headset.</p>
<p>And then exactly one week later, <strong>March 26th 2014</strong>, we get the next update (#52):</p>
<h3 id="oculus-joins-facebook">Oculus Joins Facebook.</h3>
<p>Now at this point, every right minded person is recoiling, but still horribly, passionately interested into getting their hands
on a DK2. Because WE MADE IT, yah hear? it was OURS. But Facebook was buying it out from under us.</p>
<p>And if memory serves me, we didn’t need to put in any more money at this point - we were going to get a DK2 at some point,
Facebook was about to drop a metric ton of cash into Oculus, Abrash was about to join …</p>
<p>So what do you do? Do you sit tight and get a DK2, or do you take a moral position of “Facebook is a creeping literal evil,
helmed by an amoral billionaire who enjoys selling the secrets of children to the sandman - so I’m out” ?</p>
<p>This is a proper question, by the way. What would you do?</p>
<p>Because I sucked down my moral position, waited for my DK2, told people that this (inevitable) day would come and
we would all regret it, and then moved on as a staunch Oculus supporter. Oculus - NOT Facebook. Never Facebook. because
regardless of the money, WE had made this happen, and Oculus was OURS.</p>
<p>And the DK2 arrived.</p>
<p><a href="https://xinreality.com/wiki/Oculus_Rift_DK2"><img src="/assets/2020_oculus/dk2.jpg" alt="Rift DK2" /></a></p>
<p>And it was INCREDIBLE.</p>
<p>And then Elite Dangerous came out, and it worked on the DK2, and <em>oh my noodly stars what the hell is this am
I actually living in the future and flying a spaceship? spaceship <strong>spaceship</strong> <strong>SPACESHIP</strong></em></p>
<p>Yes, I’m actually flying a spaceship. I’m sitting here at my desk, with a borrowed HOTAS, piloting a spaceship
out of a space station with a docking slot that’s impossibly far above my head. I can feel the ice forming
on the windshield in front of me, and the lure of the dark is pulling me. I engage hyperspace, and arrive
with a visceral kick, right beside an enormous blue giant star. Warnings scream.</p>
<p>Someone taps me on the shoulder, and asks if they can have a go.</p>
<p>These moments, lost in time like tears in rain. And it was still only 2014.</p>
<p>It’s very easy to ignore the creeping literal evil when it’s not actively eating your face.</p>
<h2 id="and-now">And now?</h2>
<p>Many things have happened in the intervening six years. Life has moved on for all of us. Brexit, Trump, COVID-19.
The Valve Index. PSVR. The Oculus Quest. Facebook hiring some of the best people in the world, and pouring literally
hundreds of millions of dollars into VR research and products. The Rift, becoming an actual product (CV1 for those who
had been riding the train all along) which was INCREDIBLE, before it became “just ok” comparatively, before it
became an EBay collector’s item.</p>
<p>But let’s not forget, behind all of this, the political machinations of the rich elite have continued to twist our
lives (for better or worse). And certainly since 2014, Facebook, under the guidance of Mr Zuckerberg, has actively
played a critical role in nearly every life on the planet.</p>
<p>Capturing your thoughts, messages, photographs, videos. Your children and your friends. Your ephemeral connections
to everyone you know, and the world around you.</p>
<p>And then selling that shit to whoever pays the most, especially people with an agenda for change, like Cambridge Analytica.
But if they can’t find those people, then why not just sell into the product market to make sure you keep on participating
in the economy like a good little consumer.</p>
<p>I know, Facebook isn’t the only one doing this. But it’s certainly in the bad group. It’s up there. It’s active.</p>
<p>Then, consider that a VR device isn’t a text message, or a photo. It’s a virtual <em>everything</em> that consumes
and surrounds you, like the goddamn force except real. And the device you’re interacting with can track <em>you</em>.
How you move. How you talk. Your reaction times. Your environment. Which finger you twitch first when you’re being
offended. This data is incredibly valuable, and currently no-one is selling it wholesale. Yet.</p>
<p>And we’re finally at the point where my Oculus account is required to become a Facebook account, and I have to
<em>actively</em> make the choice to re-enter the Facebook ecosystem to be able to continue to making the content I want to make.
And if I make that content, I’m <em>actively</em> recommending that the users of the content engage, and participate in,
that same ecosystem.</p>
<p>Am I angry? You bet. Am I complicit? Of course.</p>
<p>Do I have a choice?</p>
<p>Now there’s a question for you.</p>That fateful, hateful moment is here. Oculus is now Facebook.Visualiser is alive!2019-10-30T00:00:00+00:002019-10-30T00:00:00+00:00http://www.evryway.com/Visualiser<h2 id="quest-is-missing-a-music-visualiser---so-i-made-one">Quest is missing a music visualiser - so I made one.</h2>
<p>I’ve been loving my beat saber machine, but sometimes I just want to chill and listen to some tunes. One thing that’s lacking from
the Quest store (and Sidequest, as far as I can see) is a half-decent music visualiser - think Milkdrop but in VR.</p>
<p>So I made one.</p>
<h2 id="evryway-visualiser"><a href="https://evryway.itch.io/evryway-visualiser">Evryway Visualiser</a></h2>
<p>This is a super-early alpha “it’s broken, whatchagonnado” build.</p>
<p>I have no idea whether I want to make it free, or charge for it - so currently there’s a price on the Itch page (mostly to discourage
lots of people just downloading it and moaning at me about how broken it is). If you want to give it a try, email / tweet me and
I’ll pop you a download key.</p>
<p><a href="https://www.youtube.com/watch?v=0xdSKZfU7QA">Alpha build 01</a></p>
<iframe width="560" height="315" src="//www.youtube.com/embed/0xdSKZfU7QA" frameborder="0" allowfullscreen=""></iframe>
<p>If anyone shows interest, I’ll be putting in a lot more effects, and possibly some APIs for streaming music services like Spotify (if
I can figure out how to do it!)</p>Quest is missing a music visualiser - so I made one.Fork me! Moving to a new Git client on Windows2019-10-16T00:00:00+00:002019-10-16T00:00:00+00:00http://www.evryway.com/Fork-Me<h2 id="where-did-i-put-that-file-again">Where did I put that file, again?</h2>
<p>I’ve been using <a href="https://git-scm.com/book/en/v1/Getting-Started-About-Version-Control">Git</a>
as my revision control system for years now. Despite what some well intentioned folks say,
if you’re not using revision control for software development you’re doing it wrong. I’ve had projects where I’ve
lost a hard drive and everything on it - a remote copy of your workspace saves you here. I’ve had projects where
I accidentally deleted a day’s worth (or in one case, nearly a week’s worth) of work - RCS to the rescue. Not sure
how best to achieve something, and you want to make a throwaway test? revision control (with branching, natch) has
it covered.</p>
<p>I’ve used all sorts of revision control tools - from nothing at all with floppy backups through
<a href="https://en.wikipedia.org/wiki/Microsoft_Visual_SourceSafe">Visual Sourcesafe</a> (which was neither),
<a href="https:perforce.com">Perforce</a> (great),
<a href="https://www.microfocus.com/en-us/products/accurev/overview">Accurev</a> (not bad),
<a href="https://subversion.apache.org/">Subversion</a> (ok),
<a href="https://www.alienbrain.com/">AlienBrain</a> (shudder).
Many of these were either terrible to use, or caused all sorts of conflicts and workflow issues, or both.</p>
<p>Perforce was the first tool that helped me grok branching. It wasn’t easy to make it work (at the time, I was at EA
working on the Potter franchise, and this was way before Fusion or Helix). It made a huge difference to how we worked
and how we released products.</p>
<p>Then I tried out
<a href="https://www.mercurial-scm.org/">Mercurial</a> and
<a href="https://git-scm.com/">Git</a> - both excellent, both distributed systems - and plumped for Git as my tool of choice for all future
projects (where at all possible). When we set up Boss Alien, we made the call to use Git for all revision control:
code, art assets, docs, you name it.</p>
<p>Git’s awesome, in a “here’s a toolbox, try not to stab yourself - oh, you stabbed yourself, try not to bleed out” kind of way.
learning how it works is a cliff rather than a curve, and after teaching it to hundreds of people (including technical folks,
artists, producers, etc) I can safely say that it’s not something many people <em>just get</em>. It’s also not something
many people really understand the need for - probably because they’ve never had to reconstruct a project from scratch
using someone else’s partial copies of your work. (Hint - I’ve done that. It sucks).</p>
<h2 id="cli-or-gui">CLI or GUI</h2>
<p>Git is very much an engineer’s tool - using it on the commandline (preferably green on black terminal) lets you go
hacker crazy. But it’s not really the best way to interact with your files if you’re anything other than a tech dweeb.
Some sort of UI on top of that commandline mess really helps, and the various GUIs for Git have been improving in leaps
and bounds over the last few years.</p>
<p>I’ve been using <a href="https://www.sourcetreeapp.com/">Atlassian Sourcetree</a> for the last six years, on Mac and PC.
It’s good (sometimes great!) and, at the time I started using it, it was probably the best cross-platform GUI for Git out
there (after fighting with <a href="https://tortoisegit.org/">Tortoise</a>, a brief stint with <a href="https://www.git-tower.com/windows">Tower</a>
and trying out just about every other option along the way). I’m a huge fan of Atlassian in general - their tools
were instrumental in helping us ship CSR at Boss Alien, their licencing and prices were reasonable, and Sourcetree has
the best price of all - it’s free!</p>
<p>But it’s languished over the last few years - lots of UI changes, Git LFS support and more came on to the scene, but it’s
never felt completely reliable for me. The last few updates made things slower and slower, less and less usable.</p>
<p>I finally decided to get out of my comfort zone, and look at the alternatives. <a href="https://www.gitkraken.com/">GitKraken</a> was
a brief contender, but I’m very much not a fan of yearly subscriptions to tools I’m not yet sure about, and I had a few
niggles while getting it set up.</p>
<h2 id="then-i-tried-out-fork">Then I tried out <a href="https://git-fork.com/">Fork</a>.</h2>
<p>it’s awesome. It’s like Sourcetree - but it just works. With OpenSSH now included in Windows 10, credential management
is a breeze (as long as you know how to configure SSH!). Submodules work. LFS works. Rebasing, external diff and merge,
and all of the usual stuff in Sourcetree - it’s there, mostly the same.</p>
<p>And it’s <em>fast</em>. As in, I can type into the commit dialog and not be waiting for my keystokes to catch up (looking at
you, Sourcetree).</p>
<p>I’m not currently doing dev for iOS, so I’ve yet to try the Mac client - but I’m pretty hopeful I’ll have the same
experience there when the time comes.</p>
<p>So far - highly recommended. If you’re using Git and you’re not happy with your GUI - give it a test drive.</p>
<p>And if you’re not using Git (and your RCS is anything other than Plastic, Fossil, Hg or Perforce) then I can give
you a great introduction to how it works (and why it works) and why you should use it. If you need help or persuasion,
contact me at the usual place!</p>Where did I put that file, again?It’s 2019, and XR is about to go mainstream2019-06-12T00:00:00+00:002019-06-12T00:00:00+00:00http://www.evryway.com/Mainstream-Is_here<h2 id="its-been-a-busy-year-">It’s been a busy year …</h2>
<p>those of you who followed along with my Holodeck may have been wondering why everything went so quiet in 2018. The answer, as so often is the case, is hidden behind NDAs. hopefully I’ll get a chance to talk about some of the projects at a later date. One thing I can say is that it’s been a huge learning experience. Fitting consulting work around my family and lifestyle has been incredibly rewarding (and paying the bills is a bonus side effect!) but doesn’t give me much room to enthuse about all the awesome things we’re building.</p>
<p>One thing I can talk about, though, is my <b><a href="https://www.oculus.com/quest/">favourite new toy - the Oculus Quest.</a></b> I’ll be doing a short series of posts over the next week or two going into much more detail about how important this particular headset is going to be. For now, though, believe me when I say - I think this is the first true VR headset that will resonate with the general public. If they sell anything less than a million of these, I’ll be very surprised.</p>
<p>Random prediction time - I think the Quest will be the headset with the largest user base by the end of 2020.</p>
<p>More details to come next week!</p>It’s been a busy year …Consulting and Development2018-08-28T00:00:00+00:002018-08-28T00:00:00+00:00http://www.evryway.com/consulting<p>Evryway are available for consulting and development on your AR and VR projects. Talk to us at <a href="mailto:hi@evryway.com">hi@evryway.com</a> to see how we can help you.</p>Evryway are available for consulting and development on your AR and VR projects. Talk to us at hi@evryway.com to see how we can help you.Mobile VR is coming of age (and not a moment too soon)2017-10-15T00:00:00+00:002017-10-15T00:00:00+00:00http://www.evryway.com/mobile-VR-is-coming-of-age<p>Good lord, it’s October. When did that happen? I’ve been doing some contract work since April, and that’s now finished.
I’d love to talk about it, and I might do that in the near future - bits of the experience were great, but the ending
wasn’t quite what I expected. As with the best stories in life,
when doors shut, other doors open - it’s going to be an interesting few months. While I’ve been cranking away on other
people’s stuff, many cool things have been happening. For example, Oculus just made a lot of noise about their new
product, the
<a href="https://www.oculus.com/go/">Oculus Go</a>
which is basically an all-in-one Gear VR/Daydream type of thing.</p>
<p>In other words, three <a href="https://en.wikipedia.org/wiki/Degrees_of_freedom_(mechanics)">degrees of freedom</a>, or 3DOF.</p>
<p>I’m here to tell you, that’s three degrees too few. What you want - nay, <em>need</em> - is six degrees of freedom.
That’s why I think the more exciting news has been around the latest iteration of Oculus’
<a href="http://uk.pcmag.com/feature/91569/hands-on-with-the-impressive-oculus-santa-cruz-headset">Santa Cruz prototype</a>
(that’s one of a set of remarkably similar reviews).</p>
<h1 id="six-degrees-of-kevin-bacon"><a href="http://www.sixdegrees.org/">Six degrees of Kevin Bacon?</a></h1>
<p>Nothing to do with Kevin Bacon.</p>
<p><a href="https://en.wikipedia.org/wiki/Six_degrees_of_freedom">Six degrees of freedom</a> in a VR headset means it tracks both your
head <em>orientation</em> and head <em>position</em>. Most mobile VR experiences up to this point just track <em>rotation</em> which is pretty easy
to do - you can use a combination of gyroscopes to get pretty accurate tilt measurements in three axes, and use that to figure
out which way your head is facing, which is fine right up to the point you <em>move</em>. If you’ve tried Daydream, Gear VR, Cardboard
or any other 3DOF VR, this is what you’ve been experiencing. It kinda works, but it feels pretty darn strange, and then you
sort of get used to it.</p>
<p>The very first Oculus Rift prototype (the DK1) was a 3DOF headset. I recall putting it on, saying “Wow” a few thousand times,
and then telling everyone “when they get positional tracking working, this is going to be <em>awesome</em>”. And then they did, with the
DK2, and <em>it really was</em>. Cue the sale of the company for a few billion dollars, and VR going mainstream. Yes, it’s <em>that important</em>.
Sure, lots of other technology came of age (displays, PC GPUs, etcetera) but the key thing here is being able to look <em>and move</em> at
the same time.</p>
<h1 id="so-weve-had-six-degrees-of-freedom-on-our-vr-headsets-for-a-while---whats-so-special">So we’ve had six degrees of freedom on our VR headsets for a while - what’s so special?</h1>
<h2 id="cable-management">Cable management</h2>
<p>For anyone who has a Vive or a Rift, you’ll have heard the words “Cable Management”. It’s so critical that people have
turned it into a
<a href="https://www.amazon.co.uk/MIDWEC-Retractable-Management-Headset-Adhesive-Cable-Organizer-6-Pack/dp/B01MSJ6XF5">product</a>.
My personal cable management process is to have it run over the floor, step on it, get it twisted over my shoulders,
nearly choke myself, untwist a few times, and generally live with it. But oh FSM, it’s annoying. Of course, you need the
cables to take the data from your meaty PC to the headset itself.</p>
<h2 id="tracking-environment">Tracking environment</h2>
<p>As well as the cable (which is often affectionately known as the
<a href="https://www.google.co.uk/search?q=tether+goat&source=lnms&tbm=isch&biw=1886&bih=1006">tether</a>), you also have all
the tracking paraphernalia. Both Vive and Rift use fixed tracking beacons in the environment
(Cameras in the Rift’s constellation, Lighthouses for Vive). These communicate between the headset and the PC to
establish the orientation and position of the headset and controllers.</p>
<h2 id="and--gone">And … gone!</h2>
<p>The Santa Cruz does away with both of these things. Sure, there’s undoubtedly trade-offs in place (tracking quality,
visual quality, render performance, etc) but they are <em>so, so worth it</em>.</p>
<h1 id="truly-mobile-vr">Truly Mobile VR</h1>
<p>Since messing around with my DK2 and Kinect “Mobile” VR solution a few years back now, I’ve been telling folks just how
awesome truly mobile VR is. A few lucky folks have had the opportunity to try it out, both with the backpack DK2 and
with my Tango experiments. Now, everyone else gets to see how wonderful untethered VR is. If you’re a fan of presence, then
being without a cable makes an incredible difference.</p>
<h1 id="i-was-wondering-when-youd-mention-tango">I was wondering when you’d mention Tango.</h1>
<p>Yeah. So, it comes back to this. Tango and Daydream, together, could have - nay, should have - been this product. I assumed
(naively, incorrectly, stupidly) that someone smart in Google was aiming for this. And personally, I’m frustrated that
it hasn’t happened. But I’m kinda smugly amused that Facebook are getting the jump on this, using the Snapdragon 835
and most likely some derivative of Android to power it.</p>
<p>I’ll be buying one. Or two. And I’ll be using it, hopefully a lot.</p>Good lord, it’s October. When did that happen? I’ve been doing some contract work since April, and that’s now finished. I’d love to talk about it, and I might do that in the near future - bits of the experience were great, but the ending wasn’t quite what I expected. As with the best stories in life, when doors shut, other doors open - it’s going to be an interesting few months. While I’ve been cranking away on other people’s stuff, many cool things have been happening. For example, Oculus just made a lot of noise about their new product, the Oculus Go which is basically an all-in-one Gear VR/Daydream type of thing.When they met, it was murder2017-09-06T00:00:00+00:002017-09-06T00:00:00+00:00http://www.evryway.com/when-they-met-it-was-murder<h1 id="a-tale-of-tango-arcore-and-arkit">A tale of Tango, ARCore and ARKit</h1>
<p>That’s right - It’s time for a heart to heart. A little bit of intimate conversation, if you will,
about how Google has effectively killed Tango by a variety of small cuts and a few big chops.</p>
<h2 id="cast-your-mind-back-">Cast your mind back …</h2>
<p>A few years ago, I got my hands on something absolutely incredible - one of the second iteration
Project Tango tablets. It was so good, I quit my job (1). With in-built position and rotation tracking,
local spacial awareness and (best of all!) a frikkin laser (2), I very quickly realised that this
was going to be the future of mobile computing. A device that understands where it is, and can make
sense of the environment around you? Who’s not going to want that?</p>
<h2 id="and-then-">And then …</h2>
<p>The next year or so has been a real whirlwind, as I’ve been exploring AR and VR. I spent pretty much
an entire year just messing around with various pieces of kit as they arrived. First was the Rift, then
shortly after the Vive, followed by a procession of addons, RGB-D cameras, goggles, 3D cameras, input devices, you name it.
But the Tango has always had a special place in my heart. Inside-out tracking, SLAM and an untethered form
factor really are the future of both AR and VR, as will become quite evident over the next couple of years.</p>
<p>Then <a href="http://www.pokemongo.com/">Pokemon Go!</a>
came out, and for some reason everyone went bananas over it, even though I found Niantic’s previous
game <a href="www.ingress.com">Ingress</a> more interesting personally. You could actually hear the lightbulb going on
in the mainstream media - sticking virtual stuff into the real world is cool, yo. The implementation may have
been ropey as all hell (compared to recent ARKit/ARCore demos) but people got it.</p>
<p>During that time, I’d made some fantastically fun VR and AR demos, with my favourites being a multiplayer tilt-brush
clone that you could use Tango as a viewer for. Realising that context is critically important for content,
I proceeded to dump a huge amount of effort into my environment scanning software, such that I could capture
static environments and then walk around them virtually in AR and VR.</p>
<h2 id="so-what-happened-to-tango">So what happened to Tango?</h2>
<p>That’s a great question. Around the time that Pokemon Go! came out, the Tango SDK release cycle started to go a bit
tits up. From being a monthly release cycle, and fairly well tested software, the SDKs started to become irregular
releases. Bugs were raised by myself and many others in the community, and they were often ignored, and occasionally
(mind-bogglingly) the functionality related to the bug would be removed. In fact, a few critical parts of the software
stack have rotted or been deliberately disabled, such as exposure control in the camera, up to most recently
the ability to actually acquire the frame from the camera at all using the default API. The fisheye camera was readable
on device last year, but on the latest hardware (Zenfone AR) you can’t get access to that image.</p>
<h3 id="non-existent-support">Non-existent support</h3>
<p>These issues are frustrating as all hell to deal with in the best of times, but the support channels were simply
non-existent. Looking at the <a href="https://developers.google.com/tango/support">Tango support page</a>, the <em>primary</em> support
channel is supposed to be <a href="https://stackoverflow.com/questions/tagged/google-project-tango">Stack Overflow</a>.
I dare you to find an official response (go look, I’ll wait) and those that
did happen were often weeks or months after the posts were made. This certainly isn’t from lack of interest - there’s
lots of developers trying to make things work, and failing miserably because the support simply isn’t available to them.
The few people I did manage to engage with were often friendly, but just as often unable to help me resolve my issues.</p>
<p>There’s also a <a href="https://plus.google.com/communities/114537896428695886568">Google G+ group</a>,
which is (again) filled with posts from external developers - showing off their apps,
asking questions, looking for support. The amount of engagement from official Tango staff is <em>incredibly</em> small, and
went from bad a year ago to invisible over the last few months.</p>
<p>It just blows my mind that this is the level of technical, programming, development support available from Google.
It’s almost as though they don’t have a clue how to do it properly, which just can’t be the case. And if that’s not
the case, then what’s the motive for being so f**king inept at it? (3)</p>
<h3 id="obsolete-hardware">Obsolete hardware</h3>
<p>The Peanut hardware (the first Tango device) can be forgiven for being ropey, and the Yellowstone tablet (the second
Tango device, and the one I purchased four of) can also be given a bit of a pass. Unfortunately, it shipped with Android
4.4, right around the time Android 5.0 came out. And then Android 6.0 came out, and Tango was <em>still stuck on 4.4</em>.
“When is this getting fixed?” cried every developer. And do you know what the answer was? It was tumbleweed! Of course!
Not any kind of rational communication, just a complete lack of acknowledgement that it was an issue (and would remain
so until the hardware was explicitly moved into obsolete status).</p>
<p>As Android moved on, the Tango kit became more and more flakey - core services like Play Store and others would auto-update
to the latest shipped version, and then crash until they were reverted or disabled. Im the latter months of using
Yellowstone for development, I had to (not a word of a lie) press the “close service” button every 10-15 seconds
on the device, because something would be crashing that I couldn’t disable. I was not alone, in that just about every
other developer was in a similar situation.</p>
<p>This was when the <a href="http://www.techradar.com/reviews/phones/lenovo-phab-2-pro-1322905/review">Lenovo Phab 2 Pro</a>
became visible (in app analytics before any official communication was made), and
then finally there was an official post telling people “The only supported tango device from now on will be the Phab 2 Pro”.</p>
<p>Fine says I, I’ll get one. it’s a 600 quid phablet that’s too big to fit in my pocket, but hey, Tango’s going places, I need
to keep up. Folks in the states had access to them months before I did, but I finally managed to get my hands on one,
fix up my <a href="http://www.evryway.com/apps/evrywayscanner/">Scanner app</a> and various demos, and catch up to where everyone else was.</p>
<p>Now, Google and Lenovo are obviously <em>best buddies</em> because the amount of product support that came alongside the Phab 2 Pro
was awesome. No, wait, that’s wrong - it was non-existent. The Phab (which is actually a great bit of hardware) shipped
with Android 6.0 just as 7.0 was shipping, and - can you guess? - Isn’t ever going to be updated to 7.0. And it’s not
Daydream compatible. And it’s using last year’s Qualcomm hardware. Did I mention it’s insanely expensive?</p>
<p>So we’re sitting on a tiny subset of devices that actually work, with the same old products that were originally touted
as cool at the end of 2015 (and some of the coolest ones don’t now work on Phab 2 Pro).</p>
<p>And then we get to the <a href="https://www.asus.com/uk/Phone/ZenFone-AR-ZS571KL/">Asus Zenfone AR</a>,
which was released in the UK last month. It’s now my daily driver phone, I love it.
Great performance, good battery life, and a heart-attack-inducing 800 pound price tag. And as the Zenfone AR is released,
the Phab 2 Pro unofficially moves to obsolete status. The Zenfone AR also supports Daydream - but not at the same time as
running the Tango stack. Because why would you want that? Why would you want positional sensing AND 3D rendering
at the same time? who knows. (4)</p>
<p>I get that this is all developer angst, but we’re talking about consumer released products here (not devkits). purchasing
a device that then becomes unsupported for the singular thing that makes it stand out less than
six months later - not cool.</p>
<h3 id="obsolete-software">Obsolete software</h3>
<p>And then <a href="https://developer.apple.com/arkit/">Apple gets into the ring with ARKit</a>,
which obviously throws spanners into everything that Google are trying to
achieve, because Apple do the obvious thing of making it just use the existing hardware (cameras and all) for spacial tracking. It’s
nowhere near as cool as Tango is, but it does the basic stuff that you need to make Pokemon Go! actually work properly.</p>
<p>So what do Google do? Double down on Tango? Of course not. They decide to rush a tiny subset of Tango into production
called <a href="https://developers.google.com/ar/">ARCore</a>.
And they do it in the most half-assed way imaginable - they take the core code from Tango,
<a href="https://developers.google.com/tango/">rip the guts out of it, rebrand it, and slap it back into production</a>.
It’s such a half-assed effort that if you try and run it
on Tango devices, it won’t work - the ARCore and Tango services can’t co-exist (because they’re basically the same service).</p>
<p>So not only is my Phab 2 Pro obsolete, now my Zenfone AR is obsolete too - I can’t do ARCore development on it.</p>
<h2 id="you-knew-this-was-coming-right">You knew this was coming, right?</h2>
<p>Yes. Yes I did. Talking to various folks, and from being in software development in large companies for the last couple of
decades including Disney and EA - I know how product development works, and I especially know the signs when a product
is being decapitated for non-technical reasons. Tango has shown every sign of being DOA during my time developing on it,
despite my hopes and dreams that it would make it out into the real world in enough numbers that the general public would
be able to get it.</p>
<p>It grinds my gears, but I knew this was coming. It’s a damn good job I didn’t stake my career on this. (5)</p>
<h2 id="whats-next">What’s next?</h2>
<p>Well, instead of everyone getting to see the future right about now, we’re going to have to wait a few years while
the mainstream media goes ditzy over mobile AR, and then realises that compared to mobile positionally-tracked VR it’s
a pile of shite, and then realises that Tango was where it was at in the first place.</p>
<p>The laser sensor isn’t essential to do most of the things I’d like to do, so as the hardware and SLAM stacks improve
and folks release new and cool synthesis techniques for photogrammetry and object recognition we’ll start to see
much of the future begin to happen anyway.</p>
<p>And we’re going to see a metric shitload of apps and demos with virtual things hanging in “real” space while they
slowly drift across your table, then disappear when you move your phone too fast.</p>
<p>And then, if we’re all very lucky, we’ll get to the point where you’re carrying a device that can show you a 3D,
enhanced view of the real world with accurate tracking from global scale down to local scale. Shops and bars will
have product reviews and menus floating outside them, waiting for you to look. You’ll see an arrow pointing to your
friend, over on the other side of the park. Everyone will have karma auras, and things will be good.</p>
<p>But we’re not there yet. I’m not angry, I’m just disappointed.</p>
<h2 id="footnotes">Footnotes</h2>
<p>(1) Not really.</p>
<p>(2) Yes, IR counts. there were warnings on the box and everything.</p>
<p>(3) If anyone at Google would like to talk to me about how the adults do software development, please drop me a mail.</p>
<p>(4) this is LITERALLY the killer-app, and they blew it.</p>
<p>(5) It may appear that I’ve staked my career on this, but that’s an illusion.</p>A tale of Tango, ARCore and ARKit