Skip to content

The area formula I learned in school does not work on a wall

Abdul-Qudus Rufai 3 min read

Span measures area by tapping three or more corners on a floor or a wall. Getting that number right taught me more about 3D maths than any other part of the app.

Keep the maths away from the camera

The first decision was structural. AR only runs on a real phone, and I could not test it in the simulator. So all the measuring rules live in their own folder, Core, which knows nothing about ARKit or SwiftUI. It takes points in metres and returns numbers.

That means every rule can be tested with plain unit tests and made-up points, with no phone, no camera and no hand involved. Bugs in 3D maths are where most of the time on a project like this goes, so being able to test them quickly mattered more than anything else.

Why the shoelace formula fails

The polygon area formula most people learn, the shoelace formula, works on a flat x and y grid. A floor in AR is nearly flat in that sense, so it would work there. A wall stands upright, so its points vary in height, and the shoelace formula would give the wrong answer.

Newell’s method works on any plane. It adds up the cross product of each pair of neighbouring corners. The result is a vector that points straight out of the shape, and its length is twice the area:

var normal = Vec3.zero
for index in points.indices {
    let current = points[index]
    let next = points[(index + 1) % points.count]
    normal += simd_cross(current, next)
}
return simd_length(normal) / 2

It has a second advantage I did not expect. Real AR points are never perfectly on one flat surface, because each tap is a little noisy. Newell’s method still gives a stable answer when the corners are slightly out of line.

Height is not just a distance

Height mode measures from the floor straight up, like a door or a person. My first thought was to take the distance between the two points. That is wrong. If you tap the floor, then the top of a door a little to the side, the straight line between them is longer than the door is tall.

So height only counts the vertical part. In ARKit, y points up against gravity, so height is just the difference in y:

static func verticalDistance(from base: Vec3, to top: Vec3) -> Float {
    abs(top.y - base.y)
}

A test that was wrong, not the code

One snapping test failed early on. It checked that a point snapped to exactly 0.2 metres, and the result was 0.2000000x. The code was right. Computers store decimals approximately, so a calculation rarely lands exactly on a value like 0.2. The test now checks that the two numbers are within 0.1 mm of each other. I knew this rule from JavaScript, but it is easy to forget once the numbers are measurements.

One setup surprise

Before any of this ran, Xcode refused to build with “Multiple commands produce Info.plist”. The project uses folders that Xcode keeps in sync automatically, so anything inside the app folder becomes part of the app. I had put Info.plist there, so Xcode tried to process it and also copy it as a resource. Moving it to the project root fixed it.

When I later rebuilt Span in Kotlin, these maths files were ported directly, along with their 33 tests. All of them passed first time, which is the best argument I have for keeping the maths separate.

Read the Span case study