OpenLayer4——贝塞尔曲线插值算法

这是早期项目中用到的代码,经过 AI 优化调整,可能需要适当调整一下。

/**
 * 生成贝塞尔曲线插值点
 *
 * 阶乘数为数组长度减一
 *
 * @param {Array} points - 控制点数组,格式 [[x0, y0], [x1, y1], ...]
 * @param {Array} res - 存储结果的数组(可选)
 * @param {number} step - 步长,取值越小曲线越精细
 * @returns {Array} 贝塞尔曲线上的点坐标数组
 */
function createBezierCurvePoints(points, res, step = 0.2) {
    // 贝塞尔曲线阶数
    let n = points.length - 1;

    // 阶乘查找表
    const factorials = [1]; // 0! = 1
    for (let i = 1; i <= n; i++) {
        factorials[i] = factorials[i - 1] * i;
    }

    for (let t = 0; t <= 1; t = t + step) {
        res.push(multiplyBezier(points, n, factorials, t));
    }
}

/**
 * 高阶贝塞尔曲线
 *
 * @param points {Array} - 控制点数组,格式 [[x0, y0], [x1, y1], ...]
 * @param n {number} - 阶乘数
 * @param factorials {Array} - 阶乘查找表,提前算好阶乘的结果,减少重复计算
 * @param t {number} - 参数 t ∈ [0, 1]
 * @returns [x,y] ret
 */
function multiplyBezier(points, n, factorials, t) {
    let x = 0, y = 0;
    for (let i = 0; i <= n; i++) {
        const comb = factorials[n] / (factorials[i] * factorials[n - i]);
        x += comb * Math.pow((1 - t), n - i) * Math.pow(t, i) * points[i][0];
        y += comb * Math.pow((1 - t), n - i) * Math.pow(t, i) * points[i][1];
    }
    return [x, y];
}

/**
 * 2 阶贝塞尔曲线
 *
 * @param p0 - 点 0
 * @param p1 - 点 1
 * @param p2 - 点 2
 * @param t {number} - 参数 t ∈ [0, 1]
 * @returns [x,y] ret
 */
function quadraticBezier(p0, p1, p2, t) {
    // 使用数学公式计算贝塞尔曲线上的点
    const temp = 1 - t;
    const x = Math.pow(temp, 2) * p0[0] + 2 * temp * t * p1[0] + Math.pow(t, 2) * p2[0];
    const y = Math.pow(temp, 2) * p0[1] + 2 * temp * t * p1[1] + Math.pow(t, 2) * p2[1];
    return [x, y];
}

/**
 * 3 阶贝塞尔曲线
 *
 * @param p0 - 点 0
 * @param p1 - 点 1
 * @param p2 - 点 2
 * @param p3 - 点 3
 * @param t {number} - 参数 t ∈ [0, 1]
 * @returns [x,y] ret
 */
function cubicBezier(p0, p1, p2, p3, t) {
    const temp = 1 - t;
    const x = p0[0] * temp * temp * temp + 3 * p1[0] * t * temp * temp + 3 * p2[0] * t * t * temp + p3[0] * t * t * t;
    const y = p0[1] * temp * temp * temp + 3 * p1[1] * t * temp * temp + 3 * p2[1] * t * t * temp + p3[1] * t * t * t;
    return [x, y];
}

/**
 * p1 到 p2 进行 n 等分,取第 m 个点的坐标
 *
 * @param n - p1 - p2 进行 n 等分
 * @param m - 取第 m 个点的坐标
 * @param p1 - 点 1
 * @param p2 - 点 2
 */
function dividedPoint(n, m, p1, p2) {
    return [(p2[0] - p1[0]) / n * m + p1[0], (p2[1] - p1[1]) / n * m + p1[1]];
}

/**
 * 平滑转角

 * 使用高阶贝塞尔曲线,正方形会变圆(水滴形);
 *
 * 有时候,只是希望转角圆润一些,而不是整体变为曲线,
 *
 * 简单说,就是类似于 css 中设置的 border-radio 的效果
 *
 * @example
 * ```
 * 原始数据:(0,0),(5,0),(5,5)
 * 计算结果:(4,0),(5,0),(5,1)
 *
 * 需要从 (4,0) 开始绘制曲线,结束于 (5,1) 点
 * ```
 * @param p0 - 点 0
 * @param p1 - 点 1
 * @param p2 - 点 2
 * @param res - 结果数组
 * @param segments 分段数,越平滑
 */
function smoothCorner(p0, p1, p2, res, segments = 20) {
    const denominator = 10;

    //计算 4/5 等分点
    const point1 = dividedPoint(denominator, denominator - 1, p0, p1);
    const point2 = p1;
    //计算 1/5 等分点
    const point3 = dividedPoint(denominator, 1, p1, p2);

    //开始点
    res.push(point1);

    //计算曲线坐标点
    for (let i = 0; i <= segments; i++) {
        const t = i / segments;
        res.push(quadraticBezier(point1, point2, point3, t));
    }

    //结束点
    res.push(point3);
}

/**
 * 使所有的转角变平滑
 *
 * @param line 线段数组
 * @returns {*|*[]}
 */
function smoothLine(line) {
    if (line.length < 3) {
        console.error('图形点的数量不允许小于3:' + line);
        return line;
    } else {
        const n = line.length - 2, res = [];

        // first point
        res.push(line[0]);

        for (let i = 0; i < n; i++) {
            smoothCorner(line[i], line[i + 1], line[i + 2], res);
        }

        // last point
        res.push(line[line.length - 1]);
        return res;
    }
}

export default {
    // example n-bezier
    createBezierCurvePoints,

    // bezier - core
    multiplyBezier, quadraticBezier, cubicBezier,

    // smooth-corner
    dividedPoint, smoothCorner, smoothLine
};

posted on 2022-05-20 10:57  疯狂的妞妞  阅读(664)  评论(0)    收藏  举报

导航