Skip to content

Commit

Permalink
feat(geom): add closestPoint() impls for splines, line, polygons, pol…
Browse files Browse the repository at this point in the history
…yline
  • Loading branch information
postspectacular committed Jan 18, 2019
1 parent 627e20d commit eaf1a1b
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 9 deletions.
105 changes: 100 additions & 5 deletions packages/geom3/src/internal/closest-point.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Fn } from "@thi.ng/api";
import { partial } from "@thi.ng/compose";
import {
distSq,
dot,
empty,
magSq,
mixCubic,
mixN,
mixQuadratic,
ReadonlyVec,
set,
sub,
Vec
} from "@thi.ng/vectors3";

export const closestPointRaw =
export const closestPointArray =
(p: ReadonlyVec, pts: Vec[]) => {

let minD = Infinity;
Expand Down Expand Up @@ -53,17 +57,16 @@ export const closestPointSegment =
const t = closestCoeff(p, a, b);
if (t !== undefined && (!insideOnly || t >= 0 && t <= 1)) {
out = out || empty(p);
return t <= 0.0 ?
return t <= 0 ?
set(out, a) :
t >= 1.0 ?
t >= 1 ?
set(out, b) :
mixN(out, a, b, t);
}
};

export const closestPointPolyline =
(p: ReadonlyVec, pts: ReadonlyArray<Vec>, closed = false) => {

const closest = empty(pts[0]);
const tmp = empty(closest);
const n = pts.length - 1;
Expand Down Expand Up @@ -100,7 +103,7 @@ export const closestPointPolyline =
* @param to
*/
export const farthestPointSegment =
(a: Vec, b: Vec, points: Vec[], from = 0, to = points.length) => {
(a: ReadonlyVec, b: ReadonlyVec, points: ReadonlyVec[], from = 0, to = points.length) => {
let maxD = -1;
let maxIdx;
const tmp = empty(a);
Expand All @@ -114,3 +117,95 @@ export const farthestPointSegment =
}
return [maxIdx, Math.sqrt(maxD)];
};

/**
* Performs recursive search for closest point to `p` on cubic curve
* defined by control points `a`,`b`,`c`,`d`. The `res` and `recur`
* params are used to control the recursion behavior. See `closestT`.
*
* @param p
* @param a
* @param b
* @param c
* @param d
* @param res
* @param iter
*/
export const closestPointCubic = (
p: ReadonlyVec,
a: ReadonlyVec,
b: ReadonlyVec,
c: ReadonlyVec,
d: ReadonlyVec,
res = 8,
iter = 4
) => {
const fn = partial(mixCubic, [], a, b, c, d);
return fn(closestT(fn, p, res, iter, 0, 1));
};

/**
* Performs recursive search for closest point to `p` on quadratic curve
* defined by control points `a`,`b`,`c`. The `res` and `recur` params
* are used to control the recursion behavior. See `closestT`.
*
* @param p
* @param a
* @param b
* @param c
* @param res
* @param iter
*/
export const closestPointQuadratic = (
p: ReadonlyVec,
a: ReadonlyVec,
b: ReadonlyVec,
c: ReadonlyVec,
res = 8,
iter = 4
) => {
const fn = partial(mixQuadratic, [], a, b, c);
return fn(closestT(fn, p, res, iter, 0, 1));
};

/**
* Recursively evaluates function `fn` for `res` uniformly spaced values
* `t` in the closed interval `[start,end]` to compute points on a curve
* and returns the `t` producing the minimum distance to query point
* `p`. At each level of recursion the search interval is increasingly
* centered around the currently best `t`.
*
* @param fn
* @param p
* @param res
* @param iter
* @param start
* @param end
*/
const closestT = (
fn: Fn<number, Vec>,
p: ReadonlyVec,
res: number,
iter: number,
start: number,
end: number
) => {
if (iter <= 0) return (start + end) / 2;
const delta = (end - start) / res;
let minT = start;
let minD = Infinity;
for (let i = 0; i <= res; i++) {
const t = start + i * delta;
const q = fn(t);
const d = distSq(p, q);
if (d < minD) {
minD = d;
minT = t;
}
}
return closestT(
fn, p, res, iter - 1,
Math.max(minT - delta, 0),
Math.min(minT + delta, 1)
);
};
55 changes: 51 additions & 4 deletions packages/geom3/src/ops/closest-point.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,68 @@
import { defmulti } from "@thi.ng/defmulti";
import {
add2,
add,
normalize,
ReadonlyVec,
sub2,
sub,
Vec
} from "@thi.ng/vectors3";
import { Circle, IShape, Type } from "../api";
import {
Circle,
Cubic,
IShape,
Line,
PCLike,
Quadratic,
Type
} from "../api";
import {
closestPointArray,
closestPointCubic,
closestPointPolyline,
closestPointQuadratic,
closestPointSegment
} from "../internal/closest-point";
import { dispatch } from "../internal/dispatch";
import { vertices } from "./vertices";

export const closestPoint = defmulti<IShape, ReadonlyVec, Vec>(dispatch);

closestPoint.addAll({

[Type.CIRCLE]:
($: Circle, p) =>
add2(null, normalize(null, sub2([], p, $.pos), $.r), $.pos),
add(null, normalize(null, sub([], p, $.pos), $.r), $.pos),

[Type.CUBIC]:
({ points }: Cubic, p) =>
closestPointCubic(p, points[0], points[1], points[2], points[3]),

[Type.LINE]:
({ points }: Line, p) =>
closestPointSegment(p, points[0], points[1]),

[Type.POLYGON]:
($: PCLike, p) =>
closestPointArray(p, $.points),

[Type.POLYGON]:
($: PCLike, p) =>
closestPointPolyline(p, $.points, true),

[Type.POLYLINE]:
($: PCLike, p) =>
closestPointPolyline(p, $.points),

[Type.QUADRATIC]:
({ points }: Quadratic, p) =>
closestPointQuadratic(p, points[0], points[1], points[2]),

[Type.RECT]:
($, p) =>
closestPointPolyline(p, vertices($), true),

});

closestPoint.isa(Type.QUAD, Type.POLYGON);
closestPoint.isa(Type.SPHERE, Type.CIRCLE);
closestPoint.isa(Type.TRIANGLE, Type.POLYGON);

0 comments on commit eaf1a1b

Please sign in to comment.