Product and Industrial Designer
Award winning product designer with over 10 years of experience crafting meaningful, impactful, and inclusive products and experiences.
About

Building the spatial injection engine

July 2025

The heatmap in Drop looks simple. Tap a site, log an injection, and the body model updates: cool blues where you are safe to inject, warming through amber, deep red where tissue needs rest. A gradient over a 3D mesh that responds to your history in real time.

Getting there required building two things that look nothing like each other: a Metal shader pipeline inside RealityKit, and a spatial query engine that understands injection coordinates in 3D body space. This is the story of both.

The 3D body model is rendered using RealityKit with a .virtual camera — no AR, just a pure 3D scene you can rotate and tap. The mesh itself is a custom USDZ asset, morphed at runtime to match the user's body shape using a blend weight system. When a user taps the model, a raycast converts the screen touch into a precise 3D surface coordinate: a SIMD3<Float> point in body space, accompanied by a granular region label.

That coordinate is stored with every injection. It is the foundation everything else is built on.

The first challenge was the heatmap material itself. RealityKit's standard material system does not expose per-vertex data in a way that supports dynamic, data-driven colour gradients. The solution was CustomMaterial with Metal surface shaders: a .metal file that receives a staging buffer of injection heat values per mesh region and computes the surface colour at render time.

Getting this right took longer than expected. The early implementation recreated the Metal device, library, and command queue on every heatmap update, which produced [CAMetalLayer nextDrawable] returning nil errors under load. The fix was consolidating all shared Metal resources into a single RegionSharedResources actor, constructed once and reused across all renderer instances. Once that was stable, the shader pipeline became reliable.

The renderers themselves are split by context: RegionExploreRenderer for the cooldown view, RegionPickerRenderer for the logging flow, RegionSpotlightRenderer for individual injection detail, RegionTimelineRenderer for history. Each one shares the Metal infrastructure and writes into the same staging buffer format, which keeps the shader code simple and the heat values consistent.

The spatial engine is the layer underneath the visuals. The heatmap needs to know, for any point on the body surface, how many injections have occurred nearby and how recently. That requires more than looking up a region label. Two sites can share the same label and still be centimetres apart. Rotation guidance that ignores physical proximity is imprecise at best and clinically misleading at worst.

The foundation is a single query function:

func injections(near point: SIMD3<Float>, radius: Float) -> [Injection]

For a given 3D coordinate and a radius, return every injection that falls within that sphere. The implementation uses a bounding box pre-filter against all stored injection coordinates, then computes exact Euclidean distance on the candidates. Version one uses Euclidean distance as a practical approximation. Geodesic distance, which follows the surface of the mesh rather than cutting through it, is a more accurate measure for body surface proximity and is on the roadmap for a future release.

From this primitive, the heat value for any surface point is computed as a weighted sum across nearby injections, decayed by recency. A site injected this morning contributes more heat than one injected last week. The weights are tuned to reflect realistic tissue recovery timescales rather than arbitrary decay curves.

This is also the foundation for what comes next. The same spatial data that drives the visual heatmap will feed an on-device CoreML model that predicts tissue fatigue risk per site: a score derived from local injection density, recency weighting, and discomfort history. The coordinate precision Drop has been storing since launch is not incidental. It was always going to be useful for something more than drawing a gradient.

The Metal pipeline and the spatial engine are the two most technically complex parts of Drop. Neither is visible to the user in any direct way. What the user sees is a body model that honestly reflects their injection history and gives them clear, spatially accurate guidance on where to inject next.

That is the point. The complexity exists in service of a genuinely useful thing: helping people who self-inject avoid tissue damage by making good rotation decisions automatic.

Share
FacebookXMail