A Quick Stopgap for Physical Distances

When retail stores closed in early 2020, merchants lost one of their most basic selling tools: the ability to show a customer how big a product actually is. Shopify’s Augmented Reality team built Size.link during April Hack Days as a stopgap. The service takes a URL with product dimensions — something like https://size.link/?l=3&w=4&h=5&units=in — and opens an AR view where the shopper can place a correctly sized box in their own room. No app required; it runs entirely on the web and supports both iOS and Android.

Why a box and not a full 3D model? Because 3D models cost time and money to produce. A cube is the “Hello World” of computer graphics, and it’s enough to communicate spatial footprint. The real engineering problem was making that cube appear natively in AR on both mobile platforms.

The File Format Divide

WebAR — running AR experiences in JavaScript — isn’t supported on iOS. Apple instead offers AR Quick Look, a native viewer that launches from a link to a 3D model file. Android has a similar feature called Scene Viewer. Both limit you to placing and moving a single object, which was exactly the scope Size.link needed.

The catch is that the two platforms speak different 3D file formats. Android’s Scene Viewer uses glTF, an industry standard sometimes called “the JPEG of 3D,” typically delivered as binary .glb files. Apple uses USDZ, which is built on Pixar’s USD format.

3D files are more complex than image pixels. A cube is represented by the coordinates of its eight corners — the vertices — plus face arrays that say how those vertices connect. Instead of writing out every vertex position for every possible size, formats support a scale property: a value of (1,2,3) stretches the model 1x along the x-axis, 2x along y, and 3x along z. That single parameter seemed like the obvious lever for generating arbitrary cube sizes.

Three Approaches, One Winner

The team considered two conventional strategies before settling on something more creative.

Generate on the fly with official tooling. Libraries for creating glTF files are plentiful and lightweight. USDZ is another story: Apple ships precompiled conversion tools, but only for macOS, so getting them onto a Linux server is painful. Even assuming you get usdzconvert running, it takes 3–4 seconds per conversion — plus glTF creation time. A user could wait 5 seconds or more before seeing anything. Too slow.

Pre-generate every possible model. The math becomes silly fast. Limiting dimensions from 1–1000cm in 1cm increments gives 1000 × 1000 × 1000 combinations, times two file formats: 2 billion files. At 3KB per model, that’s 6TB of storage. The team declined.

Modify the binary directly. All Size.link cubes share the same geometry, textures, and materials. Only the scale differs. So the team opened a USDZ file in a hex editor and went searching for the bytes that encode the scale property. USDZ stores 32-bit floats; a test cube scaled to (1.7, 1.7, 1.7) surfaces as 0x9a99d93f in the file

Found at byte offset 1344, the three floats appeared consecutively. Swapping those bytes and reloading the file confirmed the approach: editing bytes works. The resulting generation script runs in well under 1 millisecond — orders of magnitude faster than the USD toolset.

There was one limitation: the byte-swapping logic had to run somewhere that could serve a link to the file. A purely client-side version worked for iOS, where you can pass an encoded data URL to AR Quick Look. Android’s Scene Viewer, however, requires a file to be served from a URL — no local data URLs allowed. So the logic was refactored into a compact Ruby server.

Fixing the Stretch Problem

The early byte-replacement worked, but the cube looked dull — a plain grey block. The team redesigned it to be semi-transparent with a white outline that makes it easier to align with a room (useful when a shopper is trying to see whether that table will fit beside the couch). Transparency was easy: set the material’s opacity value.

The white outline caused trouble. If the outline was implemented via a separate, slightly larger box, the thickness distorted severely with scale. On a cube stretched 2-to-1, one set of outline edges would end up twice as thick as another. Because scale applies uniformly to everything, the outline needed independent control.

The solution was to abandon the scale property altogether and position every vertex explicitly. The team restructured the cube with extra, duplicated vertices specifically for the outline, then assigned each position a unique, unlikely 32-bit float value — the vertices got x/y/z values of 51/52/53 with negations for symmetry; outline vertices used 41/42/43. At request time, the server searches the binary for those sentinel float values (e.g., 0x00004c42 for 51 and 0x00002442 for 41) and replaces them with the desired coordinates.

Because opposite vertices share the same magnitude with opposite signs, the server computes the replacement as half the desired distance. For a 2m-long, 1m-tall cube, the x-axis vertices (distance 2 between them) get value 1; the outline vertices get 0.99 to maintain a 2cm outline. This find-and-replace is fast, avoids maintaining a list of byte offsets, and keeps outline thickness consistent regardless of proportions. The same technique was then applied to produce the .glb files Android needs.

The result is a deployment that generates both file formats on the fly, in milliseconds, with no platform-specific 3D toolchains on the server and no dependence on pre-generating a billion static files.

The team is satisfied with the current outcome and the straightforward nature of the implementation. Shoppers can already visit Size.link to visualize a product's dimensions as a 3D cube in real time. However, the underlying approach has much broader potential than generic rectangular solids.

Many household items follow standard sizing conventions—rugs, posters, frames, and mattresses, among others. The next logical step is to project actual product imagery onto these shapes so users can see a photorealistic version of a specific 6x9 rug placed in their space via augmented reality. Achieving that goal depends on a single technical hurdle: dynamically inserting texture maps into .usdz and .glb files on the fly.

Solving that problem will require revisiting the low-level file manipulation techniques the project already relies on. The hex editor is likely to make a comeback.