【数学篇】07 # 如何用向量和参数方程描述曲线?

简介: 【数学篇】07 # 如何用向量和参数方程描述曲线?

说明

【跟月影学可视化】学习笔记。



如何用向量描述曲线?

用向量绘制折线的方法来绘制正多边形

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>如何用向量描述曲线</title>
    <style>
        canvas {
            border: 1px dashed salmon;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>
    <script type="module">
        import { Vector2D } from './common/lib/vector2d.js';
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        const {width, height} = canvas;
        ctx.translate(0.5 * width, 0.5 * height);
        ctx.scale(1, -1);
        /**
         * 边数 edges
         * 起点 x, y
         * 一条边的长度 step
         * */
        function regularShape(edges = 3, x, y, step) {
            const ret = [];
            const delta = Math.PI * (1 - (edges - 2) / edges);
            let p = new Vector2D(x, y);
            const dir = new Vector2D(step, 0);
            ret.push(p);
            for(let i = 0; i < edges; i++) {
                p = p.copy().add(dir.rotate(delta));
                ret.push(p);
            }
            return ret;
        }
        function draw(points, strokeStyle = 'salmon', fillStyle = null) {
            ctx.strokeStyle = strokeStyle;
            ctx.beginPath();
            ctx.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                ctx.lineTo(...points[i]);
            }
            ctx.closePath();
            if(fillStyle) {
                ctx.fillStyle = fillStyle;
                ctx.fill();
            }
            ctx.stroke();
        }
        draw(regularShape(3, 128, 128, 100));  // 绘制三角形
        draw(regularShape(6, -64, 128, 50));  // 绘制六边形
        draw(regularShape(11, -64, -64, 30));  // 绘制十一边形
        draw(regularShape(60, 128, -64, 6));  // 绘制六十边形
    </script>
</body>
</html>


5320d63fe5524a3b92a9e121c760b9d9.png



如何用参数方程描述曲线?


1. 画圆

圆可以用一组参数方程来定义。定义了一个圆心在(x0,y0),半径为 r 的圆。


73a48413e2c24cd29caadac8eaedc283.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>画圆</title>
    <style>
        canvas {
            border: 1px dashed salmon;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>
    <script>
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        const {width, height} = canvas;
        ctx.translate(0.5 * width, 0.5 * height);
        ctx.scale(1, -1);
        const TAU_SEGMENTS = 60;
        const TAU = Math.PI * 2;
        function arc(x0, y0, radius, startAng = 0, endAng = Math.PI * 2) {
            const ang = Math.min(TAU, endAng - startAng);
            const ret = ang === TAU ? [] : [[x0, y0]];
            const segments = Math.round(TAU_SEGMENTS * ang / TAU);
            for(let i = 0; i <= segments; i++) {
                const x = x0 + radius * Math.cos(startAng + ang * i / segments);
                const y = y0 + radius * Math.sin(startAng + ang * i / segments);
                ret.push([x, y]);
            }
            return ret;
        }
        function draw(points, strokeStyle = 'salmon', fillStyle = null) {
            ctx.strokeStyle = strokeStyle;
            ctx.beginPath();
            ctx.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                ctx.lineTo(...points[i]);
            }
            ctx.closePath();
            if(fillStyle) {
                ctx.fillStyle = fillStyle;
                ctx.fill();
            }
            ctx.stroke();
        }
        draw(arc(0, 0, 100));
    </script>
</body>
</html>


ad385adb71ac47cca8558c796eb84dfd.png



2. 画圆锥曲线

椭圆

a、b 分别是椭圆的长轴和短轴,当 a = b = r 时,这个方程是就圆的方程式。

f831b50cbac84ac08f7e77d7e2d6d37b.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>椭圆</title>
    <style>
        canvas {
            border: 1px dashed salmon;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>
    <script>
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        const {width, height} = canvas;
        ctx.translate(0.5 * width, 0.5 * height);
        ctx.scale(1, -1);
        const TAU_SEGMENTS = 60;
        const TAU = Math.PI * 2;
        function ellipse(x0, y0, radiusX, radiusY, startAng = 0, endAng = Math.PI * 2) {
            const ang = Math.min(TAU, endAng - startAng);
            const ret = ang === TAU ? [] : [[x0, y0]];
            const segments = Math.round(TAU_SEGMENTS * ang / TAU);
            for(let i = 0; i <= segments; i++) {
                const x = x0 + radiusX * Math.cos(startAng + ang * i / segments);
                const y = y0 + radiusY * Math.sin(startAng + ang * i / segments);
                ret.push([x, y]);
            }
            return ret;
        }
        function draw(points, strokeStyle = 'salmon', fillStyle = null) {
            ctx.strokeStyle = strokeStyle;
            ctx.beginPath();
            ctx.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                ctx.lineTo(...points[i]);
            }
            ctx.closePath();
            if(fillStyle) {
                ctx.fillStyle = fillStyle;
                ctx.fill();
            }
            ctx.stroke();
        }
        draw(ellipse(0, 0, 100, 50));
    </script>
</body>
</html>


14a479cc17924e4fadc2158b1d861175.png

抛物线

抛物线的参数方程。其中 p 是常数,为焦点到准线的距离。

e335b8f6ef03486e95466b1b8affea87.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>抛物线</title>
    <style>
        canvas {
            border: 1px dashed salmon;
        }
    </style>
</head>
<body>
    <canvas width="512" height="512"></canvas>
    <script>
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        const {width, height} = canvas;
        ctx.translate(0.5 * width, 0.5 * height);
        ctx.scale(1, -1);
        const LINE_SEGMENTS = 60;
        function parabola(x0, y0, p, min, max) {
            const ret = [];
            for(let i = 0; i <= LINE_SEGMENTS; i++) {
                const s = i / 60;
                const t = min * (1 - s) + max * s;
                const x = x0 + 2 * p * t ** 2;
                const y = y0 + 2 * p * t;
                ret.push([x, y]);
            }
            return ret;
        }
        function draw(points, strokeStyle = 'salmon', fillStyle = null) {
            ctx.strokeStyle = strokeStyle;
            ctx.beginPath();
            ctx.moveTo(...points[0]);
            for(let i = 1; i < points.length; i++) {
                ctx.lineTo(...points[i]);
            }
            ctx.closePath();
            if(fillStyle) {
                ctx.fillStyle = fillStyle;
                ctx.fill();
            }
            ctx.stroke();
        }
        draw(parabola(0, 0, 5.5, -10, 10));
    </script>
</body>
</html>

87fa50b8e3594673ad48e7417ce4e7ca.png


3. 画其他常见曲线

在 lib 下面新建一个 parametric.js 文件,封装一个更简单的 JavaScript 参数方程绘图模块。

// 根据点来绘制图形
function draw(
    points,
    context,
    { strokeStyle = "salmon", fillStyle = null, close = false } = {}
) {
    context.strokeStyle = strokeStyle;
    context.beginPath();
    context.moveTo(...points[0]);
    for (let i = 1; i < points.length; i++) {
        context.lineTo(...points[i]);
    }
    if (close) context.closePath();
    if (fillStyle) {
        context.fillStyle = fillStyle;
        context.fill();
    }
    context.stroke();
}
// 导出高阶函数绘图模块
export function parametric(xFunc, yFunc, zFunc) {
    /**
     * start、end 表示参数方程中关键参数范围的参数
     * seg 表示采样点个数的参数,当 seg 默认 100 时,就表示在 start、end 范围内采样 101(seg+1)个点
     * ...args 后续其他参数是作为常数传给参数方程的数据。
     * */ 
    return function (start, end, seg = 100, ...args) {
        const points = [];
        for (let i = 0; i <= seg; i++) {
            const p = i / seg;
            const t = start * (1 - p) + end * p;
            const x = xFunc(t, ...args); // 计算参数方程组的x
            const y = yFunc(t, ...args); // 计算参数方程组的y
            if (zFunc) {
                points.push(zFunc(x, y));
            } else {
                points.push([x, y]);
            }
        }
        return {
            draw: draw.bind(null, points),
            points, // 生成的顶点数据
        };
    };
}

下面使用上面封装的实现一下抛物线,阿基米德螺旋线,星形线。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>画其他常见曲线</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script type="module">
            import { parametric } from "./common/lib/parametric.js";
            const canvas = document.querySelector("canvas");
            const ctx = canvas.getContext("2d");
            const { width, height } = canvas;
            const w = 0.5 * width,
                h = 0.5 * height;
            ctx.translate(w, h);
            ctx.scale(1, -1);
            // 绘制坐标轴
            function drawAxis() {
                ctx.save();
                ctx.strokeStyle = "#ccc";
                ctx.beginPath();
                ctx.moveTo(-w, 0);
                ctx.lineTo(w, 0);
                ctx.stroke();
                ctx.beginPath();
                ctx.moveTo(0, -h);
                ctx.lineTo(0, h);
                ctx.stroke();
                ctx.restore();
            }
            drawAxis();
            // 绘制抛物线
            const para = parametric(
                (t) => 25 * t,
                (t) => 25 * t ** 2
            );
            para(-5.5, 5.5).draw(ctx);
            // 绘制阿基米德螺旋线
            const helical = parametric(
                (t, l) => l * t * Math.cos(t),
                (t, l) => l * t * Math.sin(t)
            );
            helical(0, 50, 500, 5).draw(ctx, { strokeStyle: "MediumPurple" });
            // 绘制星形线
            const star = parametric(
                (t, l) => l * Math.cos(t) ** 3,
                (t, l) => l * Math.sin(t) ** 3
            );
            star(0, Math.PI * 2, 50, 150).draw(ctx, { strokeStyle: "Orange" });
        </script>
    </body>
</html>

871e6bdd798e4c7c96e4b86f80b17078.png


4. 画贝塞尔曲线

贝塞尔曲线是一种使用数学方法描述的曲线,被广泛用于计算机图形学和动画中。在矢量图中,贝塞尔曲线用于定义可无限放大的光滑曲线。可以用来构建 Catmull–Rom 曲线。


贝塞尔曲线又分为二阶贝塞尔曲线(Quadratic Bezier Curve)和三阶贝塞尔曲线(Qubic Bezier Curve)。

二阶贝塞尔曲线

二阶贝塞尔曲线由三个点确定,P0是起点,P1是控制点,P2是终点


453e38ee4b0b42589810df534f52597a.png


二阶贝塞尔曲线的原理


9fd9bb39b1c60b095531ac2e2ceef31b.gif

绘制30条从圆心出发,旋转不同角度的二阶贝塞尔曲线

 
         

效果如下

0c28f500e3664893ac5aa75e6043f0f9.png


三阶贝塞尔曲线

三阶贝塞尔曲线的参数方程为:


7adeba4be41547329ee1a8dd84d8e70d.png


三阶贝塞尔曲线的原理示意图:

bd62f1dfea6688f9a9e679bb3529230c.gif

 
         

9a8a734794934d78b0ff7c6dc50c1910.png


目录
相关文章
|
7月前
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享(上)
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享
|
7月前
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享(下)
【视频】什么是非线性模型与R语言多项式回归、局部平滑样条、 广义相加GAM分析工资数据|数据分享
|
7月前
|
数据可视化
R语言广义相加(加性)模型(GAMs)与光滑函数可视化
R语言广义相加(加性)模型(GAMs)与光滑函数可视化
|
7月前
R语言蒙特卡洛计算和快速傅立叶变换计算矩生成函数
R语言蒙特卡洛计算和快速傅立叶变换计算矩生成函数
|
人工智能 开发者
回归方程求解小例子 | 学习笔记
快速学习回归方程求解小例子
回归方程求解小例子 | 学习笔记
|
机器学习/深度学习
【机器学习知识点】2. 输入一个多项式,返回该多项式的一阶导数多项式
【机器学习知识点】2. 输入一个多项式,返回该多项式的一阶导数多项式
|
机器学习/深度学习
【机器学习中的矩阵求导】(二)矩阵向量求导(定义法)
假设:x xx表示标量;X XX表示m×n维的矩阵;求导的因变量用y yy表示标量;Y YY表示p × q p×qp×q维矩阵
142 0
【机器学习中的矩阵求导】(二)矩阵向量求导(定义法)
|
机器学习/深度学习
【组合数学】组合数学简介 ( 组合思想 3 : 上下界逼近 | 上下界逼近示例 Remsey 数 )
【组合数学】组合数学简介 ( 组合思想 3 : 上下界逼近 | 上下界逼近示例 Remsey 数 )
255 0
【组合数学】组合数学简介 ( 组合思想 3 : 上下界逼近 | 上下界逼近示例 Remsey 数 )