美文网首页动效中的数学
导数,微积分,牛顿运动学制作创意地图

导数,微积分,牛顿运动学制作创意地图

作者: PrivateEye_zzy | 来源:发表于2017-10-21 12:02 被阅读0次

    大前端时代下,面向组件,面向数据编程已家常便饭,但是作为一个合格的程序员,优秀的逻辑思维和抽象能力是其最基本的素质。代码只是实现过程和编程功底的体现,更重要的是分析问题,建模实力,解决问题,才能实现技术创新,引领业务,改变世界

    点击查看本文的第一个案例

    一个优秀的程序员应该做到:

    1. 观察归纳需求,抽象需求

    2. 设计算法将需求数学化,建立合理的数学模型

    3. 扎实的编程功底实现模型

    我们首先归纳的应该有以下的细节:

    1. 整个页面的动效交互只有滚动条触发

    2. 两点之间是任意曲线,曲线的运动学是整个曲线方程的子集轨迹,也就是局部曲线

    3. 由于滚动条有上下滚动两个方向,所以曲线的运动轨迹的长度随滚动条的高度成正比,即向下滚动,轨迹变长,向上滚动,轨迹变短,注意一点,轨迹长度的变化方向,和原曲线的方向一致

    4. 飞机沿着曲线的运动轨迹而做曲线运动,注意一点,由于飞机是svg图片,而其运动轨迹是任意曲线,那么飞机的方向也必须跟随轨迹运动而实时变化,这一点非常重要!

    5. 飞机在两点之间运动时,在该曲线的起点到1/2长度之间,飞机逐渐变大,在运动到超过1/2长度和自身长度之间,飞机又逐渐变小

    6. 在曲线运动过程中,整个地图也发生运动,且地图的运动轨迹和当前时刻曲线的运动方向一致

    7. 在滚动过程中,当左侧图文面板里出现图片,那么以飞机为三角形定点,左侧图片的上下两个点为三角形底边,作一个三角形光束照射在图片上,并且随着底边点的位置改变定点角度也随着变化

    以上细节,是一个程序员最基本的观察力和对需求的归纳能力,那么接下来,开始动手编程?那是新手,接下来要做的应该是逐一分析技术算法细节,也是培养我们的建模能力

    下面开始算法原理的分析:

    1. 前端技术栈很明显,使用原生js和canvas技术就可以实现,这一点没什么可以说的

    2. 曲线的制作可以利用三次贝塞尔方程(一切的计算都源于此方程)

    三次贝塞尔曲线的参数方程

    通过此方程,我们可以方便的求其偏导数以及任意时刻t对应的x和y值

    3. 两点间一曲线,把一条曲线和4个点(绘制三次贝塞尔)抽象成一个类,这非常容易想到,而这个类里应封装基本的绘制方法,包含曲线、圆形点、飞机图形、三角形等,还应封装计算方法,包含根据任意时刻t计算相应的x和y值、根据任意x或y值反解出曲线对应的t值、求x和y的偏导数、求曲线上任意一点的斜率、求曲线的总长度

    以上的分析依然很简单,只不过是oop和canvas的api的使用,接下来实际说明为什么需要封装这些计算方法

    4. 绘制局部贝塞尔曲线:

    我们知道使用canvas的api里bezierCurveTo(p0,p1,p2,p3)很容易画出一条完整的贝塞尔曲线,但是我们需要模拟的运动学,是沿着曲线做曲线运动,也就是需要绘制局部曲线,而canvas是没有给我们提供绘制任意局部曲线的api的,所以单纯依赖api是走不通的,我们需要从本质原理出发推理

    我们知道两点可以组成一条直线,那么曲线呢?而任意一条线上有多少个点?不妨假设有n个点,那么如果这n个点的坐标不是线性的,那么将这n个点连接起来,就是一条完整的曲线,如果这n个点的坐标满足贝塞尔方程,那么组合起来就是一条完整的贝塞尔曲线

    分析到这里,我们可以抽象出我们只要选择m个点(0=<m<=n),将这m个点连接起来,就构成了局部贝塞尔曲线(请思考为什么不只选取运动起点和运动终点来连线)

    而新问题紧接着产生,我们设滚动条滚动的距离为a,怎么去计算出a所对应的方程的x和y?

    因为我们浏览器的屏幕是笛卡尔坐标系,我们绘制任意一个点需要知道点的x坐标和y坐标,所以我们很容易想到不妨用a去映射方程终点p3对应的x,那么我们只需要求出p3的y值即可完成终点的绘制。观察方程,我们不能直接利用x去求y,需要先求出p3.x对应的t值,才能用t去计算p3.y

    已知任意时刻t的方程,P0,P1,P2,P3为四个点,其中P0和P3是起点和终点,P1和P2是控制点

    曲线方程

    我们已知曲线终点的Px,求Py,那么两边除以t的三次方,整理得:

    化简一

    设 m = (1 - t) / t,即可以整理出:

    化简二

    我们知道这是一个标准的三次方程,利用三次求根公式可以解出m进而求出t,那么带入t和另外三个点的y值就可以求出p3的y值

    但是这并不是一个非常好的做法,因为考虑我们绘制的是任意曲线,方程图像如果存在以下这种情况

    一对多的情况

    那么我们很难控制曲线到底应该画到第一个y值,还是第二个y值,所以综上分析,a去映射x求t来解y,会存在多解影响笛卡尔坐标绘制,故而正确的做法应该是用a去映射t,用t来解出x和y

    那么问题继而转化为,a怎么去映射t?

    我们可以容易的想到如果已知曲线的总长度L,用a和L的比率去计算局部dl,我们假设长度为L的曲线对应可视高度为H的值(当盒子完全被滚动条卷入,也就证明线走完了),可以得到dl = L / H * a,则此时此刻的t = dl / L (t 属于[0,1])

    那么问题就转化为需要先求出任意曲线的长度L?

    我们知道路程 = 速度 * 时间,基于微积分的思想,我们假设某个很小的时刻dt,存在速度dv,那么ds = dv * dt,最后我们对所有dt对应的ds进行求和,可以逼近曲线的总长度L(我们假设将曲线S划分为N段)

    问题即又转化为怎么求速度?(这里指的是合速度)

    很明显,我们需要对曲线方程对t进行求导,即得到其速度的方程式

    求导过程非常简单(请复习高中复合函数求导)

    速度方程

    我们带入t求出两个方向的速度,很显然,一个是沿着x方向的速度vx,另一个是沿着y方向的速度vy,我们需要计算出二者产生的合力的速度v即可

    根据平行四边形法则,我们知道v = (vx * vx + vy * vy) ^ 1/2,现在我们终于可以计算出曲线的长度L = ∑ v * dt,而a和H已知(单纯的网页dom计算,不题),我们就可以得到dl = L / H * a,最后得到t = dl / L,完成了a和t的映射关系

    现在我们用a映射出t,并且可以保证t在0到1之间,根据t解出真正的x和y值,至此,我们终于可以画出局部曲线了

    5. 局部曲线长度的动效变化:

    当我们建立如何画局部曲线后,将要解决随着滚动条向上或向下滚动时候,局部曲线的长度变化过程。现在我们已经得到任一点的dt和该dt对应的x和y值,很显然想到利用canvas的LineTo(x, y)进行绘制连线即可

    但是问题出现了,如果我们直接一步LineTo(x,y)到终点,会出现以下情况:

    绘制成直线了

    显然,我们不能一步就绘制起点和终点

    那么问题又回到了绘制局部曲线的理论,我们将起点(dt = 0)和终点( dt = t)之间构造出N个dt,利用微分的思想绘制可以很方便的绘制出这段曲线

    绘制真正的曲线

    这段代码请自行感悟

    绘制曲线的流程

    至此我们只要知道a对应的dt,利用微分思想,就可以绘制任意方向的局部曲线动效

    6. 飞机做曲线运动,且随着曲线方向实时旋转方向:

    我们飞机是svg矢量图,利用canvas的drawImage非常容易绘制出飞机,但是问题是计算机是不知道飞机的机头要跟随其运动的方向旋绕,这是我们的所常识决定的,所以我们要利用计算机仿真这个常识

    单纯的绘制飞机svg(不符合常识)

    我们观察很容易发现飞机的旋转是沿着运动轨迹的切线方向,其正负值由运动方向决定,如果我们知道任意时刻dt的曲线切线与水平轴的夹角d,那么利用canvas的rotate(d)就可以改变飞机的旋转角度,并且保证其旋转的角度和曲线相切,就可以模仿这个常识

    问题就抽象出计算曲线任意时刻dt的切线与水平轴的夹角?

    计算切线,其实就是求导,而我们之前就已经计算好dt对应的两个方向的偏导数方程式,这里只需要计算两个运动方向的夹角即可,我们利用arctan vy / vx 就可以计算出该时刻切线与水平轴的夹角

    计算切线角度后实时旋转

    7. 飞机在运动过程中的大小变化

    这一步就非常简单了,我们已知飞机的运动过程由t决定,而t在[0,1]之间,我们利用canvas的scale让飞机运动在[0,1/2 * L]的时候从0到1放大,在运动的[1/2 * L,L]的时候从1到0缩小即可

    那么问题就抽象成构造一个分段函数去计算scale的值

    利用初中数学很容易构造出该分段函数

    分段函数计算缩放

    至此我们就可以用[0,1]去映射[0,1]变化到[1,0],完成飞机运动的缩放

    8. 剩下基本是canvas的知识运用了,比如save和restore的结合,lineTo和moveTo的结合,渐变填充createLinearGradient等,都非常简单的纯api调用,这里就不题了

    现在,需求归纳和技术原理,实现难点,数学模型都已经推理完,接下来的才到最后一步,编程实现,而这一步单纯考验读者原生JS,闭包,异步编程的功底,相比之前的算法分析,显得简单的太多了

    最后我们可以从这个案例总结出几点:

    1. 一个优秀的程序员可以抽象出整个世界

    2. 优秀的数学功底,很多本质上的原理,都是数学原理的实际运用

    3. 算法永远是编程的灵魂,编程只是实现,算法是先驱,也是核心大脑

    相关文章

      网友评论

        本文标题:导数,微积分,牛顿运动学制作创意地图

        本文链接:https://www.haomeiwen.com/subject/iulvuxtx.html