<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Max</title>
    <description>The latest articles on DEV Community by Max (@tapepal).</description>
    <link>https://dev.to/tapepal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4237965%2Fbe511d4f-adde-4389-a7f0-457a5a32c657.png</url>
      <title>DEV Community: Max</title>
      <link>https://dev.to/tapepal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tapepal"/>
    <language>en</language>
    <item>
      <title>Snapping a tap to a real corner: building a LiDAR tape measure for iPhone</title>
      <dc:creator>Max</dc:creator>
      <pubDate>Tue, 10 Nov 2026 22:44:48 +0000</pubDate>
      <link>https://dev.to/tapepal/snapping-a-tap-to-a-real-corner-building-a-lidar-tape-measure-for-iphone-b27</link>
      <guid>https://dev.to/tapepal/snapping-a-tap-to-a-real-corner-building-a-lidar-tape-measure-for-iphone-b27</guid>
      <description>&lt;p&gt;When you tap the screen to start a measurement, you are not tapping the corner of the table. You are tapping a pixel, and that pixel sits on a ray that leaves the camera and hits &lt;em&gt;something&lt;/em&gt;. Often the something is the floor 40 inches behind the table, because your thumb landed a hair past the edge.&lt;/p&gt;

&lt;p&gt;This post is how the tape measure in our iPhone app deals with that: two kinds of evidence (depth and image), a little geometry, and a filter that refuses to let the point jitter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Depth alone is too blurry
&lt;/h2&gt;

&lt;p&gt;On a Pro iPhone, ARKit hands you a depth map with every frame (&lt;code&gt;sceneDepth&lt;/code&gt;, plus &lt;code&gt;smoothedSceneDepth&lt;/code&gt; when the device supports it) and a confidence map next to it. It is wonderful and it is coarse. The reconstructed mesh is a few centimetres per triangle, so it rounds off exactly the thing you want to measure to: the edge.&lt;/p&gt;

&lt;p&gt;It is also blind to edges that are flat on a surface. A picture frame on a wall, door trim, the printed edge of a box: depth sees one plane. The camera image sees a sharp line.&lt;/p&gt;

&lt;p&gt;So the snapper looks at both, every frame, in a small window around the reticle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: find the surfaces in a window of depth
&lt;/h2&gt;

&lt;p&gt;We back-project the high-confidence depth samples around the reticle into world space and look for up to three planes with RANSAC. A few details that mattered more than we expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Inliers within 3 mm or 0.4% of the distance: LiDAR noise, not a different surface.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.003&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.004&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;minInliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The tolerance grows with distance.&lt;/strong&gt; A fixed 3 mm (about 1/8 in) works up close and splits a wall into slabs at 10 feet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merge near-parallel slabs.&lt;/strong&gt; LiDAR noise can turn one flat surface into two planes a few millimetres apart. If you keep both, the nearer one wins every ray and your edge ends land on different "surfaces". Planes within about 5° and 1 cm of each other get merged and re-fitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The median distance by selection, not sorting.&lt;/strong&gt; Quickselect with a median-of-three pivot gives the same value in a fraction of the time, and this runs every frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A seeded random generator.&lt;/strong&gt; RANSAC is random; we use a tiny SplitMix generator seeded from the point count, so the same depth window always gives the same planes. That made the whole thing unit-testable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plane fit itself is plain least squares: the normal is the eigenvector of the covariance with the smallest eigenvalue, found with a few Jacobi sweeps on a 3 × 3 matrix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: turn surfaces into corners and edges
&lt;/h2&gt;

&lt;p&gt;Once you have planes, corners and edges fall out of the geometry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Corner:&lt;/strong&gt; the point where three planes meet. We only accept it when they meet clearly (the determinant of the three normals above 0.2), the way a box corner or a room corner does. Nearly parallel planes give a "corner" miles away.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge (a fold):&lt;/strong&gt; the line where two planes meet, but only if they meet at more than about 25°. A slightly bowed wall is not an edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nearest surface wins.&lt;/strong&gt; When the reticle's ray passes a table edge, the edge belongs to the table, not the floor behind it. We take the nearest surface that actually has measured points near the hit, with a little slack because LiDAR blurs the last centimetre at an edge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 3: ask the image for the edges depth can't see
&lt;/h2&gt;

&lt;p&gt;In parallel, we copy a small grayscale patch around the reticle (a few thousand pixels, at half resolution) and look for the strongest edge or corner nearby, to a fraction of a pixel. Corners use Shi–Tomasi on a 5 × 5 structure tensor and are then refined to the point every nearby edge line passes through.&lt;/p&gt;

&lt;p&gt;The number that took the longest to get right was the contrast floor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Edge contrast below this (8% of full brightness) is texture or noise:&lt;/span&gt;
&lt;span class="c1"&gt;// wood grain, carpet, wallpaper, a shadow's soft rim.&lt;/span&gt;
&lt;span class="c1"&gt;// At 5% the reticle got pulled about on textured surfaces.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When depth and image agree on a point, the snapper needs fewer confirming frames before it locks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: hold the point still
&lt;/h2&gt;

&lt;p&gt;Finding the corner again in every frame makes the dot shimmer, even when every individual find is "right". So a locked corner is held as one fixed point in the room, a Kalman filter on a static point, and each new find refines it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each find is weighted by how sure it is: &lt;strong&gt;tight across the camera ray, loose along it&lt;/strong&gt; (depth error is mostly along the ray). The longer you aim, the steadier it gets.&lt;/li&gt;
&lt;li&gt;A find too far from the estimate is gated out as noise (χ² for three degrees of freedom at 99%, about 11.3).&lt;/li&gt;
&lt;li&gt;Three rejected finds in a row means you really moved to a different corner, and tracking restarts there.&lt;/li&gt;
&lt;li&gt;A small random-walk allowance lets ARKit's own map corrections come through instead of fighting them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edges get the same treatment as a line in the room: the snapped point is where that line passes closest to the reticle's ray, so the dot slides smoothly along a table edge instead of hopping between separate finds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Without LiDAR
&lt;/h2&gt;

&lt;p&gt;Most iPhones don't have LiDAR. There, the tape uses ARKit's estimated planes and the image snapping, and the scale comes from the camera alone, which is where most of the error lives. Our fix is low-tech: lay any bank or ID card on a table. ID-1 cards are 85.60 × 53.98 mm (ISO/IEC 7810) everywhere in the world, so once the app finds the card it knows the true size of something in view and can correct the scale for that phone.&lt;/p&gt;

&lt;p&gt;Room scans split the same way. With LiDAR, Apple's RoomPlan finds the walls, doors and windows while you walk the room. Without it, you tap each corner where the wall meets the floor, type the ceiling height, and get the same floor plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the part people actually want: the answer
&lt;/h2&gt;

&lt;p&gt;A length is rarely the question. "Will the sofa fit through the door?" is. That check turned out to be a nice little piece of geometry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sort the item's three dimensions so a ≤ b ≤ c.&lt;/li&gt;
&lt;li&gt;It goes through lengthways (along c) if a fits the door's width and b fits its height.&lt;/li&gt;
&lt;li&gt;Which of width, depth and height ended up as a and b tells you how to carry it: on its back, upright, or on its end. Ties prefer on its back.&lt;/li&gt;
&lt;li&gt;If that fails and it's a sofa, try it upright and tilted back, where the &lt;strong&gt;diagonal depth&lt;/strong&gt; (from the bottom back corner to the midpoint of a straightedge laid from the top of the back to the front of the arm) crosses the door's width.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;FitVerdict&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;spare&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;comfortableMargin&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yes&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spare&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1e-9&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tight&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// comfortableMargin = 0.02 (2 cm, about 3/4 in): under that it says "tight".&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;1e-9&lt;/code&gt; slack is there because a door measured as exactly the sofa's width should say "tight", not "no", after floating-point arithmetic has had its say.&lt;/p&gt;

&lt;p&gt;We also wrote the classic hallway-corner check: the longest item of thickness &lt;em&gt;t&lt;/em&gt; that can be carried flat around a right-angle corner between corridors of widths &lt;em&gt;A&lt;/em&gt; and &lt;em&gt;B&lt;/em&gt; is the minimum over θ of A/cos θ + B/sin θ − t(tan θ + cot θ). It's in the code and has tests, but it isn't in the app yet: today the app checks the doors on the route, not the turns between them. The open question for us is the UX, since it means asking people to measure two corridors and the item's thickness. If you've solved that nicely, I'd love to hear how.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd tell anyone building this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use both signals.&lt;/strong&gt; Depth finds the surfaces; the image finds the edges on them. Either one alone snaps to the wrong thing in common rooms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make every random step seeded.&lt;/strong&gt; A geometry bug you can't reproduce is a geometry bug you can't fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter the point, not the pixel.&lt;/strong&gt; Once a corner is a point in the world, stability comes almost for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be honest about limits.&lt;/strong&gt; Plain white walls and glass are hard for every phone, and for a critical cut you should still check with a real tape.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're building this into &lt;a href="https://tapepal.app" rel="noopener noreferrer"&gt;Tapepal&lt;/a&gt;, an iPhone tape measure that gives you the answer, not just the number: point at a door, a wall or a room and it tells you if the sofa fits, how much paint to buy or how many boxes of flooring you need. It's coming to the US App Store first. Happy to answer questions about any of the above in the comments.&lt;/p&gt;

&lt;p&gt;(We make Tapepal.)&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>augmentedreality</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
