【数学篇】05 # 如何用向量和坐标系描述点和线段?

简介: 【数学篇】05 # 如何用向量和坐标系描述点和线段?

说明

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



坐标系与坐标映射


   HTML:采用的是窗口坐标系,以参考对象(参考对象通常是最接近图形元素的 position 非 static 的元素)的元素盒子左上角为坐标原点,x 轴向右,y 轴向下,坐标值对应像素值。


   SVG:采用的是视区盒子(viewBox)坐标系。这个坐标系在默认情况下,是以 svg 根元素左上角为坐标原点,x 轴向右,y 轴向下,svg 根元素右下角坐标为它的像素宽高值。如果我们设置了 viewBox 属性,那么 svg 根元素左上角为 viewBox 的前两个值,右下角为 viewBox 的后两个值。


   Canvas:采用的坐标系默认以画布左上角为坐标原点,右下角坐标值为 Canvas 的画布宽高值。


   WebGL:是一个三维坐标系。它默认以画布正中间为坐标原点,x 轴朝右,y 轴朝上,z 轴朝外,x 轴、y 轴在画布中范围是 -1 到 1。


上面4个都属于直角坐标系。


   直角坐标系特性:不管原点和轴的方向怎么变,用同样的方法绘制几何图形,它们的形状和相对位置都不变。


转换坐标系:


   HTML、SVG 和 Canvas 都提供了 transform 的 API 转换坐标系。

   WebGL 本身不提供 tranform 的 API,但可以在 shader 里做矩阵运算来实现坐标转换。



如何用 Canvas 实现坐标系转换?


以一个例子为例:在宽 512 * 高 256 的一个 Canvas 画布上实现如下的视觉效果。其中,山的高度是 100,底边 200,山是等腰三角形,两座山的中心位置到中线的距离都是 80,太阳的圆心高度是 150。可以使用一个 Rough.js:https://github.com/rough-stuff/rough的库,绘制一个手绘风格的图像。

fdaaab7b2d8d42909ca3d43485bcb321.png



方法一:不转换坐标系

首先我们需要计算出来三角形各个顶点的坐标

e66732c6ddfb444dabec4a5cf053301e.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="256"></canvas>
    <script src="https://lib.baomitu.com/rough.js/3.1.0/rough.umd.js"></script>
    <script>
        const rc = rough.canvas(document.querySelector('canvas'));
        const hillOpts = {
            roughness: 2.8,
            strokeWidth: 2,
            fill: 'cyan'
        };
        rc.path('M76 256 L176 156 L276 256', hillOpts);
        rc.path('M236 256 L336 156 L436 256', hillOpts);
        rc.circle(256, 106, 105, {
            stroke: 'red',
            strokeWidth: 4,
            fill: 'salmon',
            fillStyle: 'solid',
        });
    </script>
</body>
</html>


效果如下:

9142a3c552784e6c8d31d41f01f6a1dc.png



方法二:转换坐标系

以画布底边中点为原点,x 轴向右,y 轴向上的坐标系,相对来说转换之后的坐标系计算的坐标点简单清晰一些:

// 以画布底边中点为原点
ctx.translate(256, 256);
// x 轴向右,y 轴向上的坐标系
ctx.scale(1, -1);


330ad974692245ab9437194defb9b3f4.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 src="https://lib.baomitu.com/rough.js/3.1.0/rough.umd.js"></script>
    <script>
        const rc = rough.canvas(document.querySelector('canvas'));
        console.log(rc)
        const ctx = rc.ctx;
        // 以画布底边中点为原点
        ctx.translate(256, 256);
        // x 轴向右,y 轴向上的坐标系
        ctx.scale(1, -1);
        const hillOpts = {
            roughness: 2.8,
            strokeWidth: 2,
            fill: 'gray'
        };
        // //线条宽度
        // ctx.lineWidth = 2;
        // ctx.fillStyle = 'orange';
        // //线条颜色填充
        // ctx.strokeStyle = 'black';
        // //开启绘画路径
        // ctx.beginPath();
        // //画笔初始化点
        // ctx.moveTo(-180, 0);
        // // 画笔目标位置
        // ctx.lineTo(-80, 100);
        // // 连接路径
        // ctx.stroke();
        // ctx.lineTo(20, 0);
        // ctx.stroke();
        // ctx.closePath(); //闭合线路(首尾坐标)
        // ctx.stroke(); //连接首尾
        // ctx.fill();
        rc.path('M-180 0 L-80 100 L20 0', hillOpts);
        rc.path('M-20 0 L80 100 L180 0', hillOpts);
        rc.circle(0, 150, 105, {
            stroke: 'salmon',
            strokeWidth: 4,
            fill: 'gold',
            fillStyle: 'solid',
        });
    </script>
</body>
</html>


实现的效果如下,这里我有个疑问就是为什么y轴左边的这个三角形没有填充到颜色,我试了一下canvas原生的代码是可以填充的,有点搞不懂,知道的大佬还请指导一下,在此先感谢。

31c50f7d66454eff88e4178128e31716.png


如何用向量来描述点和线段?

可以用二维向量来表示这个平面上的点和线段。二维向量其实就是一个包含了两个数值的数组,一个是 x 坐标值,一个是 y 坐标值。


baee467accce4474a78b1e6aac14d996.png


  • 向量和标量一样可以进行数学运算。
  • 一个向量包含有长度和方向信息。



向量运算的意义

向量运算的意义并不仅仅只是用来算点的位置和构造线段,可视化呈现依赖于计算机图形学,而向量运算是整个计算机图形学的数学基础。



实战演练:用向量绘制一棵树

需要实现的效果如下:


6ccaac7389d94e4384b786c8c3bb59dc.png



二维旋转矩阵与向量旋转基本思想:处于某二维空间中的任意向量,可以通过标准正交基来表示。通俗来讲,就是用坐标系来表示。不过表示这个向量的不是x轴和y轴坐标,而是二维的基向量。我们可以联想一下物理中的静止参考系和动参考系。动静参考系在这里对应于动静坐标系。向量旋转的同时,动坐标系是相对于这个向量不动的,相对于静止坐标系则旋转同样的角度。只要知道旋转后动坐标系中的标准正交基在静止坐标系中的表达,就能知道旋转后的向量在静止坐标系中的表达。


新建文件 vector2d.js 实现 Vector2D

export class Vector2D extends Array {
    constructor(x = 1, y = 0) {
        super(x, y);
    }
    set x(v) {
        this[0] = v;
    }
    set y(v) {
        this[1] = v;
    }
    get x() {
        return this[0];
    }
    get y() {
        return this[1];
    }
    get length() {
        return Math.hypot(this.x, this.y);
    }
    get dir() {
        return Math.atan2(this.y, this.x);
    }
    copy() {
        return new Vector2D(this.x, this.y);
    }
    add(v) {
        this.x += v.x;
        this.y += v.y;
        return this;
    }
    sub(v) {
        this.x -= v.x;
        this.y -= v.y;
        return this;
    }
    scale(a) {
        this.x *= a;
        this.y *= a;
        return this;
    }
    cross(v) {
        return this.x * v.y - v.x * this.y;
    }
    dot(v) {
        return this.x * v.x + v.y * this.y;
    }
    normalize() {
        return this.scale(1 / this.length);
    }
    rotate(rad) {
        const c = Math.cos(rad),
            s = Math.sin(rad);
        const [x, y] = this;
        this.x = x * c + y * -s;
        this.y = x * s + y * c;
        return this;
    }
}


代码实现如下:

<!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="640" height="512"></canvas>
    <script type="module">
        import {Vector2D} from './common/lib/vector2d.js';
        const canvas = document.querySelector('canvas');
        const ctx = canvas.getContext('2d');
        // 以画布左下角为原点
        ctx.translate(0, canvas.height);
        // x 轴向右,y 轴向上的坐标系
        ctx.scale(1, -1);
        ctx.lineCap = 'round';
        /**
         * 画树枝的函数
         * context 是 Canvas2D 上下文
         *   v0 是起始向量
         *   length 是当前树枝的长度
         *   thickness 是当前树枝的粗细
         *   dir 是当前树枝的方向,用与 x 轴的夹角表示,单位是弧度。
         *   bias 是一个随机偏向因子,用来让树枝的朝向有一定的随机性
         * */ 
        function drawBranch(context, v0, length, thickness, dir, bias) {
            // 计算出树枝的终点坐标;创建一个单位向量 (1, 0),它是一个朝向 x 轴,长度为 1 的向量。然后旋转 dir 弧度,再乘以树枝长度 length。
            const v = new Vector2D().rotate(dir).scale(length);
            const v1 = v0.copy().add(v);
            // 绘制一个固定方向的树枝为根部
            context.lineWidth = thickness;
            context.beginPath();
            context.moveTo(...v0);
            context.lineTo(...v1);
            context.stroke();
            // 从一个起始角度开始递归地旋转树枝,每次将树枝分叉成左右两个分枝        
            if(thickness > 2) {
                const left = Math.PI / 4 + 0.5 * (dir + 0.2) + bias * (Math.random() - 0.5);
                drawBranch(context, v1, length * 0.9, thickness * 0.8, left, bias * 0.9);
                const right = Math.PI / 4 + 0.5 * (dir - 0.2) + bias * (Math.random() - 0.5);
                drawBranch(context, v1, length * 0.9, thickness * 0.8, right, bias * 0.9);
            }
            // 随机绘制花瓣
            if(thickness < 5 && Math.random() < 0.3) {
                context.save();
                context.strokeStyle = '#c72c35';
                const th = Math.random() * 6 + 3;
                context.lineWidth = th;
                context.beginPath();
                context.moveTo(...v1);
                context.lineTo(v1.x, v1.y - 2);
                context.stroke();
                context.restore();
            }
        }
        // 在(256, 0)位置绘制
        const v0 = new Vector2D(256, 0);
        drawBranch(ctx, v0, 50, 10, 1, 3);
    </script>
</body>
</html>


024e2440ce52404d875331dddf41e49f.png

目录
相关文章
|
7月前
|
存储 人工智能 监控
Mahilo:多智能体实时协作框架开源!人类与AI无缝交互,复杂任务一键协同
Mahilo 是一个灵活的多智能体框架,支持创建与人类互动的多智能体系统,适用于从客户服务到紧急响应等多种场景。
418 2
Mahilo:多智能体实时协作框架开源!人类与AI无缝交互,复杂任务一键协同
|
12月前
|
并行计算 数据可视化
ECCV 2024:JHU上交等提出首个可渲染X光3DGS!推理速度73倍NeRF,性能提升6.5dB
【10月更文挑战第8天】近日,约翰斯•霍普金斯大学和上海交通大学等机构的研究人员提出了一种名为X-Gaussian的新型3D Gaussian Splatting框架,用于X光新视角合成。该框架通过优化辐射性Gaussian点云模型和可微分辐射光栅化技术,显著提升了X光成像的渲染质量,同时大幅减少了训练时间和推理时间。实验结果显示,X-Gaussian在性能上比现有方法提升了6.5dB,训练时间减少了85%,推理速度提高了73倍。此外,该框架在稀疏视角CT重建中也展现出巨大潜力。
246 4
|
Serverless Python
Python中绘制移动平均线(MA)
要在Python中绘制移动平均线(MA),可以使用matplotlib和pandas库。pandas库提供了方便的函数来计算移动平均线,matplotlib库则用于绘制图表。
496 2
|
Serverless Python
使用Python的pandas和matplotlib库绘制移动平均线(MA)示例
使用Python的pandas和matplotlib库绘制移动平均线(MA)示例:加载CSV数据,计算5日、10日和20日MA,然后在K线图上绘制。通过`rolling()`计算平均值,`plot()`函数展示图表,`legend()`添加图例。可利用matplotlib参数自定义样式。查阅matplotlib文档以获取更多定制选项。
493 1
|
传感器 算法 机器人
基于 IMU 的位姿解算
解算 IMU 采样数据的过程与惯导解算技术原理有关,而提高定位精度的方法主要依赖于IMU自身精度的提高和算法改进。
1630 0
|
缓存 网络协议 Shell
ADB各种操作指令详解大汇总
这篇文章提供了ADB(Android Debug Bridge)的详细操作指令汇总,包括设备管理、应用操作、日志查看、文件操作、屏幕截取与录制、Shell命令使用等。
1910 2
|
机器人
ROS2教程 04 话题Topic
本文是关于ROS2(机器人操作系统2)中话题(Topic)机制的教程,详细介绍了ROS2中话题的命令使用,包括列出、回显、发布、信息查询、类型查询等功能,并通过示例代码展示了如何创建发布者(Publisher)和订阅者(Subscriber)节点,以及如何测试发布-话题-订阅通信。
1530 1
ROS2教程 04 话题Topic
|
存储 前端开发 安全
快速了解std::promise的工作原理和使用
快速了解std::promise的工作原理和使用
333 3
|
Java
JAVA 端口被占用 报错解决方案:java.net.BindException: Address already in use: bind
JAVA 端口被占用 报错解决方案:java.net.BindException: Address already in use: bind
719 0
|
人工智能 数据可视化 测试技术
Meta AI开源CLIP-DINOiser | 如何将自监督DINO的Trick教给CLIP?这里就是答案!
Meta AI开源CLIP-DINOiser | 如何将自监督DINO的Trick教给CLIP?这里就是答案!
329 0