@thi.ng/geom-sdf 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2022-06-20T18:04:57Z
3
+ - **Last updated**: 2022-06-23T12:16:18Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
@@ -9,6 +9,29 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/geom-sdf@0.2.0) (2022-06-23)
13
+
14
+ #### 🚀 Features
15
+
16
+ - major update: combinators, modifiers, shape support ([4ffbc86](https://github.com/thi-ng/umbrella/commit/4ffbc86))
17
+ - support more shapes (and conversions) in asSDF()
18
+ - update/extend SDFAttribs
19
+ - add new SDF combinators (chamfer, round, step)
20
+ - add higher order combinators defOp(), defParamOp()
21
+ - add support for combinator params to be spatial
22
+ - update asSDF() to support more shape types and auto-convert to poly/line
23
+ - add domain modifiers, update `sample2d()` to support domain mods
24
+ - update various distance functions (incl. uniform arg order, minimize allocs)
25
+ - add docstrings
26
+ - add bounds pre-checks, update SDFAttribs, ops ([ddf0a6e](https://github.com/thi-ng/umbrella/commit/ddf0a6e))
27
+ - update `SDFn` signature, add opt. min dist param
28
+ - add `withBoundingCircle/Rect()` SDF wrappers
29
+ - update shape fns (points2, polygon2, polyline2)
30
+ - update SDF combinators (union, isec, diff etc.)
31
+ - update `asSDF()` group impl
32
+ - update `SDFAttribs`, allow `round` & `smooth` opts to be field based
33
+ - add docstrings
34
+
12
35
  ### [0.1.1](https://github.com/thi-ng/umbrella/tree/@thi.ng/geom-sdf@0.1.1) (2022-06-20)
13
36
 
14
37
  #### 🩹 Bug fixes
package/README.md CHANGED
@@ -10,7 +10,11 @@ This project is part of the
10
10
  [@thi.ng/umbrella](https://github.com/thi-ng/umbrella/) monorepo.
11
11
 
12
12
  - [About](#about)
13
+ - [SDF creation](#sdf-creation)
14
+ - [SDF combinators](#sdf-combinators)
15
+ - [SDF discretization, sampling & domain modifiers](#sdf-discretization-sampling--domain-modifiers)
13
16
  - [Status](#status)
17
+ - [Related packages](#related-packages)
14
18
  - [Installation](#installation)
15
19
  - [Dependencies](#dependencies)
16
20
  - [API](#api)
@@ -21,12 +25,94 @@ This project is part of the
21
25
 
22
26
  2D Signed Distance Field creation from [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom) shapes, conversions, sampling, combinators.
23
27
 
28
+ Includes several distance functions and SDF operators ported from GLSL
29
+ implementations by:
30
+
31
+ - Inigo Quilez ([Article](https://iquilezles.org/articles/distfunctions2d/))
32
+ - Mercury demogroup ([HG_SDF](https://mercury.sexy/hg_sdf/))
33
+
34
+ ### SDF creation
35
+
36
+ SDFs can be directly defined/composed via provided shape primitive functions and
37
+ combinators OR via automatic conversion from @thi.ng/geom geometry
38
+ types/hierarchies. In the latter case various
39
+ [attributes](https://docs.thi.ng/umbrella/geom-sdf/interfaces/SDFAttribs.html)
40
+ can be used to control the conversion process. Regardless of approach, the
41
+ result will be a single distance function which accepts a world position and
42
+ returns the signed distance to the encoded scene.
43
+
44
+ ```ts
45
+ // via direct SDF composition
46
+ import { circle2, union } from "@thi.ng/geom-sdf";
47
+
48
+ const f = union([circle2([-50, 0], 100), circle2([50, 0], 100)]);
49
+
50
+ // via conversion
51
+ import { circle, group } from "@thi.ng/geom";
52
+ import { asSDF } from "@thi.ng/geom-sdf";
53
+
54
+ const f = asSDF(group({}, [circle([-50, 0], 100), circle([50, 0], 100)]));
55
+ ```
56
+
57
+ ### SDF combinators
58
+
59
+ The following table illustrates various options how SDFs can be combined. When
60
+ using the [`asSDF()`](https://docs.thi.ng/umbrella/geom-sdf/modules.html#asSDF)
61
+ geometry converter, these operators can be specified and configured (most are
62
+ parametric) via a shape `group()`'s
63
+ [attributes](https://docs.thi.ng/umbrella/geom-sdf/interfaces/SDFAttribs.html),
64
+ e.g.
65
+
66
+ ```ts
67
+ group({ __sdf: { combine: "diff", chamfer: 50 }}, [
68
+ rectFromCentroid([-50,-50], 200),
69
+ rectFromCentroid([50,50], 200),
70
+ ])
71
+ ```
72
+
73
+ | Operator | Union | Difference | Intersection |
74
+ |----------|----------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
75
+ | default | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-none-union.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-none-diff.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-none-isec.png) |
76
+ | chamfer | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-chamfer-union.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-chamfer-diff.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-chamfer-isec.png) |
77
+ | round | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-round-union.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-round-diff.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-round-isec.png) |
78
+ | smooth | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-smooth-union.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-smooth-diff.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-smooth-isec.png) |
79
+ | steps | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-steps-union.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-steps-diff.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/combine-steps-isec.png) |
80
+
81
+ ### SDF discretization, sampling & domain modifiers
82
+
83
+ The package provides the
84
+ [`sample2d()`](https://docs.thi.ng/umbrella/geom-sdf/modules.html#sample2d) and
85
+ [`asPolygons()`](https://docs.thi.ng/umbrella/geom-sdf/modules.html#asPolygons)
86
+ functions to discretize an SDF and cache results in a buffer (image) and then
87
+ extract contour polygons from it, i.e. convert the 2D back into geometry (see
88
+ example further below). The SDF will be sampled in a user defined bounding
89
+ rectangle (with customizable resolution) and the sampling positions can be
90
+ modulated via several provided domain modifiers to create various axial/spatial
91
+ repetions, symmetries etc. Modifiers are nestable/composable via standard
92
+ functional composition (e.g. using
93
+ [`compL()`](https://docs.thi.ng/umbrella/compose/modules.html#compL)) and also
94
+ support custom modfifiers. The table below illustrates a few examples effects:
95
+
96
+ | Modifier | | | |
97
+ |-------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
98
+ | `repeat2()` | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-repeat-01.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-repeat-02.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-repeat-03.png) |
99
+ | `repeatGrid2()` | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-grid-01.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-grid-02.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-grid-03.png) |
100
+ | `repeatMirror2()` | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-mirror-01.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-mirror-02.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-mirror-03.png) |
101
+ | `repeatPolar2()` | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-polar-01.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-polar-02.png) | ![](https://raw.githubusercontent.com/thi-ng/umbrella/develop/assets/geom-sdf/domain-polar-03.png) |
102
+
24
103
  ### Status
25
104
 
26
105
  **ALPHA** - bleeding edge / work-in-progress
27
106
 
28
107
  [Search or submit any issues for this package](https://github.com/thi-ng/umbrella/issues?q=%5Bgeom-sdf%5D+in%3Atitle)
29
108
 
109
+ ### Related packages
110
+
111
+ - [@thi.ng/distance-transform](https://github.com/thi-ng/umbrella/tree/develop/packages/distance-transform) - Binary image to Distance Field transformation
112
+ - [@thi.ng/geom-isoline](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isoline) - Fast 2D contour line extraction / generation
113
+ - [@thi.ng/pixel](https://github.com/thi-ng/umbrella/tree/develop/packages/pixel) - Typedarray integer & float pixel buffers w/ customizable formats, blitting, drawing, convolution
114
+ - [@thi.ng/shader-ast-stdlib](https://github.com/thi-ng/umbrella/tree/develop/packages/shader-ast-stdlib) - Function collection for modular GPGPU / shader programming with [@thi.ng/shader-ast](https://github.com/thi-ng/umbrella/tree/develop/packages/shader-ast)
115
+
30
116
  ## Installation
31
117
 
32
118
  ```bash
@@ -50,7 +136,7 @@ node --experimental-repl-await
50
136
  > const geomSdf = await import("@thi.ng/geom-sdf");
51
137
  ```
52
138
 
53
- Package sizes (gzipped, pre-treeshake): ESM: 2.63 KB
139
+ Package sizes (gzipped, pre-treeshake): ESM: 3.87 KB
54
140
 
55
141
  ## Dependencies
56
142
 
@@ -61,6 +147,8 @@ Package sizes (gzipped, pre-treeshake): ESM: 2.63 KB
61
147
  - [@thi.ng/geom](https://github.com/thi-ng/umbrella/tree/develop/packages/geom)
62
148
  - [@thi.ng/geom-api](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-api)
63
149
  - [@thi.ng/geom-isoline](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-isoline)
150
+ - [@thi.ng/geom-poly-utils](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-poly-utils)
151
+ - [@thi.ng/geom-resample](https://github.com/thi-ng/umbrella/tree/develop/packages/geom-resample)
64
152
  - [@thi.ng/math](https://github.com/thi-ng/umbrella/tree/develop/packages/math)
65
153
  - [@thi.ng/transducers](https://github.com/thi-ng/umbrella/tree/develop/packages/transducers)
66
154
  - [@thi.ng/vectors](https://github.com/thi-ng/umbrella/tree/develop/packages/vectors)
@@ -94,7 +182,8 @@ const scene = group({ stroke: "red", __sdf: { smooth: 20 } }, [
94
182
  ]);
95
183
 
96
184
  // compute bounding box + some extra margin
97
- // the margin is to ensure
185
+ // the extra margin is to ensure the SDF can be fully sampled
186
+ // at some distance from the original boundary (see further below)
98
187
  const sceneBounds = bounds(scene, 40);
99
188
 
100
189
  // convert to an SDF distance function
package/api.d.ts CHANGED
@@ -1,12 +1,102 @@
1
1
  import type { Fn } from "@thi.ng/api";
2
2
  import type { ReadonlyVec } from "@thi.ng/vectors";
3
- export declare type SDFn = Fn<ReadonlyVec, number>;
3
+ /**
4
+ * Signed Distance Field function. Computes distance from given point `p`,
5
+ * optionally taking into account an already computed `minD` min distance (e.g.
6
+ * used for bounding hierarchies). `minD` defaults to positive ∞.
7
+ */
8
+ export declare type SDFn = (p: ReadonlyVec, minD?: number) => number;
4
9
  export declare type SDFCombineOp = "union" | "isec" | "diff";
10
+ export declare type FieldCoeff<T = number> = Fn<ReadonlyVec, T>;
11
+ /**
12
+ * Options object to customize geometry -> SDF conversions. Given as value to
13
+ * the special `__sdf` shape attribute.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const sdf = asSDF(circle(100, { __sdf: { abs: true } }));
18
+ * ```
19
+ */
5
20
  export interface SDFAttribs {
21
+ /**
22
+ * If true, only the absolute (unsigned) distance will be used. For closed
23
+ * shapes the default is false, for lines/curves the default is true (since
24
+ * there's no real interior).
25
+ *
26
+ * @defaultValue false
27
+ */
28
+ abs: boolean;
29
+ /**
30
+ * Advanced usage only. If true (default: false), the SDF will be wrapped
31
+ * with a bounding box pre-check.
32
+ *
33
+ * @remarks
34
+ * Currently only supported by some shape types and only usable in some
35
+ * circumstances, hence disabled by default.
36
+ */
37
+ bounds: boolean;
38
+ /**
39
+ * Only used for `groups()`. Specifies the type of operation used for
40
+ * combining child SDFs. If {@link SDFAttribs.smooth} is != zero, smoothed
41
+ * versions of the operators will be used.
42
+ *
43
+ * @defaultValue "union"
44
+ */
6
45
  combine: SDFCombineOp;
7
- smooth: number;
46
+ /**
47
+ * If true (default: false), the sign of the resulting distance will be
48
+ * flipped. Useful for boolean operations.
49
+ *
50
+ * @defaultValue false
51
+ */
8
52
  flip: boolean;
9
- abs: boolean;
10
- round: number;
53
+ /**
54
+ * Subtracts given value from actual distance, thereby creating an
55
+ * offsetting effect. If given as function, it will be called with the
56
+ * current SDF query point and the return value will be used as param.
57
+ *
58
+ * @defaultValue 0
59
+ */
60
+ offset: number | FieldCoeff;
61
+ /**
62
+ * Coefficient for smooth union, intersection, difference ops (only
63
+ * supported for `group()` shapes). If given as function, it will be called
64
+ * with the current SDF query point and the return value will be used as
65
+ * param. Ignored if zero (default).
66
+ *
67
+ * @defaultValue 0
68
+ */
69
+ smooth: number | FieldCoeff;
70
+ /**
71
+ * Radius coefficient for chamfered union, intersection, difference ops
72
+ * (only supported for `group()` shapes). If given as function, it will be
73
+ * called with the current SDF query point and the return value will be used
74
+ * as param. Ignored if zero (default).
75
+ *
76
+ * @defaultValue 0
77
+ */
78
+ chamfer: number | FieldCoeff;
79
+ /**
80
+ * Radius coefficient for rounded union, intersection, difference ops (only
81
+ * supported for `group()` shapes). If given as function, it will be called
82
+ * with the current SDF query point and the return value will be used as
83
+ * param. Ignored if zero (default).
84
+ *
85
+ * @defaultValue 0
86
+ */
87
+ round: number | FieldCoeff;
88
+ /**
89
+ * Coefficient tuple of `[radius, num]` used for stepped union,
90
+ * intersection, difference ops (only supported for `group()` shapes). If
91
+ * given as function, it will be called with the current SDF query point and
92
+ * the return value will be used as param. Ignored if zero (default).
93
+ */
94
+ steps?: [number, number] | FieldCoeff<[number, number]>;
95
+ /**
96
+ * If given, this value is used to control the number of samples used for
97
+ * converting the original geometry to a polygon or polyline. See
98
+ * {@link asSDF} for more details.
99
+ */
100
+ samples?: number;
11
101
  }
12
102
  //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,26 @@
1
+ import type { NumericArray } from "@thi.ng/api";
2
+ import type { Polygon } from "@thi.ng/geom";
3
+ import type { AABBLike } from "@thi.ng/geom-api";
4
+ import type { ReadonlyVec } from "@thi.ng/vectors";
5
+ import type { SDFn } from "./api.js";
6
+ /**
7
+ * Extract contour polygons from given SDF at specified `distances`. The SDF can
8
+ * be either given as distance function or as pre-discretized image (e.g. as
9
+ * result of {@link sample2d}). The SDF will be sampled in the given bounding
10
+ * rect and resolution (if SDF was given as image, resolution MUST be the
11
+ * same!).
12
+ *
13
+ * @remarks
14
+ * If `distances` are not given, only the original boundary (i.e. distance=0)
15
+ * will be extracted. By default all resulting polygons will be simplified using
16
+ * Douglas-Peucker with a threshold of `eps`. By default this will only remove
17
+ * co-linear vertices, but more agressive settings are possible/recommended.
18
+ *
19
+ * @param sdf
20
+ * @param bounds
21
+ * @param res
22
+ * @param distances
23
+ * @param eps
24
+ */
25
+ export declare const asPolygons: (sdf: NumericArray | SDFn, bounds: AABBLike, res: ReadonlyVec, distances?: Iterable<number>, eps?: number) => Polygon[];
26
+ //# sourceMappingURL=as-polygons.d.ts.map
package/as-polygons.js ADDED
@@ -0,0 +1,39 @@
1
+ import { isFunction } from "@thi.ng/checks/is-function";
2
+ import { isolines, setBorder } from "@thi.ng/geom-isoline";
3
+ import { simplify } from "@thi.ng/geom-resample/simplify";
4
+ import { polygon } from "@thi.ng/geom/polygon";
5
+ import { comp } from "@thi.ng/transducers/comp";
6
+ import { map } from "@thi.ng/transducers/map";
7
+ import { mapcat } from "@thi.ng/transducers/mapcat";
8
+ import { push } from "@thi.ng/transducers/push";
9
+ import { transduce } from "@thi.ng/transducers/transduce";
10
+ import { add2 } from "@thi.ng/vectors/add";
11
+ import { div2 } from "@thi.ng/vectors/div";
12
+ import { sample2d } from "./sample.js";
13
+ /**
14
+ * Extract contour polygons from given SDF at specified `distances`. The SDF can
15
+ * be either given as distance function or as pre-discretized image (e.g. as
16
+ * result of {@link sample2d}). The SDF will be sampled in the given bounding
17
+ * rect and resolution (if SDF was given as image, resolution MUST be the
18
+ * same!).
19
+ *
20
+ * @remarks
21
+ * If `distances` are not given, only the original boundary (i.e. distance=0)
22
+ * will be extracted. By default all resulting polygons will be simplified using
23
+ * Douglas-Peucker with a threshold of `eps`. By default this will only remove
24
+ * co-linear vertices, but more agressive settings are possible/recommended.
25
+ *
26
+ * @param sdf
27
+ * @param bounds
28
+ * @param res
29
+ * @param distances
30
+ * @param eps
31
+ */
32
+ export const asPolygons = (sdf, bounds, res, distances = [0], eps = 1e-6) => {
33
+ const $sdf = isFunction(sdf) ? sample2d(sdf, bounds, res) : sdf;
34
+ const { pos, size } = bounds;
35
+ const [resX, resY] = res;
36
+ setBorder($sdf, resX, resY, 1e6);
37
+ const scale = div2([], size, [resX - 1, resY - 1]);
38
+ return transduce(comp(mapcat((iso) => isolines($sdf, resX, resY, iso, scale)), map((pts) => pts.map((p) => add2(null, p, pos))), map((pts) => polygon(eps >= 0 ? simplify(pts, eps, true) : pts))), push(), distances);
39
+ };
package/as-sdf.d.ts CHANGED
@@ -9,14 +9,22 @@ import type { SDFn } from "./api.js";
9
9
  * Currently supported shape types:
10
10
  *
11
11
  * - circle
12
+ * - cubic (auto-converted to polyline)
12
13
  * - ellipse
13
14
  * - group
14
15
  * - line
16
+ * - path (auto-converted to polygon/polyline)
15
17
  * - points
16
18
  * - polygon
17
19
  * - polyline
18
20
  * - quadratic bezier
19
21
  * - rect
22
+ *
23
+ * For shapes which need to be converted to polygons/polylines, the
24
+ * {@link SDFAttribs.samples} attribute can be used to control the resulting
25
+ * number of vertices. If not specified {@link @thi.ng/geom-api#DEFAULT_SAMPLES}
26
+ * will be used (which can be globally set via
27
+ * {@link @thi.ng/geom-api#setDefaultSamples}).
20
28
  */
21
29
  export declare const asSDF: MultiFn1<IShape, SDFn>;
22
30
  //# sourceMappingURL=as-sdf.d.ts.map
package/as-sdf.js CHANGED
@@ -1,9 +1,13 @@
1
1
  import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
2
+ import { assert } from "@thi.ng/errors/assert";
2
3
  import { unsupported } from "@thi.ng/errors/unsupported";
4
+ import { asPolygon } from "@thi.ng/geom/as-polygon";
5
+ import { asPolyline } from "@thi.ng/geom/as-polyline";
6
+ import { simplify } from "@thi.ng/geom/simplify";
3
7
  import { __dispatch } from "@thi.ng/geom/internal/dispatch";
4
8
  import { add2 } from "@thi.ng/vectors/add";
5
9
  import { mulN2 } from "@thi.ng/vectors/muln";
6
- import { difference, intersection, smoothDifference, smoothIntersection, smoothUnion, union, } from "./ops.js";
10
+ import { chamferDiff, chamferIsec, chamferUnion, diff, isec, roundDiff, roundIsec, roundUnion, smoothDiff, smoothIsec, smoothUnion, stepDiff, stepIsec, stepUnion, union, } from "./ops.js";
7
11
  import { box2, circle2, DEFAULT_ATTRIBS, ellipse2, line2, points2, polygon2, polyline2, quadratic2, withSDFAttribs, } from "./shapes.js";
8
12
  /**
9
13
  * Takes an {@link @thi.ng/geom-api#IShape} instance (possibly a tree, e.g. via
@@ -13,14 +17,22 @@ import { box2, circle2, DEFAULT_ATTRIBS, ellipse2, line2, points2, polygon2, pol
13
17
  * Currently supported shape types:
14
18
  *
15
19
  * - circle
20
+ * - cubic (auto-converted to polyline)
16
21
  * - ellipse
17
22
  * - group
18
23
  * - line
24
+ * - path (auto-converted to polygon/polyline)
19
25
  * - points
20
26
  * - polygon
21
27
  * - polyline
22
28
  * - quadratic bezier
23
29
  * - rect
30
+ *
31
+ * For shapes which need to be converted to polygons/polylines, the
32
+ * {@link SDFAttribs.samples} attribute can be used to control the resulting
33
+ * number of vertices. If not specified {@link @thi.ng/geom-api#DEFAULT_SAMPLES}
34
+ * will be used (which can be globally set via
35
+ * {@link @thi.ng/geom-api#setDefaultSamples}).
24
36
  */
25
37
  export const asSDF = defmulti(__dispatch, {
26
38
  quad: "poly",
@@ -28,31 +40,40 @@ export const asSDF = defmulti(__dispatch, {
28
40
  }, {
29
41
  [DEFAULT]: ($) => unsupported(`shape type: ${$.type}`),
30
42
  circle: ($) => circle2($.pos, $.r, __sdfAttribs($.attribs)),
43
+ cubic: ($) => asSDF(simplify(asPolyline($, (__sdfAttribs($.attribs) || {}).samples), 0)),
31
44
  ellipse: ($) => ellipse2($.pos, $.r, __sdfAttribs($.attribs)),
32
- group: ({ attribs, children }) => {
33
- const $attribs = { ...DEFAULT_ATTRIBS, ...__sdfAttribs(attribs) };
45
+ group: ($) => {
46
+ const { attribs, children } = $;
47
+ const attr = { ...DEFAULT_ATTRIBS, ...__sdfAttribs(attribs) };
48
+ __validateAttribs(attr);
34
49
  const $children = children.map(asSDF);
35
50
  let res;
36
51
  if ($children.length > 1) {
37
- switch ($attribs.combine) {
52
+ switch (attr.combine) {
38
53
  case "diff":
39
- res =
40
- $attribs.smooth !== 0
41
- ? smoothDifference($attribs.smooth, ...$children)
42
- : difference(...$children);
54
+ res = __selectCombineOp(attr, $children, diff, {
55
+ chamfer: chamferDiff,
56
+ round: roundDiff,
57
+ smooth: smoothDiff,
58
+ steps: stepDiff,
59
+ });
43
60
  break;
44
61
  case "isec":
45
- res =
46
- $attribs.smooth !== 0
47
- ? smoothIntersection($attribs.smooth, ...$children)
48
- : intersection($children);
62
+ res = __selectCombineOp(attr, $children, isec, {
63
+ chamfer: chamferIsec,
64
+ round: roundIsec,
65
+ smooth: smoothIsec,
66
+ steps: stepIsec,
67
+ });
49
68
  break;
50
69
  case "union":
51
70
  default: {
52
- res =
53
- $attribs.smooth !== 0
54
- ? smoothUnion($attribs.smooth, ...$children)
55
- : union($children);
71
+ res = __selectCombineOp(attr, $children, union, {
72
+ chamfer: chamferUnion,
73
+ round: roundUnion,
74
+ smooth: smoothUnion,
75
+ steps: stepUnion,
76
+ });
56
77
  }
57
78
  }
58
79
  }
@@ -60,11 +81,15 @@ export const asSDF = defmulti(__dispatch, {
60
81
  res = $children[0];
61
82
  }
62
83
  else {
63
- return $attribs.flip ? () => -Infinity : () => Infinity;
84
+ return attr.flip ? () => -Infinity : () => Infinity;
64
85
  }
65
- return withSDFAttribs(res, $attribs);
86
+ return withSDFAttribs(res, attr);
66
87
  },
67
88
  line: ({ points: [a, b], attribs }) => line2(a, b, __sdfAttribs(attribs)),
89
+ path: ($) => {
90
+ const n = (__sdfAttribs($.attribs) || {}).samples;
91
+ return asSDF(simplify($.closed ? asPolygon($, n) : asPolyline($, n), 0));
92
+ },
68
93
  points: ($) => points2($.points, __sdfAttribs($.attribs)),
69
94
  poly: ($) => polygon2($.points, __sdfAttribs($.attribs)),
70
95
  polyline: ($) => polyline2($.points, __sdfAttribs($.attribs)),
@@ -76,3 +101,13 @@ export const asSDF = defmulti(__dispatch, {
76
101
  });
77
102
  /** @internal */
78
103
  const __sdfAttribs = (attribs) => attribs ? attribs.__sdf : null;
104
+ const OPS = ["chamfer", "round", "smooth", "steps"];
105
+ const __validateAttribs = (attribs) => assert(OPS.filter((x) => attribs[x]).length < 2, "only 1 of these options can be used at once: chamfer, round, smooth");
106
+ const __selectCombineOp = (attribs, children, op, paramOps) => {
107
+ for (let k of OPS) {
108
+ if (attribs[k]) {
109
+ return paramOps[k](attribs[k], children);
110
+ }
111
+ }
112
+ return op(children);
113
+ };
package/bounds.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { ReadonlyVec } from "@thi.ng/vectors";
2
+ import type { SDFn } from "./api.js";
3
+ /**
4
+ * Augments given distance function with a bounding circle pre-check. The circle
5
+ * can be either given as `[centroid, radius]` tuple or can be auto-computed
6
+ * from given array of points. When the returned function is called it first
7
+ * computes the distance to the bounding circle and *only* if that is less then
8
+ * the current (already computed) min distance (default: positive ∞), the
9
+ * original (wrapped) `sdf` function is called. If the distance to the bounding
10
+ * circle is > `minD`, the presumably more costly `sdf` is skipped and `minD`
11
+ * returned.
12
+ *
13
+ * @remarks
14
+ * Currently used for {@link polygon2}, {@link polyline2}, {@link points2}.
15
+ *
16
+ * @param sdf
17
+ * @param pts
18
+ */
19
+ export declare function withBoundingCircle(sdf: SDFn, pts: ReadonlyVec[]): SDFn;
20
+ export declare function withBoundingCircle(sdf: SDFn, centroid: ReadonlyVec, r: number): SDFn;
21
+ /**
22
+ * Similar to {@link withBoundingCircle}, but using a bounding rect (defined via
23
+ * `min`/`max` or computed from an array of points).
24
+ *
25
+ * @param sdf
26
+ * @param pts
27
+ */
28
+ export declare function withBoundingRect(sdf: SDFn, pts: ReadonlyVec[]): SDFn;
29
+ export declare function withBoundingRect(sdf: SDFn, min: ReadonlyVec, max: ReadonlyVec): SDFn;
30
+ //# sourceMappingURL=bounds.d.ts.map
package/bounds.js ADDED
@@ -0,0 +1,31 @@
1
+ import { boundingCircle, bounds2 } from "@thi.ng/geom-poly-utils/bounds";
2
+ import { addmN2 } from "@thi.ng/vectors/addmn";
3
+ import { sub2 } from "@thi.ng/vectors/sub";
4
+ import { submN2 } from "@thi.ng/vectors/submn";
5
+ import { distBox2 } from "./dist.js";
6
+ export function withBoundingCircle(sdf, ...args) {
7
+ let [[cx, cy], r] = args.length === 1
8
+ ? boundingCircle(args[0])
9
+ : args;
10
+ r *= r;
11
+ return (p, minD = Infinity) => {
12
+ if (minD === Infinity)
13
+ return sdf(p, minD);
14
+ const dx = p[0] - cx;
15
+ const dy = p[1] - cy;
16
+ return dx * dx + dy * dy - r < minD * minD ? sdf(p, minD) : minD;
17
+ };
18
+ }
19
+ export function withBoundingRect(sdf, ...args) {
20
+ const [min, max] = args.length === 1 ? bounds2(args[0]) : args;
21
+ const centroid = addmN2([], min, max, 0.5);
22
+ const hSize = submN2([], max, min, 0.5);
23
+ const t = [0, 0];
24
+ return (p, minD = Infinity) => {
25
+ if (minD === Infinity)
26
+ return sdf(p, minD);
27
+ return distBox2(sub2(t, p, centroid), hSize) < minD
28
+ ? sdf(p, minD)
29
+ : minD;
30
+ };
31
+ }
package/dist.d.ts CHANGED
@@ -1,10 +1,97 @@
1
1
  import { ReadonlyVec } from "@thi.ng/vectors/api";
2
+ /**
3
+ * Computes signed distance to centered 2D circle of given `radius`.
4
+ *
5
+ * @remarks
6
+ * Ported from code by Inigo Quilez:
7
+ * https://iquilezles.org/articles/distfunctions2d/
8
+ *
9
+ * @param p
10
+ * @param radius
11
+ */
2
12
  export declare const distCircle2: (p: ReadonlyVec, radius: number) => number;
13
+ /**
14
+ * Computes signed distance to centered 2D box of given `halfSize`.
15
+ *
16
+ * @remarks
17
+ * Ported from code by Inigo Quilez:
18
+ * https://iquilezles.org/articles/distfunctions2d/
19
+ *
20
+ * @param p
21
+ * @param halfSize
22
+ */
3
23
  export declare const distBox2: (p: ReadonlyVec, halfSize: ReadonlyVec) => number;
4
- export declare const distPolygon2: (pts: ReadonlyVec[], p: ReadonlyVec) => number;
5
- export declare const distSegment2: (a: ReadonlyVec, b: ReadonlyVec, p: ReadonlyVec) => number;
6
- export declare const distPolyline2: (pts: ReadonlyVec[], p: ReadonlyVec) => number;
24
+ /**
25
+ * Computes signed distance to the 2D polygon defined by given point array.
26
+ *
27
+ * @remarks
28
+ * Ported from code by Inigo Quilez:
29
+ * https://iquilezles.org/articles/distfunctions2d/
30
+ *
31
+ * @param p
32
+ * @param pts
33
+ */
34
+ export declare const distPolygon2: (p: ReadonlyVec, pts: ReadonlyVec[]) => number;
35
+ /**
36
+ * Computes distance to 2D line segment defined by endpoints `a` and `b`.
37
+ *
38
+ * @remarks
39
+ * Ported from code by Inigo Quilez:
40
+ * https://iquilezles.org/articles/distfunctions2d/
41
+ *
42
+ * @param p
43
+ * @param a
44
+ * @param b
45
+ */
46
+ export declare const distSegment2: (p: ReadonlyVec, a: ReadonlyVec, b: ReadonlyVec) => number;
47
+ /**
48
+ * Computes distance to 2D polyline defined by given point array (i.e. the union
49
+ * of pairwise results of {@link distSegment2}).
50
+ *
51
+ * @param p
52
+ * @param pts
53
+ */
54
+ export declare const distPolyline2: (p: ReadonlyVec, pts: ReadonlyVec[]) => number;
55
+ /**
56
+ * Computes signed distance to circular arc with given aperture, radius and
57
+ * radius offset (thickness). The aperture is symmetric along the Y-axis and has
58
+ * its origin in +Y.
59
+ *
60
+ * @remarks
61
+ * Ported from code by Inigo Quilez:
62
+ * https://iquilezles.org/articles/distfunctions2d/
63
+ *
64
+ * @param p
65
+ * @param apert - pre-computed vec2 of [sin, cos] of aperture angle
66
+ * @param ra - inner radius
67
+ * @param rb - outer radius offset (thickness)
68
+ */
7
69
  export declare const distArc2: (p: ReadonlyVec, apert: ReadonlyVec, ra: number, rb: number) => number;
70
+ /**
71
+ * Computes (unsigned, by default) distance to given 2D quadratic bezier
72
+ * segment. If `signed` is set to true, points in the curve's interior region
73
+ * will yield negative distances.
74
+ *
75
+ * @remarks
76
+ * Based on code by Inigo Quilez:
77
+ * https://iquilezles.org/articles/distfunctions2d/
78
+ *
79
+ * @param pos
80
+ * @param a
81
+ * @param b
82
+ * @param c
83
+ * @param signed
84
+ */
8
85
  export declare const distQuadratic2: (pos: ReadonlyVec, a: ReadonlyVec, b: ReadonlyVec, c: ReadonlyVec, signed?: boolean) => number;
86
+ /**
87
+ * Computes signed distance to centered 2D ellipse.
88
+ *
89
+ * @remarks
90
+ * Based on code by Inigo Quilez:
91
+ * https://iquilezles.org/articles/distfunctions2d/
92
+ *
93
+ * @param p
94
+ * @param radii
95
+ */
9
96
  export declare const distEllipse2: ([px, py]: ReadonlyVec, [abx, aby]: ReadonlyVec) => number;
10
97
  //# sourceMappingURL=dist.d.ts.map