【Aegisub】匀速化一般参数曲线现有的最佳方案

简单说一下现有方案中,在精确性、快速性、灵活性、方便性上全都最好的匀速化参数曲线的方法。
先看一些对比举例:

左边是kyanko前辈的匀速贝塞尔曲线,右边是我匀速贝塞尔曲线的效果。可以看到kyanko前辈匀速曲线的准确度不够好,在该例中,并不是kyanko前辈的求积分求长度不够精确导致的,而是牛顿迭代不准确导致的,牛顿法本来就不太好使,也就是说,就算其它部分的代码不变、只改变迭代算法,这里的曲线一样能被很好地匀速化。下面这4张对比图都是同样的情况(仅因为牛顿法不咋地而导致匀速效果不准确,左边是kyanko前辈的,右边是我的效果):




然后对于一般的参数曲线,kyanko前辈在求长度的时候有利用周期性来分段求值,但是,没有周期的振荡曲线当然多得数不过来,比如:

上图是用我的函数匀速化该曲线后的样子,可见匀速的效果非常好。那这样的振荡曲线,如果用kyanko前辈的思路,自然在求积分的时候会有很大误差。再比如,就算函数有周期,利用周期性来求也不见得计算有多正确,比如这样的振荡曲线:

上图还是用我的函数将该曲线匀速化后的样子,可见效果非常精准。而如果用kyanko前辈的方法,这函数虽然有周期,但按周期来算,也会有很大误差,更别说还需要使用者自己去计算不同函数的周期,所以,这在准确性和便利性上都不太好。
说完了精确度的问题,然后是灵活性和便利性的问题。kyanko前辈的匀速一般曲线,需要使用者自己去计算导数,所以使用者每次想要匀速某个曲线时,就要自行求导,这怎么想都非常地不方便,而我写的函数,可以支持使用者不自行求导,就是说,使用者如果自己愿意求导,那可以传入使用者求出的导数,使用者若懒得求导,那就算不人为地提供导数,一样能做匀速曲线效果,这样,在灵活性和便利性上都大大的提高了。
最后就是代码速度的问题了。下图标红的是kyanko前辈的耗时,标白的是我的耗时:


可见我的代码在速度上也是更快的,就比如匀速贝塞尔曲线,我的函数速度一般比kyanko前辈的快了约一倍,也就是说,在精度速度灵活度便利度,每一个方面,我现有的方案都是最佳的。
简单的介绍一下我采用的方案。在求数值积分上,我测试了很多方法,比如修改优化后的双指数方法,虽然这种方法精确度不错,但速度十分缓慢,因为需要循环很多次,不管是在提前准备好节点的情况下,还是不提前生成节点的情况下,都十分的缓慢,所以就无所谓是否提前生成节点了,所以自然不能用这种方法来实现匀速曲线效果。那其它方法怎样呢,比如自适应Gauss–Kronrod积分,也就是算积分的同时也有误差估计,根据估计的误差来分段,每一次将误差最大的段平分,然后计算积分以及估计误差,直到每一段估计误差加起来小于你设定的容忍值为止,这种方法虽然得到的结果比不用误差估计的Gauss–Kronrod方法要精确,但速度肯定是慢的,尤其是,如果要做匀速曲线,就得不停地求积分,耗时并不会乐观,所以这种自适应积分也是不能用。那这么说来,实际就不可能有又快又高精度的数值积分方法了,那确实,是这样的,没错的。但这里有个问题啊,先清醒一下啊!做匀速曲线的原理我在视频里老早就讲过,由于咱们等间隔的取时间参数t,采得曲线上的点并不是等间隔的,所以咱们需要弧长参数s,当我们等间隔地取弧长参数s(如0 , 0.1 , 0.2 , 0.3)时,得到的曲线上的点也会是均匀的,所以重点就是怎样实现弧长参数化。这很简单,首先要求得曲线的总长度L,对吧,这时咱们要均匀采样就能得到匀速的点了(如取0*L , 0.1*L , 0.2*L , 0.3*L),也就是咱们要求得在长度为 s*L 时,点的坐标,所以只要求得在长度为 s*L 处的时间参数t,就能将t代入曲线方程,就直接有点坐标了。所以,现在只有两个问题,一是求曲线总长度,二是根据指定长度求此处的时间参数t,求长度需要数值积分,求t需要反复迭代,所以呢,在求t的过程中是会反复用到"求长度"的函数的,什么意思,就是说假设你希望均匀地取1000个曲线上的点,每取一次就要求t,每求一次t就要反复使用"求长度"函数,来1000次爽不爽啊,问题在哪,在求长度啊,很清晰吧,求一回长度,是不需要用更高精度的积分的,只要分段就行了,直接用Gauss–Kronrod积分就行了啊,总想着怎么高精度地求长度很耗时,不如直接分段啊,分段以后,每一段的求长度求积分是精确的(足够),迭代求t的时候,在这一段上找解不就完了,这好家伙,不是又快又精确吗?分明是一个很简单的问题,而如果总是纠结于高精度数值积分,那思维就完全被限制住了。
分段的话,kyanko前辈是有分的,那为什么kyanko前辈的函数这么慢呢,这是因为在每一次求长度时,都分成很多段来计算,可问题是,分段,只需要分一次,不需要次次分!次次分,只会又连累速度又连累精确度!一条曲线就是分成64段又如何,只分一次,又快又准,而如果次次分,一条曲线总共只分5段又如何,次次分,又慢又不准!分段,只分一次,每一小段的长度是精确计算的(足够),每一段的长度是已知的,这样,指定长度处的点,落在哪一段上也是已知的,这很清晰,在这一段上找t,自然是精确的,这很简单!
当然除了这些,刚刚也讲了,我用了其它迭代算法、比牛顿法更精准,同时还提供了数值求导。还有就是,很早以前我提过,kyanko前辈计算的切线角度明显是有错误的,在y=0时,有0和π两种可能,kyanko前辈求的角度在y=0时只能得到0,其实,有一个函数,名叫,math.atan2,是的没错,大家可以在aeg里使用这个函数,对,是这样,自己写不写这函数都无所谓,但若写错了就不是无所谓了。math.atan2函数和math.atan不同,math.atan2返回值的范围,要猜猜,那当然是-π到π了,能覆盖整个平面直角坐标系,求角度直接用math.atan2即可。
最后,具体的代码照旧在视频里介绍。