Skip to content

Commit

Permalink
fix(geom-closest-point): use alt algorithm closestPointEllipse()
Browse files Browse the repository at this point in the history
- new approach has no weird edge cases as previous
  • Loading branch information
postspectacular committed Sep 8, 2020
1 parent 11eb67e commit 6b3d00f
Showing 1 changed file with 29 additions and 45 deletions.
74 changes: 29 additions & 45 deletions packages/geom-closest-point/src/ellipse.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,44 @@
import { SQRT3, THIRD } from "@thi.ng/math";
import { clamp01, SQRT2_2 } from "@thi.ng/math";
import { ReadonlyVec } from "@thi.ng/vectors";

/**
* Based on: https://www.iquilezles.org/www/articles/ellipsedist/ellipsedist.htm
*
* @remarks
* Updated to avoid `NaN` if `px == eo.x` or `py == eo.y`
* Based on iterative solution by Luc Maisonobe:
*
* - https://www.spaceroots.org/documents/distance/distance-to-ellipse.pdf
* - https://gist.github.com/JohannesMP/777bdc8e84df6ddfeaa4f0ddb1c7adb3
*
* Further optimizations: constant folding, avoiding duplicate calculations in
* loop
*
* @param p - query point
* @param eo - ellipse center/origin
* @param er - ellipse radii
* @param n - number of iterations
*/
export const closestPointEllipse = (
[px, py]: ReadonlyVec,
[ex, ey]: ReadonlyVec,
[rx, ry]: ReadonlyVec
[rx, ry]: ReadonlyVec,
n = 3
) => {
px = Math.abs(px - ex);
py = Math.abs(py - ey);
const flip = rx > ry;
if (flip) {
let t = px;
px = py;
py = t;
t = rx;
rx = ry;
ry = t;
}
const l = ry * ry - rx * rx;
const m = (rx * px) / l;
const m2 = m * m;
const n = (ry * py) / l;
const n2 = n * n;
const c = (m2 + n2 - 1) / 3;
const c3 = c * c * c;
const q = c3 + m2 * n2 * 2;
const d = c3 + m2 * n2;
const g = m + m * n2;
let co: number;
if (d < 0) {
const h = Math.acos(q / c3) / 3;
const s = Math.cos(h);
const t = Math.sin(h) * SQRT3;
const a = Math.sqrt(-c * (s + t + 2) + m2);
const b = Math.sqrt(-c * (s - t + 2) + m2);
co = (b + Math.sign(l) * a + Math.abs(g) / (a * b) - m) / 2;
} else {
const h = 2 * m * n * Math.sqrt(d);
const s = Math.sign(q + h) * Math.pow(Math.abs(q + h), THIRD);
const u = Math.sign(q - h) * Math.pow(Math.abs(q - h), THIRD);
const a = -s - u - 4 * c + 2 * m2;
const b = (s - u) * SQRT3;
const rm = Math.sqrt(a * a + b * b);
const p = Math.max(rm - a, 0);
co = p > 0 ? (b / p + (2 * g) / rm - m) / 2 : 1;
const apx = Math.abs(px - ex);
const apy = Math.abs(py - ey);
const ab = (rx * rx - ry * ry) / rx;
const ba = (ry * ry - rx * rx) / ry;
let tx = SQRT2_2;
let ty = tx;
for (; --n >= 0; ) {
const _ex = ab * (tx * tx * tx);
const _ey = ba * (ty * ty * ty);
const qx = apx - _ex;
const qy = apy - _ey;
const q = Math.hypot(rx * tx - _ex, ry * ty - _ey) / Math.hypot(qx, qy);
tx = clamp01((qx * q + _ex) / rx);
ty = clamp01((qy * q + _ey) / ry);
const t = Math.hypot(tx, ty);
tx /= t;
ty /= t;
}
const si = Math.sqrt(Math.max(1 - co * co, 0));
return flip ? [ry * si + ex, rx * co + ey] : [rx * co + ex, ry * si + ey];
return [rx * (px < ex ? -tx : tx) + ex, ry * (py < ey ? -ty : ty) + ey];
};

0 comments on commit 6b3d00f

Please sign in to comment.