# Two Spheres Melt Into One

*Task 2 of 6 · [Ray-Marched Metaballs](https://gpu.rocks/learn/ray-marched-metaballs-8b1282bd.md) · GPU.js Learn*

Combining two SDFs is just `Math.min` — the nearest surface wins.
But `min` leaves a hard crease where the shapes meet. Swap it for a
**smooth minimum** and the fields *blend*: wherever the two distances
are within `k` of each other, the result dips below both, bulging the surfaces
toward each other. That bulge *is* a metaball.

Since every task from here on needs this helper, register it once with
`gpu.addFunction()` — gpu.js transpiles it alongside the kernel, and any kernel
on that GPU instance can call it by name.

## Goal

**Goal:** implement the polynomial smooth minimum and use it to blend
two sphere fields into one metaball field.

## Requirements

- Register `smin(a, b, k)` with `gpu.addFunction()`
- Blend amount: `h = Math.max(k - Math.abs(a - b), 0) / k`
- Return `Math.min(a, b) - h * h * k * 0.25`
- Call `smin(d1, d2, k)` in the kernel instead of `Math.min`

## Hint 1 — what should change?

Far from the seam, `smin` equals plain `min`. Exactly
between the spheres the two distances are equal, so `h = 1` and the field
dips by `k / 4`. With the starter's arguments the midpoint should read
`0.2 − 0.1 = 0.1`.

## Hint 2 — the function, verbatim

```js
gpu.addFunction(function smin(a, b, k) {
  const h = Math.max(k - Math.abs(a - b), 0.0) / k;
  return Math.min(a, b) - h * h * k * 0.25;
});
```

## Same idea elsewhere

Smooth blends of implicit surfaces are how molecular-surface renderers in CUDA
draw proteins and how Metal-based sculpting apps merge clay-like blobs — the union operator
is soft everywhere, and the GPU evaluates it millions of times per frame without blinking.

## Starter code

```js
// min() gives a hard crease. smin() gives a blend. Metaballs are just smin.
const gpu = new GPU({ mode });

// TODO: register smin(a, b, k) with gpu.addFunction():
//   const h = Math.max(k - Math.abs(a - b), 0.0) / k;
//   return Math.min(a, b) - h * h * k * 0.25;

const metaField = gpu.createKernel(function (sep, r, k) {
  const wx = (this.thread.x - 32) / 16;
  const wy = (this.thread.y - 32) / 16;
  // one sphere at (-sep, 0), one at (+sep, 0)
  const d1 = Math.sqrt((wx + sep) * (wx + sep) + wy * wy) - r;
  const d2 = Math.sqrt((wx - sep) * (wx - sep) + wy * wy) - r;
  // TODO: blend with smin(d1, d2, k) instead of the hard minimum
  return Math.min(d1, d2);
}, { output: [64, 64] });

const field = await metaField(0.7, 0.5, 0.4);
console.log('midpoint (should dip to 0.1):', field[32][32]);
```

---

Interactive version: https://gpu.rocks/learn/ray-marched-metaballs-8b1282bd/2

[Previous task](https://gpu.rocks/learn/ray-marched-metaballs-8b1282bd/1.md) · [Next task](https://gpu.rocks/learn/ray-marched-metaballs-8b1282bd/3.md)
