Skip to content

Commit

Permalink
feat(geom-axidraw): add pointsByNearestNeighbor() ordering
Browse files Browse the repository at this point in the history
- use pointsByNearestNeighbor() as default for point clouds
  - new method is 25%-30% faster than pointsByProximity()
- update PointOrdering/ShapeOrdering return types
- update doc strings
- update deps
  • Loading branch information
postspectacular committed Dec 10, 2022
1 parent 42bf4eb commit d1ebd21
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 23 deletions.
2 changes: 1 addition & 1 deletion packages/geom-axidraw/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
"@thi.ng/compare": "^2.1.18",
"@thi.ng/defmulti": "^2.1.23",
"@thi.ng/geom": "^3.4.23",
"@thi.ng/geom-accel": "^3.2.28",
"@thi.ng/geom-api": "^3.3.20",
"@thi.ng/geom-clip-line": "^2.1.37",
"@thi.ng/geom-isec": "^2.1.37",
"@thi.ng/geom-poly-utils": "^2.3.21",
"@thi.ng/vectors": "^7.5.26"
},
"devDependencies": {
Expand Down
10 changes: 6 additions & 4 deletions packages/geom-axidraw/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FnU } from "@thi.ng/api";
import type { Fn } from "@thi.ng/api";
import type { IShape } from "@thi.ng/geom-api";
import type { ReadonlyVec } from "@thi.ng/vectors";

Expand Down Expand Up @@ -42,17 +42,19 @@ export interface AxiDrawAttribs {
down: number;
/**
* Ordering function (in lieu of full path planning/optimization, which is
* planned for a later stage). By default order of appearance is used.
* planned for a later stage). For shapes other than `points()`, order of
* appearance is used by default.
*
* @remarks
* Currently available implementations:
*
* - {@link pointsByNearestNeighbor} (for `points()`, default)
* - {@link pointsByProximity} (for `points()`)
* - {@link shapesByProximity} (for `group()`)
*/
sort: PointOrdering | ShapeOrdering;
}

export type PointOrdering = FnU<ReadonlyVec[]>;
export type PointOrdering = Fn<ReadonlyVec[], Iterable<ReadonlyVec>>;

export type ShapeOrdering = FnU<IShape[]>;
export type ShapeOrdering = Fn<IShape[], Iterable<IShape>>;
17 changes: 11 additions & 6 deletions packages/geom-axidraw/src/as-axidraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { asPolyline } from "@thi.ng/geom/as-polyline";
import { __dispatch } from "@thi.ng/geom/internal/dispatch";
import type { ReadonlyVec } from "@thi.ng/vectors";
import type { AxiDrawAttribs, PointOrdering, ShapeOrdering } from "./api.js";
import { pointsByNearestNeighbor } from "./sort.js";

export interface AsAxiDrawOpts {
/**
Expand Down Expand Up @@ -38,17 +39,19 @@ export interface AsAxiDrawOpts {
* The provided conversion options can (and will) be overridden by a shape's
* `__axi` attribute. See {@link AxiDrawAttribs} for details.
*
* Currently supported shape types (basically all types which
* are supported by the
* Currently supported shape types (at least all types which are supported by
* the
* [`asPolyline()`](https://docs.thi.ng/umbrella/geom/functions/asPolyline.html)
* function):
*
* - arc
* - circle
* - cubic
* - ellipse
* - group
* - line
* - path
* - points
* - polygon
* - polyline
* - quad
Expand Down Expand Up @@ -118,8 +121,10 @@ function* __points(
opts?: Partial<AsAxiDrawOpts>
): IterableIterator<DrawCommand> {
if (!pts.length) return;
const { clip, delayDown, delayUp, down, speed, sort } =
__axiAttribs(attribs);
const { clip, delayDown, delayUp, down, speed, sort } = {
sort: pointsByNearestNeighbor(),
...__axiAttribs(attribs),
};
const clipPts = clip || opts?.clip;
if (clipPts) {
pts = pts.filter((p) => !!pointInPolygon2(p, clipPts));
Expand All @@ -128,10 +133,10 @@ function* __points(
yield UP;
if (down != undefined) yield ["pen", down];
for (let p of sort ? (<PointOrdering>sort)(pts) : pts) {
yield* <DrawCommand[]>[
yield* [
["m", p, speed],
["d", delayDown],
["up", delayUp],
["u", delayUp],
];
}
if (down != undefined) yield ["pen"];
Expand Down
41 changes: 31 additions & 10 deletions packages/geom-axidraw/src/sort.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
import { sortByCachedKey } from "@thi.ng/arrays/sort-cached";
import { compareNumAsc } from "@thi.ng/compare/numeric";
import { KdTreeSet } from "@thi.ng/geom-accel/kd-tree-set";
import type { IShape } from "@thi.ng/geom-api";
import { bounds2 } from "@thi.ng/geom-poly-utils/bounds";
import { bounds } from "@thi.ng/geom/bounds";
import { centroid } from "@thi.ng/geom/centroid";
import { __collBounds } from "@thi.ng/geom/internal/bounds";
import { ReadonlyVec, ZERO2 } from "@thi.ng/vectors/api";
import { distSq2 } from "@thi.ng/vectors/distsq";
import type { PointOrdering, ShapeOrdering } from "./api.js";

/**
* Higher order point sorting fn. Sorts points by proximity to given `ref` point
* (default: bounds min).
* Higher order point ordering fn. Lazily sorts points by nearest neighbor
* distance, starting selection of first point based on given `ref` point
* (default: [0, 0]).
*
* @remarks
* Internally uses a
* [`KdTreeSet`](https://docs.thi.ng/umbrella/geom-accel/classes/KdTreeSet.html)
* to index all points and then successively perform efficient nearest neighbor
* searches (always w.r.t the most recent result point).
*
* @param ref
*/
export const pointsByNearestNeighbor = (
ref: ReadonlyVec = ZERO2
): PointOrdering =>
function* (pts: ReadonlyVec[]) {
const index = new KdTreeSet(2, pts);
while (index.size) {
ref = index.queryKeys(ref, 1e4, 1)[0];
index.remove(ref);
yield ref;
}
};

/**
* Higher order point ordering fn. Sorts points by proximity to given `ref` point
* (default: [0, 0]).
*
* @param ref
*/
export const pointsByProximity =
(ref?: ReadonlyVec): PointOrdering =>
(ref: ReadonlyVec = ZERO2): PointOrdering =>
(pts: ReadonlyVec[]) => {
ref = ref || bounds2(pts)[0];
return sortByCachedKey(
pts.slice(),
(p) => distSq2(p, ref!),
Expand All @@ -28,14 +50,13 @@ export const pointsByProximity =

/**
* Higher order shape sorting fn. Sorts shapes by their centroid's proximity to
* given `ref` point (default: bounds min).
* given `ref` point (default: [0, 0]).
*
* @param ref
*/
export const shapesByProximity =
(ref?: ReadonlyVec): ShapeOrdering =>
(ref: ReadonlyVec = ZERO2): ShapeOrdering =>
(shapes: IShape[]) => {
ref = ref || __collBounds(shapes, bounds)![0];
return sortByCachedKey(
shapes.slice(),
(s) => distSq2(centroid(s) || ZERO2, ref!),
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2984,7 +2984,7 @@ __metadata:
languageName: unknown
linkType: soft

"@thi.ng/geom-accel@workspace:^, @thi.ng/geom-accel@workspace:packages/geom-accel":
"@thi.ng/geom-accel@^3.2.28, @thi.ng/geom-accel@workspace:^, @thi.ng/geom-accel@workspace:packages/geom-accel":
version: 0.0.0-use.local
resolution: "@thi.ng/geom-accel@workspace:packages/geom-accel"
dependencies:
Expand Down Expand Up @@ -3052,10 +3052,10 @@ __metadata:
"@thi.ng/compare": ^2.1.18
"@thi.ng/defmulti": ^2.1.23
"@thi.ng/geom": ^3.4.23
"@thi.ng/geom-accel": ^3.2.28
"@thi.ng/geom-api": ^3.3.20
"@thi.ng/geom-clip-line": ^2.1.37
"@thi.ng/geom-isec": ^2.1.37
"@thi.ng/geom-poly-utils": ^2.3.21
"@thi.ng/testament": ^0.3.6
"@thi.ng/vectors": ^7.5.26
rimraf: ^3.0.2
Expand Down

0 comments on commit d1ebd21

Please sign in to comment.