前言
三角学在动画技术中被广泛运用,使用三角学编程时,你几乎不需要和数学打交道,只需关注于可视化的图形以及它们之间的关系。大部分情况下,你只需要处理与位置、距离以及角相关的变量,而不太会跟实际的数字打交道,其重点在于掌握各种边角关系。
一、角度与弧度
角的度量单位分别有角度和弧度两大系统。相信你对角度是比较熟悉的,但是在衡量角的大小时,计算机更倾向于使用弧度的概念。所以在使用三角学知识进行编程开发之前,你需要了解弧度。
一弧度约等于 57.2958°。当你在 canvas 元素上旋转一幅图像时,会用到 context.rotate(angle) 方法,其中参数 angle 的大小就需要用弧度表示。
JavaScript与大多数的开发语言相似,都使用弧度作为角的度量单位。你可能又会同时用到角度与弧度,因此你需要知道如何在两者间进行转换。
角度与弧度的转换公式:
弧度:radians = degrees * Math.PI / 180;
角度:degrees = radians * 180 / Math.PI;
二、canvas 坐标系
最常见的二维坐标系以 x 轴作为水平坐标,y 轴作为垂直坐标。
通常情况下(0,0)坐标会显示在空间的正中心,随后 x 轴的坐标值向右以正数形式不断增大,向左以负数形式不断变小,而 y 轴的坐标值向上以正数形式不断增大,向下以负数形式不断变小。如下图所示:
而 canvas 元素却是基于视频画面的坐标系,其中(0,0)处在空间的左上角,x 轴的坐标值从左往右不断增大,而 y 轴的坐标系的变化却与标准坐标系相反,向下以正数形式不断增大,向上以负数形式不断变小。如下图所示:
此外,在大多数系统中,角度的测量是以逆时针为正值,0° 表示一条线沿着 x 轴正方形延伸。如下图所示:
canvas 元素在这方面又显示了它的与众不同,在 canvas 元素中,顺时针的角度才是正值,逆时针的角度反而成了负值。如下图所示:
三、反正切
反正切是正切的逆运算,输入角的对边与邻边的比率,通过计算反正切可以得到角的弧度。JavaScript 提供了两个函数用于计算反正切。
第一个函数是 Math.atan(ratio),它接受角的对边长度除以邻边长度所得的小数作为参数。但这里会存在一个情况,如下图所示:
当我们用 Math.tan 函数来计算 A、B、C、D 四个角的正切值时,如下:
-
tan(A):-1 / 2 = -0.5;
-
tan(B):1 / 2 = 0.5;
-
tan(C):1 / (-2) = -0.5;
-
tan(D):(-1) / (-2) = 0.5;
然后把所得正切值 0.5,作为参数输入到 Math.atan(0.5),再将所得弧度转化为角度,就会得到一个接近 26.57° 的值。此时,你并无法分辨这个角度是指角 B 还是角 D,因为这两个角所对应的正切值都是0.5。
这时候,我们就需要用到 JavaScript 中的另一个反正切函数:Math.atan2(y, x)。该函数接受两个参数:对边的长度 y 与邻边的长度 x。
当我们把角 B 的对边 1 和邻边 2 和角 D 的对边 -1 和邻边 -2 作为参数输入到此反正切函数中,并把所得的弧度值转换成角度。
let angle = Math.atan2(1, 2) * 180 / Math.PI;
console.log("角B的大小为:" + angle);
angle = Math.atan2(-1, -2) * 180 / Math.PI;
console.log("角D的大小为:" + angle);
得到如下结果:
即如下图所示:
尽管从三角形 D 的底边算起,它的内角 D 确实是 26.57°,但是在 canvas 中,角是从 x 正轴开始以逆时针方向计算的,得到的角度 -153.43° 才是我们在 canvas 元素的视角中所关注的角 D 的角度。
因此,当你在 canvas 中进行动画开发时,涉及到角度的计算,都可以使用 Math.atan2 函数来处理,就不必担心所得的值不是你需要的那个角度的值。实际上你可能专门使用 Math.atan2 而很少会用到 Math.atan 来求角的角度。
四、三角函数在动画开发中的应用
1、旋转
绘制一个物体,并让它随着鼠标旋转,使其总能指向鼠标。
首先我们先绘制一个箭头 arrow,将其放置在 canvas 元素的正中央并使其指向正向的 x 轴方向,此时是它旋转 0° 时的表象。
然后我们通过计算得出鼠标的坐标值,再与箭头的坐标值进行相减得到两个坐标的差值,就可以计算三角形两边的长度 dy 和 dx。此时,只须通过 Math.atan2(dy, dx) 方法算出角度的大小,并将其赋值给箭头对象的 rotation 属性。如下图:
dx = mouse.x - arrow.x;
dy = mouse.y - arrow.y;
arrow.rotation = Math.atan2(dy, dx);
实现代码:下载查看文件夹 2_1
2、波
a、利用正弦函数绘制正弦波。
正弦函数 y = sin(x) 对应的图像如下:
我们可以通过不断增加角的度数,模拟实现从 0 到 1 再到 -1 最后回到 0 的效果。通过不断增加角的度数就可以不断得到上下波动的波形图如下所示:
此时如果将正弦值乘以更大的数字,比如 100,就可以得到一系列从 -100 到 100 不断变化的数值。
首先我们先绘制一个球形 ball,将其放置在 canvas 元素的正中央。然后定义一个 angle 变量并将其初始化为 0。
在 drawFrame 函数中,将 angle 的正弦值 Math.sin(angle) 乘以 range=50,这样这些乘积处在 -50 到 50 的范围内,以便于我们更明显的查看运动的效果。如果再将该值加上 canvas 高度的一半,就会得到介于 150 ~ 250 的数值(以一个高度为 400 像素的 canvas 元素作为参考标准)。以此作为球形的 y 坐标,并在循环中以 speedy=0.1 弧度的步长不断增加 angle 的大小。这样就可以获得一个基于 y 轴上不断波形移动的球形。
x 轴方向上的处理则简单些,只须定义 x 方向上的速度 speedx,并初始化为 2,然后球形的 x 坐标不断加上 speedx,即可实现水平方向上的运动。
ball.x += speedx;
ball.y = canvasH / 2 + Math.sin(angle) * range;
angle += speedy;
实现代码:下载查看文件夹 2_2
b、使用绘图API绘制波
接下来我们直接使用 canvas 的绘图 API 绘制正弦波而不再通过 ball 对象。需要注意的是,在 drawFrame 函数中取消了对 context.clearRect 的调用,这样 canvas 元素就不会在每一帧开始的时候擦除之前绘制的图像,使得每一帧绘制的图像都保留在 canvas 上。
context.beginPath();
context.moveTo(xpos, ypos);
xpos += xspeed;
ypos = canvasH / 2 + Math.sin(angle) * range;
angle += yspeed
context.lineTo(xpos, ypos);
context.stroke();
效果如下:
实现代码:下载查看文件夹 2_3
3、圆与椭圆
a、圆周运动
当余弦配合正弦使用时,会获得一个更常用并更有价值的功能:让物体做圆周运动。我们先来看看物体做圆周运动时所处的多个位置的情况:
如果你从上图圆形的正右方观察时,会发现物体在做圆周运动时,实际是在不断的上下垂直运动,如下图所示:
上下运动的中心点是圆心所在的位置,而上下运动的范围恰好是圆的半径 radius。物体在运动中所处的位置即 y 坐标,也就是所处三角形的对边,因此该对边的长度 y 为物体所处角度 angle 的正弦值与圆的半径的乘积,即
y = radius * Math.sin(angle);
现在,再想象你从圆的正下方进行观察,则会发现物体在做圆周运动时,实际是在不断的左右水平运动,如下图所示:
此时,在计算物体在运动中所处的位置即 x 坐标,也就是所处三角形的邻边时,就使用余弦函数。
x = radius * Math.cos(angle);
因此,实现圆周运动,只需让物体 x 坐标做余弦运动,y 坐标做正弦运动:
ball.x = centerX + radius * Math.cos(angle);
ball.y = centerY + radius * Math.sin(angle);
angle += speed;
实现代码:下载查看文件夹 2_4
b、椭圆运动
当物体进行圆周运动时,上下沿 y 轴运动和前后沿 x 轴运动的范围是一致的,都是 radius,这种情况下我们得到了一个完美的圆形。
为了获得椭圆,可以使用不同的半径计算 x 与 y 的坐标位置,即上下沿 y 轴运动的范围 radiusY 和前后沿 x 轴运动的范围 radiusX 是不相等的。如此我们即可得到了一个椭圆。
ball.x = centerX + radiusX * Math.cos(angle);
ball.y = centerY + radiusY * Math.sin(angle);
angle += speed;
代码:下载查看文件夹 2_5
4、勾股定理
勾股定理:直接三角形的两条直角边的平方和等于斜边的平方。
在动画中,我们可以利用勾股定理来求两点间的距离。假设有两个问题,它们的坐标分别是(x1, y1)、(x2, y2),如下图:
现在我们需要求出两物体间的距离 dist 是多少。我们把两个物体间的连线作为三角形的斜边,转化为一个直角三角形,如下图:
上图的 dx 表示两个物体在 x 轴上的距离,dy 为它们在 y 轴上的距离。由图可知
dx = x2 - x1 = 58 - 50 = 8;
dy = y2 - y1 = 50 - 56 = -6;
根据勾股定理,将两个值取平方后相加,得到两物体间的距离 dist 的平方,即 (-6)² + 8² = dist²,最后取平方根,即求得了 dist 的值:
dx = x2 - x2;
dy = y2 - y1;
dist = Math.sqrt(dx * dx, dy * dy);
实现效果如下:
实现代码:下载查看文件夹 2_6
源码下载地址:
https://gitee.com/dplate/trigonometry_in_animation.git