@thi.ng/geom-sdf
v1.0.65
Published
2D Signed Distance Field creation from @thi.ng/geom shapes, conversions, sampling, combinators
Downloads
391
Maintainers
Readme
[!NOTE] This is one of 212 standalone projects, maintained as part of the @thi.ng/umbrella monorepo and anti-framework.
🚀 Please help me to work full-time on these projects by sponsoring me on GitHub. Thank you! ❤️
About
2D Signed Distance Field creation from @thi.ng/geom shapes, conversions, sampling, combinators.
Includes several distance functions and SDF operators ported from GLSL implementations by:
SDF creation
SDFs can be directly defined/composed via provided shape primitive functions and combinators OR via automatic conversion from @thi.ng/geom geometry types/hierarchies. In the latter case various attributes can be used to control the conversion process. Regardless of approach, the result will be a single distance function which accepts a world position and returns the signed distance to the encoded scene.
// via direct SDF composition
import { circle2, union } from "@thi.ng/geom-sdf";
const f = union([circle2([-50, 0], 100), circle2([50, 0], 100)]);
// via conversion
import { circle, group } from "@thi.ng/geom";
import { asSDF } from "@thi.ng/geom-sdf";
const f = asSDF(group({}, [circle([-50, 0], 100), circle([50, 0], 100)]));Supported thi.ng/geom shape types
- circle
- complexPoly (polygon w/ holes)
- cubic
- ellipse
- group (of supported shapes)
- line
- path (w/ holes and/or sub-paths, multiple curves)
- points
- polygon
- polyline
- quad
- quadratic
- rect
- triangle
SDF combinators
The following table illustrates various options how SDFs can be combined. When
using the asSDF()
geometry converter, these operators can be specified and configured (most are
parametric) via a shape group()'s
attributes,
e.g.
import { group, rectFromCentroid } from "@thi.ng/geom";
group({ __sdf: { combine: "diff", chamfer: 50 }}, [
rectFromCentroid([-50,-50], 200),
rectFromCentroid([50,50], 200),
]);| Operator | Union | Difference | Intersection |
|----------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| default |
|
|
|
| chamfer |
|
|
|
| round |
|
|
|
| smooth |
|
|
|
| steps |
|
|
|
SDF discretization, sampling & domain modifiers
The package provides the
sample2d() and
asPolygons()
functions to discretize an SDF and cache results in a buffer (image) and then
extract contour polygons from it, i.e. convert the 2D back into geometry (see
example further below). The SDF will be sampled in a user defined bounding
rectangle (with customizable resolution) and the sampling positions can be
modulated via several provided domain modifiers to create various axial/spatial
repetions, symmetries etc. Modifiers are nestable/composable via standard
functional composition (e.g. using
compL()) and also
support custom modfifiers. The table below illustrates a few examples effects:
| Modifier | | | |
|-------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| repeat2() |
|
|
|
| repeatGrid2() |
|
|
|
| repeatMirror2() |
|
|
|
| repeatPolar2() |
|
|
|
Status
ALPHA - bleeding edge / work-in-progress
Search or submit any issues for this package
Related packages
- @thi.ng/distance-transform - Binary image to Distance Field transformation
- @thi.ng/geom-isoline - Fast 2D contour line extraction / generation
- @thi.ng/pixel - Typedarray integer & float pixel buffers w/ customizable formats, blitting, drawing, convolution
- @thi.ng/shader-ast-stdlib - Function collection for modular GPGPU / shader programming with @thi.ng/shader-ast
Installation
yarn add @thi.ng/geom-sdfESM import:
import * as sdf from "@thi.ng/geom-sdf";Browser ESM import:
<script type="module" src="https://esm.run/@thi.ng/geom-sdf"></script>For Node.js REPL:
const sdf = await import("@thi.ng/geom-sdf");Package sizes (brotli'd, pre-treeshake): ESM: 3.76 KB
Dependencies
- @thi.ng/api
- @thi.ng/checks
- @thi.ng/defmulti
- @thi.ng/errors
- @thi.ng/geom
- @thi.ng/geom-isoline
- @thi.ng/geom-poly-utils
- @thi.ng/geom-resample
- @thi.ng/math
- @thi.ng/transducers
- @thi.ng/vectors
Note: @thi.ng/api is in most cases a type-only import (not used at runtime)
Usage examples
Two projects in this repo's /examples directory are using this package:
| Screenshot | Description | Live demo | Source | |:---------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------|:----------------------------------------------------|:---------------------------------------------------------------------------------| | | (Re)Constructing the thi.ng logo using a 2D signed-distance field | Demo | Source | | | SVG path to SDF, applying deformation and converting back to SVG | Demo | Source |
API
import { asSvg, bounds, circle, group, svgDoc } from "@thi.ng/geom";
import { asPolygons, asSDF, sample2d } from "@thi.ng/geom-sdf";
import { range, repeatedly } from "@thi.ng/transducers";
import { randMinMax2 } from "@thi.ng/vectors";
import { writeFileSync } "node:fs";
const RES = [256, 256];
// create a group of 20 random circle shapes
// the special `__sdf` attrib object is used to control the conversion later
// the `smooth` option will combine the circles using the `smoothUnion()` operator
// see: https://docs.thi.ng/umbrella/geom-sdf/interfaces/SDFAttribs.html
const scene = group({ stroke: "red", __sdf: { smooth: 20 } }, [
...repeatedly(
() =>
circle(
randMinMax2([], [-100, -100], [100, 100]),
5 + Math.random() * 15
),
20
),
]);
// compute bounding box + some extra margin
// the extra margin is to ensure the SDF can be fully sampled
// at some distance from the original boundary (see further below)
const sceneBounds = bounds(scene, 40);
// convert to an SDF distance function
// more information about supported shape types:
// https://docs.thi.ng/umbrella/geom-sdf/functions/asSDF.html
const sdf = asSDF(scene);
// sample SDF in given bounding rect and resolution
const image = sample2d(sdf, sceneBounds, RES);
// extract contour polygons from given image
// in this case the contours extracted are at distances in the [0..32) interval
// the function also simplifies the resulting polygons using the Douglas-Peucker algorithm
// with the given threshold (0.25) - the default setting only removes co-linear vertices...
// see: https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
const contours = asPolygons(image, sceneBounds, RES, range(0, 32, 4), 0.25);
// convert to SVG and output as file
writeFileSync(
"export/metaballs.svg",
asSvg(
svgDoc(
{ fill: "none" },
// contour polygons
group({ stroke: "#000" }, contours),
// original geometry
scene
)
)
);Results:
| circle() | rect() |
|------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
|
|
|
| circle() (smooth) | rect() (smooth) |
|
|
|
Authors
If this project contributes to an academic publication, please cite it as:
@misc{thing-geom-sdf,
title = "@thi.ng/geom-sdf",
author = "Karsten Schmidt",
note = "https://thi.ng/geom-sdf",
year = 2022
}License
© 2022 - 2025 Karsten Schmidt // Apache License 2.0
