Skip to content

Files

Latest commit

4e4df17 · Jun 28, 2023

History

History
This branch is 3 commits ahead of, 2445 commits behind thi-ng/umbrella:develop.

geom-sdf

@thi.ng/geom-sdf

npm version npm downloads Mastodon Follow

This project is part of the @thi.ng/umbrella monorepo.

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)]));

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.

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

Installation

pnpm i @thi.ng/geom-sdf

ES module import:

<script type="module" src="https://cdn.skypack.dev/@thi.ng/geom-sdf"></script>

Skypack documentation

For Node.js REPL:

const geomSdf = await import("@thi.ng/geom-sdf");

Package sizes (brotli'd, pre-treeshake): ESM: 3.52 KB

Dependencies

API

Generated API docs

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 } from "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()
metaballs based on circles metaballs based on rectangles
circle() (smooth) rect() (smooth)
metaballs w/ smooth union metaballs w/ smooth union

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 - 2023 Karsten Schmidt // Apache License 2.0