「动画中的数学与物理基础」点和直线

1,724 阅读11分钟

如果你想制作一款酷炫的动画效果或者做一款h5的小游戏,但又不知道如何入手?计算机动画怎么知道一个物体放到何处的?它又是怎么让物体移动的?等等类似的问题,解决这些问题,肯定少不了数学与物理基础知识的应用,从本系列文章起,笔者将介绍一些基础的数学与物理知识,希望对你有所帮助。

本篇文章先从最基础的点和直线开始介绍,主要涉及以下内容:

  • 坐标系和点
  • 直线及计算直线的斜率
  • 检测直线是否相交及计算交点
  • 在网页上绘制直线和箭头

坐标系和点

让我们先来思考一个问题,计算机是怎么将我们指定的物体放置到对应的位置?一般来说,我们开发人员是通过使用笛卡尔坐标系确定物体的具体位置,笛卡尔坐标系由一个水平轴x和一个垂直轴y组成,每个点都可以写成类似(x,y),其中x和y分别为该点在x轴和y轴上的坐标值。坐标系的原点(0,0)是量轴相交的地方。从原点出发,向右是x轴的正方向,向左是x轴的负方向;同样,y轴的正方向向上,y轴的负方向向下。

在二维坐标系里表示点

例1: 现在有个需求在屏幕上放置6个物体A-F,并在笛卡尔坐标系进行表示。

A(0,0)、B(1,2)、C(4,3)、D(-1,2),E(-2,-1)和 F(3,-2)。

「前端动画数学与物理基础」点和直线

在三维坐标系里表示点

所谓的三维坐标,就是在二维的基础上,添加第三个坐标轴——Z轴而已。z轴的具体方向在哪,目前还没有统一的标准。目前有两个标准:左手系统和右手系统。

伸出你的右手,弯曲你的无名指和小拇指,让大拇指指向右方(X轴的正方向),并且让食指指向上方(Y轴的正方向),那你会发现中指指向屏幕的外部(Z轴的正方向)。如果用左手做同样的事情,让左大拇指指向右方(X轴的正方向),食指向上(Y轴的正方向),那么你的中指指向屏幕的内部(左手系统中Z轴的正方向)。本系列文章会选择y轴向上的右手系统,原因有以下几点:

  • 它是传统数学中采用的坐标系
  • 它是大多数开发人员采用的坐标系
  • 它是OPENGL中采用的坐标系。

在这种坐标系中,我们可以用(x,y,z)来表示三维空间的任意一个位置。

例2: 如图所示,给出P点的坐标:

「前端动画数学与物理基础」点和直线

如图所示,我们做了黄色的辅助线,可以清楚看出p的坐标,其实p点沿着原点右移了2个单位,然后向上移了4个单位,再沿着z轴移动了5个单位,因此p点的坐标为(2,4,5)

屏幕中的坐标系

前面我们讲过,在笛卡尔坐标系中y轴正方向是向上的,然而显示器则是被设置成从上往下读,因此屏幕坐标系使用向下作为y轴的正方向。如下图示意:

「前端动画数学与物理基础」点和直线

直线及计算直线的斜率

直线的定义

我们都知道两点确定一条直线,在数学中我们一般用类似y=2x这样的函数方程表示直线,而方程的全解则是满足该方程的点。

如何根据一个函数方程画一条直线呢?

  • 首先对方程进行变换,使方程的一边只有y
  • 然后选择一个x值,并代入方程式计算出一个y值。(一般选择三个值)

例3: 画出方程3x-2y=8表示的直线。

1、首先变换方程将y移动到方程的一边。

y=(3/2)x-4

2、在画点时,使用整数坐标比较容易些,因此x取值0,2,4。将这3个值带入方程后,将会得到以下三个点:

(0,4)、(2,-1)、(4,2)。

3、在坐标系里画出这3个点,并用线将它们连接起来如图所示:

「前端动画数学与物理基础」点和直线

斜率

斜率是直线的一个重要属性,如图所示展示了一个斜面(直线),一个物体以速度50m/s沿垂直方向上升,以速度100m/s沿水平方向运动,该斜面的斜率是通过垂直上升的速度与水平运动的速度比率来确定的,在该图的比率就是50/100,或50%。如果用函数方程表示这条斜线:1/2x-y=c。

「前端动画数学与物理基础」点和直线

1、两点之间的斜率

接下来让我们来看坐标系中的P点(x1,y1)和Q点(x2,y2),用m来表示斜率,其对应的计算斜率公式如下:

「前端动画数学与物理基础」点和直线

斜率=m=△y/△x=(y2-y1)/(x2-x1)

例4: 计算点(1,5)和(-2,0)之间的斜率

斜率=m=△y/△x=(y2-y1)/(x2-x1)=(0-5)/(-2-1)=5/3

2、计算直线的斜率

对于标准的直线方程形如Ax+By=C这样的方程,其计算斜率的公式为m=-A/B。

例5: 计算直线2x+y=5的斜率。

如果y前没数字,这意味着B=1,如果没有y项,则B=0,带入斜率公式:

斜率=m=-A/B=-2/1=-2

除了Ax+By=C这种标准的直线函数方程,我们还会见到如下的表示形式:

斜截式:y=mx+b

点斜式:(y-y1)=m(x-x1),其中(x1,y1)是直线上任一点。

3、关于斜率应用的一些重要特征

  • 如果斜率为负值,那么直线就沿着左上——右下方向延伸
  • 如果斜率为正值,那么它就向左下——右上方向延伸
  • 如果斜率为0,该直线将会是一条水平的直线
  • 如果斜率公式中,分母为0(y不存在),则是一条垂直线。
  • 如果两条直线相互垂直,那么m1*m2=-1
  • 如果两条直线平行,那么他们的斜率是相等的。

4、有趣的练习

例6: 假如人物角色在游戏中的位置为(50,200),当玩家在点(150,400)点击了鼠标,这说明它想要到此位置,那么就需要找到一条到达目的直线的路径,请计算出该直线方程。

解答思路:

1、首先我们需要通过两点之间的斜率公式,计算出直线斜率:

斜率=m=(400-200)/(150-50)=200/100=2

2、然后将其中一点和斜率m带入点斜式方程:

(y-y1)=m(x-x1)

(y-200)=2(x-50)

如果你不习惯点斜式的表述方式,你可以改成斜截式,只需要多几部运算而已:

y=2x+100

有了直线方程,现在我们可以让人物角色按照直线路径动起来了。

例7: 在你的游戏中角色正沿着直线y=(2/3)x+20移动,当它到达位置(30,40)时玩家按了下方向按钮,命令它向左转90。然后继续沿着直线前进,请计算出新的路径直线方程。

解答思路:

1、根据斜率的属性我们得知,两条直线垂直,其斜率相乘等于-1,由此我们可以得出另一条直线方程的斜率为-3/2。

2、然后我们把斜率和点带入点斜式方程中:

(y-40)=(-2/3)(x-30)

如果你不习惯点斜式的表述方式,你可以改成斜截式,只需要多几步运算而已:

y=(-3/2)x+85

检测直线是否相交及计算交点

在游戏和动画编程中,我们经常要判断两条直线是否相交,如果相交的话交点在哪里?直线在游戏或动画里可以代表建筑的边界、地面或者物体路径,因此需要思考如何判断两直线是否相交以及直线在哪里。其实计算交点,就相当两个方程组求解,计算出满足两个方程中的(x,y)的点而已。

如果同一平面的两条直线,其解的情况如下:

  • 如果两条直线的斜率不相等,则仅有一组解
  • 如果两条直线的斜率及在y轴上的截距分别相等,则有无穷组解
  • 如果两条直线斜率相等,而在y轴上的截距不相等,则方程组无解

方程组的求解方法一般分为两种——消元法和带入法:

消元法的步骤:

  • 选择你要消去的变量(x或y)
  • 将两个方程分别乘上一个非0值,使你想要消去的变量前系数相同
  • 用一个方程减去另一个方程,得到联合方程
  • 求解出方程组第一个变量的值
  • 将得出的变量带入原始方程,求出另外一个变量的值。

带入法的步骤:

  • 从原始方程组中选择一个等式,对其进行变换使一个未知数用另外一个未知数表示出来,即一个未知数位于方程式的一边,而其余的元素全部位于等式另一边。
  • 将在上步中得到的等式带入原始方程组中的另一个方程中,此时就可以消去一个未知数。
  • 求解出一个未知数的值
  • 将上步骤中得到值带入原始方程中,从而求出另外一个未知数的值。

接下来让我们来看一个例子,加深下对消元法和代入法的理解,示例如下:

例8: 假如在你的游戏中,一辆汽车沿着直线3x+5y=8的方向行驶,而一堵墙被放置在直线x+3y=4处,如果汽车沿着原来的路线前进,它是否会撞到墙上?如果发生碰撞,那么碰撞点是多少?

解题思路:

消元法求解

1、是否会碰到墙上,我们需要确认两条直线的斜率,第一条直线为 -3/5,第二条直线为 -1/3,因此必相交。

2、利用消元法求解直线方程组

3x+5y=8

x+3y=4

3、选择你将要消去的变量,假如我们在这里要消去x。

4、我们在第二个方程的两边同时乘以3,从而得到下面方程组:

3x+5y=8

3x+9y=12

5、用上面的方程减去下面的方程,可以得到0x-4y=-4,y=1。

6、将y值带入任意一个方程,我们带入下面一个方程,得到x=1,因此方程组求解就是(1,1)

带入法求解:

1、从x+3y=4这个方程中,我们得出x=4-3y

2、把x=4-3y,带入3x+5y=8这个方程中,得出-4y=-4,然后得出y=1

3、然后把y=1,带入任意一方程进行求解,得出x=1,因此方程组求解就是(1,1)

网页上绘制直线和箭头

了解了点和直线的基础知识后,我们开始在电脑上进行实践,这里需要用到html5的canvas,通过这个技术我们可以画图以及进行更加灵活的的高级动画设计,甚至可以进行3D绘图,今天我们先利用其实现简单的直线和箭头的绘制。

关于线条的绘制主要包含以下几个常用方法:

context.moveTo(x,y):把画笔移动到(x,y)坐标,建立新的子路径。

context.lineTo(x,y):用于建立上一个点到(x,y)坐标的直线,如果没有上一个点,则等同moveTo(x,y),把(x,y)点添加到子路径中。

context.stroke():使用lineWidth、lineCap、lineJoin及strokeStyle对所有子路径进行描边。

context.closePath():如果当前路子路径是打开的,则关闭它。

给画布绘制一条对角线

假如我们从画布左上角的点(0,0)画一条对角线,我们需要知道右下角点的坐标,其实右下角的坐标即为画布的(宽,高),因此我们的代码部分如下:

<body>
   <h2>画线例子</h2>
   <canvas id="can"  width="300" height="300" ></canvas>
</body>
<script>
   //获取2d上下文
   var ctx = can.getContext("2d");
   var width = can.width,
       height = can.height;
   ctx.moveTo(0,0);
   ctx.lineTo(width,height);
   ctx.lineWidth = 6;
   ctx.strokeStyle = "red";   
   //开始画线
   ctx.stroke();
</script>

效果如下图:

「前端动画数学与物理基础」点和直线

绘制箭头

如果我们需要绘制一个箭头,如下图所示,我们需要知道其对应关键点的集合,如下图所示,然后不断的使用lineTo方法进行各个关键点的连接:

「前端动画数学与物理基础」点和直线

<body>
   <canvas id="can" width="400" height="300" ></canvas>
</body>
<script>
   //获取2d上下文
   var ctx = can.getContext("2d");
   var width = can.width,
       height = can.height;
   var pts=[[30,100],[300,100],[300,50],[350,130],[300,210],[300,160],[30,160]]
   ctx.strokeStyle="red";
   ctx.lineWidth = 2;
   ctx.moveTo(pts[0][0],pts[0][1]);
   for(var i=1;i<pts.length;i++)
   {
      ctx.lineTo(pts[i][0],pts[i][1]);
   }
   ctx.closePath();
   ctx.stroke();
</script>

今天的文章就到这里,接下来的文章将会介绍几何相关的基础知识,敬请期待。

更多精彩内容请关注“前端达人”公众号!