packages/geom-poly-utils/src/tangent.ts
import type { Maybe } from "@thi.ng/api";
import { EPS } from "@thi.ng/math/api";
import type { ReadonlyVec, Vec } from "@thi.ng/vectors";
import { addmN } from "@thi.ng/vectors/addmn";
import { direction } from "@thi.ng/vectors/direction";
import { mag } from "@thi.ng/vectors/mag";
import { mixN } from "@thi.ng/vectors/mixn";
import { normalize } from "@thi.ng/vectors/normalize";
import { perpendicularCCW } from "@thi.ng/vectors/perpendicular";
import { set } from "@thi.ng/vectors/set";
import { sub } from "@thi.ng/vectors/sub";
/**
* Takes an array of polyline/polygon vertices and computes the tangent
* directions for each (point).
*
* @remarks
* Use `close=true` to indicate a polygon and cause the last tangent to be the
* direction from last vertex to the first. If `close=false`, the last point's
* tangent will be the same as the previous one.
*
* The optional `scale` arg can be used to scale the tangents (default: 1).
*
* @param pts -
* @param close -
* @param scale -
*/
export const tangents = (pts: ReadonlyVec[], close = false, scale = 1) => {
const n = pts.length - 1;
const res: Vec[] = [];
for (let i = 1; i <= n; i++) {
res.push(direction([], pts[i - 1], pts[i], scale));
}
res.push(
close ? direction([], pts[n], pts[0], scale) : set([], res[n - 1])
);
return res;
};
/**
* Extended version of {@link tangents}. Takes an array of polyline/polygon
* vertices and computes the smoothed tangent directions for each (point).
*
* @remarks
* By default, the tangent direction is proportionally influenced by the length
* ratio of a point's previous and current line segments: `currLen / (prevLen +
* currLen)`.
*
* If `proportional` is disabled, the tangents will be the simple mean of the
* direction of the prev/curr segments.
*
* The optional `scale` arg can be used to scale the tangents (default: 1).
*
* @param pts -
* @param close -
* @param proportional -
* @param scale -
*/
export const smoothTangents = (
pts: ReadonlyVec[],
close = false,
proportional = true,
scale = 1
) => {
const res: Vec[] = [];
const n = pts.length - 1;
if (n < 1) return res;
let prev: Maybe<Vec>;
let plen: Maybe<number>;
if (close) {
prev = sub([], pts[0], pts[n]);
plen = mag(prev!);
normalize(null, prev!);
}
let t: Vec;
for (let i = 0; i <= n; i++) {
let curr: Vec;
let clen: number;
if (i === n) {
if (close) {
curr = sub([], pts[0], pts[i]);
} else {
res.push(prev!);
return res;
}
} else {
curr = sub([], pts[i + 1], pts[i]);
}
clen = mag(curr);
normalize(null, curr);
if (i > 0 || close) {
t = proportional
? mixN(
[],
prev!,
curr,
Math.min(1, clen / (clen + plen! + EPS))
)
: addmN([], prev!, curr, 0.5);
} else {
t = set([], curr);
}
res.push(normalize(null, t, scale));
prev = curr;
plen = clen;
}
return res;
};
/**
* Transforms an array of 2d tangent vectors into a new array with each tangent
* rotated 90 degrees counter-clockwise.
*
* @param tangents -
*/
export const bitangents2 = (tangents: ReadonlyVec[]) =>
tangents.map((t) => perpendicularCCW([], t));