• 二维空间下的向量旋转


    9c968abbb9273445860174991c340275.gif

    向量运算是计算机图形学的数学基础,而向量的旋转是向量的一种常见操作,本文将详细讲解向量在二维空间下的旋转原理。

    在前端项目中,旋转一个元素我们会使用CSS的 rotate 函数,本文会让你对 rotate 有一个全新的认识。

    向量

    二维中间中的向量其实就是一个包含了两个数值的数组,一个是 x 坐标值,一个是 y 坐标值。

    2f48c624dca7085bf994601dd94b1b08.png

    向量既可以表示一个 “点”(x, y),也可以表示一个从原点(0,0)到坐标点(x, y)的 “线” 称为 “向量”。

    向量的旋转

    向量的旋转指的是将一个已知向量旋转给定的弧度后得到一个旋转后的新向量,也就是说我们使用 “向量” 和 “弧度” 求出旋转后的“新向量”。我们通过一段伪代码来描述:

    1. /**
    2. * v1: 原始向量
    3. * v2: 旋转后的新向量
    4. * rad: 旋转弧度
    5. */
    6. const v2 = v1.rotate(rad);

    若想实现一个 rotate 函数来旋转向量,“矩阵的线性变换” 可以帮助我们做到这一点。

      矩阵的线性变换

    当一个矩阵和一个向量做乘法时,将产生一个新的向量(可以理解为:向量和矩阵做乘法描述了一个运动),矩阵描述了一个二维空间如何变换(旋转、拉伸等),向量相当于一个入参。

    这与函数类似,当一个矩阵描述了二维空间逆时针旋转90°,那么任意一个向量与该矩阵做乘法时,都会得到一个逆时针旋转了90°的新向量。

    bb8053d2235cecd432fbe892d19801e5.png

    上面公式中,左侧的 2x2 的矩阵描述了二维空间如何变换,右侧的 (x, y) 是一个向量,整体计算过程给出了矩阵和向量通过乘法得到一个新的向量。

    • 基向量

    为什么 2x2 的矩阵可以描述二维空间如何变换?这个问题很关键,因此这里单独用一个小节展开介绍一下。

    空间中的任意线性变换都可以由“基向量”的变换来表示,而基向量的数量与维度保持一致,比如二维空间中,基向量就是两个被称为:i 帽和 j 帽的向量。这两个基向量都有各自的x坐标(1, 0)和y坐标(0, 1),因此加起来就是一个 2x2 的矩阵:

    86bcff7596124c0fb70da6b4c2462ed0.png

    左列表示 i 帽,右列表示 j 帽,上面的数字为 x 坐标,下面的数字为 y 坐标,空间中的表示如下图所示:

    ac3e8ec860858702ec6f1bcf6f2a836e.png

    如上图所示,向量 v 是由基向量 i 帽和 j 帽相加得到的,因此当 i 帽和 j 帽的坐标发生变化时,向量 v 就会发生变化,比如逆时针旋转90°,那么矩阵值为:

    此时 2x2 矩阵值为:

    6b91b13e6d43a41386aff63a4dea2848.png

    空间中的表示如下图所示:

    3e8d45c90562c204b0e92000c7e6e4f2.png

    由于整个空间是线性变化的,因此基向量的变化等同于该空间下任意向量、坐标、图形的变化。

    举个例子:空间中任意一个向量 v→(3, 2),与上面的矩阵相乘,将得到一个全新的逆时针旋转90°后的新向量:

    fab3f1745584f940530bda3561957c11.png

    空间中的表示如下图所示:

    8c40b5a06f5e0ac49cae9bb96609f638.png

    • 小结

    回到我们的主题,就是说,当我们拥有矩阵和向量时,我们就可以得到变换后的新向量。向量是我们已知的,矩阵我们暂时还没有,但我们有一个弧度值,所以我们要做一件事,就是将弧度转化为矩阵。然后再用这个矩阵和向量做乘法得出变换后的新向量。

      将弧度值转化为矩阵

    本节介绍一种通过弧度值来计算旋转后基向量 i 帽与 j 帽坐标的方法。

    • 基向量 i 帽旋转后的坐标

    旋转前,基向量 i 帽的坐标为:(1, 0),旋转后 i 帽的坐标是:(cosθ, sinθ)。

    5cd98a4f5e29046118f0898c1036b57d.png

    计算 i 帽旋转后的坐标,本质上是计算旋转后的 i 帽在 x 轴的位置以及在 y 轴的位置。

    旋转后的 i 帽在 x 轴的位置等同于旋转后的 i 帽在旋转前 i 帽上的投影,如下图所示:

    2ae04351e026e9928d74804dbd27af2a.png

    向量 a 在向量 b 上的投影等于两个向量夹角的余弦值乘以向量 a 的长度,反过来 b 在 a 上的投影就是余弦值乘以向量 b 的长度。

    在我们的场景里就是旋转后 i 帽和旋转前 i 帽夹角的余弦值乘以 i 帽的长度,而由于 i 帽的长度为 1,因此余弦值就是我们要的 i 帽旋转后在 x 轴的坐标。

    至此,我们得到了旋转后 i 帽的 x 坐标:cosθ

    旋转后的 i 帽在 y 轴的位置,我们可以用夹角的正弦值来计算。用夹角与边长来求出三角形的第三条边,而这第三条边的长度就是旋转后 i 帽在 y 轴的位置,如下图所示:

    5736c2cbfdb21225ae7c46f5a24f19a7.png

    如上图所示,边 d 等于旋转后的 i 帽在 y 轴的位置等于 sinθ,我们用一个简单的推导步骤来证明:

    1. sinθ = 对边 / 斜边
    2. 已知斜边边长为 1(i帽长度为1,旋转后长度不变)
    3. sinθ = d / 1
    4. d / 1 = sinθ
    5. d = sinθ * 1
    6. d = sinθ

    至此,我们得出旋转后 i 帽的 y 坐标:sinθ,同时我们也得到了旋转后 i 帽的完整坐标:

    ee4661d19c6d0f468ae16fbc6e9206a6.png

    • 基向量 j 帽旋转后的坐标

    获取 j 帽旋转后的坐标方法与获取 i 帽一样,仍然是通过夹角的正弦值与余弦值来获取坐标。旋转前,基向量 j 帽的坐标为:(0, 1),旋转后 j 帽的坐标是:(-sinθ, cosθ):

    cb00c0dd258a61a55722685a76041ffe.png

    由于旋转后的 j 帽在旋转前 j 帽上的投影是投在了 y 轴,因此 cosθ 得到的是旋转后 j 帽在 y 轴的位置。同理,sinθ 则是旋转后 j 帽在 x 轴的位置,由于在 x 轴小于0的位置,因此需要给位置(sinθ)加一个负号:-sinθ,如下图所示:

    e05d5f7804f0b03c67771b8845d038a2.png

    至此,我们得到了旋转后 j 帽的完整坐标:

    5e3c90a2a1683a0bccd207fd4dbc571a.png

    • 小结

    通过本小节的详细介绍,我们得知了一种通过弧度值来计算旋转矩阵(旋转后基向量 i 帽与 j 帽坐标)的方法,旋转矩阵如下:

    27cbc4a7edc15ba1b13b1af5a67dd2bf.png

      小结

    向量的旋转指的是将一个已知向量旋转给定的弧度或角度后得到一个旋转后的新向量。矩阵描述了一个二维空间如何变换,当一个矩阵和一个向量做乘法时,将产生一个新的向量。而这个描述了空间如何变换的矩阵可以通过弧度计算得出。

    至此,我们已经掌握了向量旋转的理论知识,一个完整的向量旋转矩阵计算如下所示:

    85446c9c79e5fd0fd45f6ea5b2fbeb99.png

    向量旋转的代码实现

    通过上一节详细的介绍,我们产出了一个公式:

    3b3698484c468bc48917f7de0cb34c21.png

    最左侧的 2x2 矩阵是使用弧度计算出来的,与右边的向量做乘法,得出最后的一个新的向量,代码中直接使用最后一步生产出来的那个向量即可,代码如下:

    1. /**
    2. * @param {[x, y]} v 原始向量
    3. * @param {number} rad 旋转弧度
    4. * @return {[x, y]} 旋转后的新向量
    5. */
    6. function rotate(v, rad) {
    7. const c = Math.cos(rad);
    8. const s = Math.sin(rad);
    9. const [x, y] = v;
    10. return [
    11. x * c + y * -s,
    12. x * s + y * c
    13. ];
    14. }

    Demo展示:基于向量旋转实现的一棵树


    5541e2eb38ef1d99ddf9b4d3a82cb5e5.png

    Demo designed by 月影

    团队介绍

    我们是大淘宝技术前端 - 活动与场制作团队,负责阿里最核心业务(电商)的最核心板块(淘宝营销活动);团队核心亮点:

    1. 负责并深度参与双十一、618、造物节等大促活动;

    2. 负责营销活动对应面向小二的营销页面搭建产品;服务淘系数千运营小二,所有营销会场页面从这里诞生;

    3. 负责营销活动对应面向商家的营销页面搭建产品;这是大淘宝前端内唯一一个纯nodejs开发的面向数十万商家提供服务的中后台应用,挑战基于nodejs下的超高稳定性、开放性、扩展性和性能要求

    4. 负责大淘宝营销活动页面的终端渲染方案:基于搭建场景下,业务隔离可扩展、极致性能体验的营销终端渲染架构

    5. 负责构建营销搭建产品的产研平台 :提供可扩展可编排营销页面制作能力的产研平台;在这里有要解决业务研发效率的面向中后台前端+后端的应用功能扩展方案,有面向数十万行代码的中大型应用下的技术架构设计:微服务化、DDD模式、faas化、nodejs热更新等等

    ✿  拓展阅读

    7b461debf0e398d043aa9706fcf4d9d8.jpeg

    ad2c464f943333ee517f2939126d860a.jpeg

    作者|刘博文(玖五)

    编辑|橙子君

    e47c217be68b4ec25eaf62163d05818e.png

  • 相关阅读:
    CSS通用样式3——表格
    SSL证书系列--Let’s Encrypt和TrustAsia哪个好?
    Ai语音机器人系统语音识别达到了什么水准
    大家要的Biotin-PEG3-Br/acid/NHS ester/alcohol/amine合集分享
    白鲸开源 X SelectDB 金融大数据联合解决方案公布!从源头解决大数据开发挑战
    《Vue3.x+TypeScript实践指南》已出版
    java - 包装类
    ROS多设备组网(WSL+miniPC+Nv Orin)
    如何在 JavaScript 中使用 Cache-Control 头来控制缓存?
    图解LeetCode——782. 变为棋盘(难度:困难)
  • 原文地址:https://blog.csdn.net/Taobaojishu/article/details/126239271