thi-ng/umbrella

View on GitHub
packages/geom-poly-utils/src/tangent.ts

Summary

Maintainability
A
2 hrs
Test Coverage
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));