Signed Distance Function (SDF)
Signed Distance Fields (SDFs) let you describe simple 3D geometry (like spheres, boxes, capsules, and planes) using a distance function instead of meshes. In our haptics stack, an SDF becomes a force field you can attach to a device session: when the cursor approaches (or enters) the shape, the system produces a normal force whose strength depends on distance, range, and easing.
This page documents the public API surface added/updated by the patch: the SDF Haptics effect payload, supported shapes, parameters, and request/response examples for creating, updating, listing, and deleting SDF effects.
Conceptual model
An SDF effect is:
- A shape (e.g.,
sphere,box,capsule,plane) - A transform (position/rotation/scale of the effect in space)
- A parameter object (
params) that configures the selected shape (radii, endpoints, extents, normals, etc.) - Force controls:
force_scale— overall magnitude multiplierrange— thickness of the “active band” outside the surface where forces ramp down to zeroease+reverse_easing— how force ramps with distance
- Composition controls:
symmetry— how inside/outside is treatedblend— how multiple SDF effects combine
You can create multiple SDF effects per device; each effect has a unique id.
SDF Effect
{
"id": "string",
"transform": {
/* transform object */
},
"shape": "sphere | box | rounded_box | capsule | capsule_vertical | capped_cylinder | capped_cylinder_vertical | plane",
"params": {
/* shape parameters */
},
"force_scale": 1.0,
"range": 1.0,
"ease": "linear",
"reverse_easing": false,
"symmetry": "single | mirror | align",
"blend": "additive"
}
Field reference
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | ✅ | Unique identifier for this effect within a device. |
transform | object | ✅ | Pose of the effect in world/device space. |
shape | string | ✅ | The SDF primitive to evaluate. |
params | object | ✅ | Shape-specific parameters (see below). |
force_scale | number | ⛔ | Multiplies the final force magnitude. Default: 1.0. |
range | number | ⛔ | Distance outside the surface (in meters) where the effect is active. Default: 1.0. |
ease | string | ⛔ | Easing curve applied over [0..range]. Default: linear. |
reverse_easing | boolean | ⛔ | If true, flips easing direction. Default: false. |
symmetry | string | ⛔ | How inside/outside is treated (see below). Default: single. |
blend | string | ⛔ | How multiple SDF effects combine. Default: additive. |
Shapes and parameters
All shapes are configured through a single params object. You can provide only the fields you need for the selected shape.
params object
{
"r": 1.0,
"h": 0.0,
"a": [0.0, 0.0, 0.0],
"b": [0.0, 0.0, 0.0],
"n": [0.0, 1.0, 0.0]
}
Tip: Values are interpreted in the effect’s local space (after applying
transform).
Supported shapes (current)
Only the following SDF primitives are currently supported (see IQ’s SDF gallery for math background):
| Shape | shape value | Parameters used |
|---|---|---|
| Sphere | sphere | r |
| Box | box | b (half-extents) |
| Rounded box | rounded_box | b (half-extents), r (corner radius) |
| Capsule (segment) | capsule | a, b, r |
| Capsule (vertical) | capsule_vertical | h, r |
| Capped cylinder (segment) | capped_cylinder | a, b, r |
| Capped cylinder (vertical) | capped_cylinder_vertical | h, r |
| Plane | plane | n, h |
Notes on conventions
- Vectors are arrays:
a,b,nare 3D ([x,y,z]). - Box uses
bas half-extents (e.g.,b=[0.1,0.2,0.1]). - Plane uses
nas the normal andhas offset along the normal. - Vertical variants use
has half-height (capsule/cylinder centered on origin along the local Y axis).
Symmetry
symmetry controls how the field behaves with respect to the surface:
single— Standard behavior. Inside vs. outside is preserved.mirror— Uses the absolute distance to the surface and flips direction when “behind” the surface, creating mirrored behavior.align— Uses the absolute distance without flipping direction (useful for “always push the same way” effects).
Easing and range
The force magnitude ramps with distance from the surface:
- At the surface (distance =
0): maximum magnitude (subject to easing) - At distance =
range: magnitude becomes0(inactive)
ease defines the curve of the falloff. reverse_easing inverts the curve.
Commands
"set_sdf": create/update localized SDF effects (array of items)"remove_sdf": remove effects by id (array of ids)
Set / Update SDF Effect
{
"inverse3": [
{
"device_id": "049D",
"commands": {
"set_sdf": [
{
"id": "boundary_049D",
"transform": {
"position": {
"x": 0,
"y": -0.05,
"z": -0.04
},
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"w": 1
},
"scale": {
"x": 0.15,
"y": 0.1,
"z": 0.1
}
},
"shape": "sphere",
"force_scale": -2.0,
"range": 0.1,
"ease": "cubicInOut",
"reverse_easing": false,
"symmetry": "single",
"blend": "additive",
"params": {
"r": 1.0
}
}
]
}
}
]
}
Remove an SDF Effect
{
"inverse3": [{
"device_id": "049D",
"commands": {
"remove_sdf": ["boundary_049D"]
}
}]
}
- Each id in the array is removed if present.
- Removing a non-existent id is treated as a no-op (safe to call).
REST API
The SDF endpoints follow the same session/device routing conventions as the rest of the haptics API.
- A device can have many SDF effects.
- Each effect is identified by its
id.- You can manage effects individually or in bulk per device.
Routes
-
GET
/{device_type}/{device_id}/sdf?session_id=<sid>→ device scope (list of SDFs)/{device_type}/{device_id}/sdf/{hfx_id}?session_id=<sid>→ single SDF/{device_type}/*/sdf?session_id=<sid>→ session scope (all devices)/{device_type}/*/sdf(nosession_id) → all sessions
-
POST
/{device_type}/{device_id}/sdf/{hfx_id}?session_id=<sid>body:sdf_hfx(create/update one)/{device_type}/{device_id}/sdf?session_id=<sid>body:device_sdf_dto(replace or upsert many for device)
-
DELETE
/{device_type}/{device_id}/sdf?session_id=<sid>or.../sdf/{hfx_id}?session_id=<sid>session_id=*supported for “clear all”.
Handlers validate device_type (inverse3 only), enforce session_id/device_id presence, and return 404 for missing session/device/effect.
Behavior highlights
- GET routing covers: all → session → device → single effect depending on path/params.
- POST enforces path/body
idconsistency for single-effect writes and merges/sets device-level SDFs when posting a list. - DELETE is idempotent and returns a standard ok envelope even if nothing was erased
Examples
Sphere “bubble” you can press into
{
"id": "bubble",
"transform": {
/* place it where you want */
},
"shape": "sphere",
"params": {
"r": 0.12
},
"force_scale": 1.0,
"range": 0.08,
"ease": "linear",
"reverse_easing": false,
"symmetry": "single",
"blend": "additive"
}
Rounded box “soft wall”
{
"id": "soft_wall",
"transform": {
/* oriented wall */
},
"shape": "rounded_box",
"params": {
"b": [
0.30,
0.02,
0.30
],
"r": 0.01
},
"force_scale": 0.9,
"range": 0.06,
"ease": "cubicInOut",
"reverse_easing": false,
"symmetry": "single",
"blend": "additive"
}
Segment capsule “rail”
{
"id": "rail",
"transform": {
/* position/rotate rail */
},
"shape": "capsule",
"params": {
"a": [
-0.15,
0.0,
0.0
],
"b": [
0.15,
0.0,
0.0
],
"r": 0.015
},
"force_scale": 0.7,
"range": 0.05,
"ease": "quadraticOut",
"reverse_easing": false,
"symmetry": "mirror",
"blend": "additive"
}
Plane “invisible boundary”
{
"id": "boundary",
"transform": {
/* place plane */
},
"shape": "plane",
"params": {
"n": [
1.0,
0.0,
0.0
],
"h": 0.0
},
"force_scale": 0.8,
"range": 0.10,
"ease": "linear",
"reverse_easing": false,
"symmetry": "single",
"blend": "additive"
}