Skip to content

Latest commit

 

History

History

geom-sdf

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

@thi.ng/geom-sdf

npm version npm downloads Mastodon Follow

Note

This is one of 199 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

(shape type descriptions)

  • 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

Installation

yarn add @thi.ng/geom-sdf

ESM import:

import * as sdf from "@thi.ng/geom-sdf";

Browser ESM import:

<script type="module" src="https://esm.run/@thi.ng/geom-sdf"></script>

JSDelivr documentation

For Node.js REPL:

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

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

Dependencies

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

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 } "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()
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 - 2024 Karsten Schmidt // Apache License 2.0