Source: utils/algorithms/CubicSplinePath.js

"use strict";
/**
 * @classdesc Compute a natural cubic Bézier spline path from a given sequence of points/vertices.
 *
 * Inspired by http://weitz.de/hobby/
 *
 * @author   Ikaros Kappler (ported from basic script to class).
 * @date     2020-04-15
 * @modified 2020-08-19 Ported this class from vanilla JS to TypeScript.
 * @version  1.0.0
 *
 * @file CubicSplinePath
 * @public
 **/
Object.defineProperty(exports, "__esModule", { value: true });
var CubicBezierCurve_1 = require("../../CubicBezierCurve");
var HobbyPath_1 = require("./HobbyPath");
var Vertex_1 = require("../../Vertex");
;
var CubicSplinePath = /** @class */ (function () {
    /**
     * @constructor
     * @name CubicSplinePath
     * @param {Array<Vertex>=} vertices? - An optional array of vertices to initialize the path with.
     **/
    function CubicSplinePath(vertices) {
        this.vertices = vertices ? vertices : [];
    }
    ;
    /**
    * Add a new point to the end of the vertex sequence.
    *
    * @name addPoint
    * @memberof CubicSplinePath
    * @instance
    * @param {Vertex} p - The vertex (point) to add.
    **/
    CubicSplinePath.prototype.addPoint = function (p) {
        this.vertices.push(p);
    };
    ;
    /**
     * Generate a sequence of cubic Bézier curves from the point set.
     *
     * @name generateCurve
     * @memberof CubicSplinePath
     * @instance
     * @param {boolean=} circular - Specify if the path should be closed.
     * @return Array<CubicBezierCurve>
     **/
    CubicSplinePath.prototype.generateCurve = function (circular) {
        var xs = [];
        var ys = [];
        for (var i = 0; i < this.vertices.length; i++) {
            xs.push(this.vertices[i].x);
            ys.push(this.vertices[i].y);
        }
        var curves = [];
        var n = this.vertices.length;
        if (n > 1) {
            if (n == 2) {
                // for two points, just draw a straight line
                curves.push(new CubicBezierCurve_1.CubicBezierCurve(this.vertices[0], this.vertices[1], this.vertices[0], this.vertices[1]));
            }
            else {
                if (!circular) {
                    // open curve
                    // x1 and y1 contain the coordinates of the first control
                    // points, x2 and y2 those of the second
                    var controlsX = CubicSplinePath.utils.naturalControlsOpen(xs);
                    var controlsY = CubicSplinePath.utils.naturalControlsOpen(ys);
                    for (var i_1 = 1; i_1 < n; i_1++) {
                        // add Bézier segment - two control points and next node
                        curves.push(new CubicBezierCurve_1.CubicBezierCurve(this.vertices[i_1 - 1], this.vertices[i_1], new Vertex_1.Vertex(controlsX.start[i_1 - 1], controlsY.start[i_1 - 1]), // x1[i-1], y1[i-1]), // new Vertex(x1[i-1], y1[i-1]),
                        new Vertex_1.Vertex(controlsX.end[i_1 - 1], controlsY.end[i_1 - 1]) // new Vertex(x2[i-1], y2[i-1])
                        ));
                    }
                }
                else {
                    // closed curve, i.e. endpoints are connected
                    // see comments for open curve
                    var controlsX = CubicSplinePath.utils.naturalControlsClosed(xs);
                    var controlsY = CubicSplinePath.utils.naturalControlsClosed(ys);
                    for (var i_2 = 0; i_2 < n; i_2++) {
                        // if i is n-1, the "next" point is the first one
                        var j = (i_2 + 1) % n;
                        curves.push(new CubicBezierCurve_1.CubicBezierCurve(this.vertices[i_2], this.vertices[j], new Vertex_1.Vertex(controlsX.start[i_2], controlsY.start[i_2]), // new Vertex(x1[i], y1[i]),
                        new Vertex_1.Vertex(controlsX.end[i_2], controlsY.end[i_2]) // new Vertex(x2[i], y2[i])
                        ));
                    }
                }
            }
        }
        return curves;
    };
    ;
    CubicSplinePath.utils = {
        naturalControlsClosed: function (coords) {
            var n = coords.length;
            // a, b, and c are the diagonals of the tridiagonal matrix, d is the
            // right side
            var a = new Array(n);
            var b = new Array(n);
            var c = new Array(n);
            var d = new Array(n);
            // the video explains why the matrix is filled this way
            b[0] = 4;
            c[0] = 1;
            d[0] = 4 * coords[0] + 2 * coords[1];
            a[n - 1] = 1;
            b[n - 1] = 4;
            d[n - 1] = 4 * coords[n - 1] + 2 * coords[0];
            for (var i = 1; i < n - 1; i++) {
                a[i] = 1;
                b[i] = 4;
                c[i] = 1;
                d[i] = 4 * coords[i] + 2 * coords[i + 1];
            }
            // add a one to the two empty corners and solve the system for the
            // first control points
            var x1 = HobbyPath_1.HobbyPath.utils.sherman(a, b, c, d, 1, 1);
            // compute second controls points from first
            var x2 = new Array(n);
            for (var i = 0; i < n - 1; i++)
                x2[i] = 2 * coords[i + 1] - x1[i + 1];
            x2[n - 1] = 2 * coords[0] - x1[0];
            return { start: x1, end: x2 };
        },
        // computes two arrays for the first and second controls points for a
        // natural cubic spline through the points in K, an "open" curve where
        // the curve doesn't return to the starting point; the function works
        // with one coordinate at a time, i.e. it has to be called twice
        naturalControlsOpen: function (coords) {
            var n = coords.length - 1;
            // a, b, and c are the diagonals of the tridiagonal matrix, d is the
            // right side
            var a = new Array(n);
            var b = new Array(n);
            var c = new Array(n);
            var d = new Array(n);
            // the video explains why the matrix is filled this way
            b[0] = 2;
            c[0] = 1;
            d[0] = coords[0] + 2 * coords[1];
            a[n - 1] = 2;
            b[n - 1] = 7;
            d[n - 1] = 8 * coords[n - 1] + coords[n];
            for (var i = 1; i < n - 1; i++) {
                a[i] = 1;
                b[i] = 4;
                c[i] = 1;
                d[i] = 4 * coords[i] + 2 * coords[i + 1];
            }
            // solve the system to get the first control points
            var x1 = HobbyPath_1.HobbyPath.utils.thomas(a, b, c, d);
            // compute second controls points from first
            var x2 = new Array(n);
            for (var i = 0; i < n - 1; i++) {
                x2[i] = 2 * coords[i + 1] - x1[i + 1];
            }
            x2[n - 1] = (coords[n] + x1[n - 1]) / 2;
            return { start: x1, end: x2 };
        }
    }; // END utils
    return CubicSplinePath;
}());
exports.CubicSplinePath = CubicSplinePath;
; // END class
//# sourceMappingURL=CubicSplinePath.js.map