LNCollectionView-替换幂率流体

引言

本篇文章我们讨论一些有趣的东西:使用另外一种流体模型替换LNScrollView中的一部分零件;这里有一些算式,最终他们会汇总到一个新的Simulator中,它将会替换掉LNScrollView中的DecelerateSimulator零件;我们可以用现实中的物料解释这步操作具体做了什么:在初始的时候,LNScrollView中充满了水,现在我们需要把它们替换成奶油或者牙膏。

一些理论

牛顿流体:摩擦力(或"剪切应力")和流体速度线性关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v a = -cv </math>a=−cv

非牛顿流体:摩擦力和流体速度非线性关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = f ( v ) a = f(v) </math>a=f(v)

幂率流体:摩擦力和流体速度幂率关系:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v n a = -cv^n </math>a=−cvn

幂率流体是一种通用模型,它是非牛顿流体中的一种,n叫"流性指数":

  1. <math xmlns="http://www.w3.org/1998/Math/MathML"> n < 1 n < 1 </math>n<1:假塑性流体
  2. <math xmlns="http://www.w3.org/1998/Math/MathML"> n > 1 n > 1 </math>n>1:胀塑性流体
  3. <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1:牛顿流体 所以我们替换为幂率模型实际上是对牛顿流体进行了拓展;在实际的代码中,我们也会分情况进行讨论,其中的一种情况是牛顿流体。

一些运算

幂率流体我们同样遵循三个步骤:创建Simulator、累积时间、结束。 幂率我们称为PowerLaw;PowerLaw和Decelerate输入一致,我们需要当前velocity和位移两个变量;然后让velocity不断减少,offset不断增加。

我们应该确定每段时间velocity减少的量和每段时间offset增加的量;这个章节会讲解如何使用一个 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ t \Delta t </math>Δt计算出 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ y \Delta y </math>Δy和 <math xmlns="http://www.w3.org/1998/Math/MathML"> Δ v \Delta v </math>Δv,从最基础的算式开始:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> a = − c v n a = -cv^n </math>a=−cvn

做同样的事情:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v ′ = − c v n v' = -cv^n </math>v′=−cvn

两边积分:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = [ ( n − 1 ) ( δ t − C ) ] 1 1 − n , C = v 0 1 − n 1 − n v = [(n - 1)(\delta t - C)]^{\frac{1}{1-n}}, C = \frac{v_0^{1-n}}{1-n} </math>v=[(n−1)(δt−C)]1−n1,C=1−nv01−n

OK,我们可以将时间间隔带入到公式中,计算出下一个时间点的速度:当前的速度是 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 0 v_0 </math>v0,经过一段时间后,这个速度会变成把 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t带入上面算式算出的速度 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v。

除此之外,我们还需要计算出经过了时间 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t后的位移;当然,我们可以通过把 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y和 <math xmlns="http://www.w3.org/1998/Math/MathML"> y ′ y' </math>y′移动到一端,然后两边积分以得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> y y </math>y对 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t的表达式相减。这比较麻烦,我们可以稍微做个代换,使用 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v替换 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = d l d t = d l d v d v d t = d l d v a = d l d v ( − δ v n ) v=\frac{dl}{dt}=\frac{dl}{dv}\frac{dv}{dt}=\frac{dl}{dv}a=\frac{dl}{dv}(-\delta v^n) </math>v=dtdl=dvdldtdv=dvdla=dvdl(−δvn)
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v 1 − n d v = − δ d l v^{1-n}dv = -\delta dl </math>v1−ndv=−δdl

在这个基础上进行俩边积分会非常容易:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> l = 1 δ ( 2 − n ) ∗ ( v 1 2 − n − v 2 2 − n ) l = \frac{1}{\delta(2-n)}*(v_1^{2-n} - v_2^{2-n}) </math>l=δ(2−n)1∗(v12−n−v22−n)

用这个公式,我们可以将上面的 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 0 v_0 </math>v0和 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t计算出的时间 <math xmlns="http://www.w3.org/1998/Math/MathML"> v v </math>v带入到 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 1 v_1 </math>v1和 <math xmlns="http://www.w3.org/1998/Math/MathML"> v 2 v_2 </math>v2可以算出这个位移差;这会很简单,因为不需要从t再算了,相当于我们使用了第一步的结果。

这里有几个特殊情况:

  1. <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1时:不能使用上面的算式计算,因为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n − 1 n-1 </math>n−1在分母,无法计算;我们之前说了这种情况是牛顿流体,所以当 <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n = 1 </math>n=1的时候,我们可以直接使用Decelerate的算式。

  2. <math xmlns="http://www.w3.org/1998/Math/MathML"> n = 2 n = 2 </math>n=2时: <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 − n = 0 2-n = 0 </math>2−n=0出现在分母中,所以我们不能使用这个算式计算偏移量(或许在这里对n->0使用洛必达求极限是正确的,但是没有这样做);在代码中专门对这种情况进行了处理:
    <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> v = v 0 1 + δ v t v = \frac{v_0}{1 + \delta vt} </math>v=1+δvtv0
    <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y = 1 δ l n 1 + δ v t y = \frac{1}{\delta}ln^{1+\delta vt} </math>y=δ1ln1+δvt

代码

这里有3部分代码: 初始化:

ini 复制代码
- (instancetype)initWithPosition:(CGFloat)position
                        velocity:(CGFloat)velocity
                               k:(CGFloat)k
                               n:(CGFloat)n
{
    self = [super init];
    if (self) {
        self.position = position;
        self.velocity = velocity;
        self.k = k;
        self.n = n;
    }
    return self;
}

保存一下位置、速度、运动参数等。 累积时间:

objectivec 复制代码
- (void)accumulate:(NSTimeInterval)during
{
    if (fabs(self.n - 1.f) < 0.000001) {
        [self accumulateNewtonian:during];
    } else if (fabs(self.n - 2.f) < 0.000001) {
        [self accumulateNonNewtonianLn:during];
    } else {
        [self accumulateNonNewtonian:during];
    }
}

常规场景(n!=1&n!=2)时:

ini 复制代码
- (void)accumulateNonNewtonian:(NSTimeInterval)during
{
    self.currentTime += during;
    if (self.velocity == 0) {
        return;
    } else if (self.velocity > 0) {
        CGFloat c = pow(self.velocity, 1.0 - self.n)/(1.0 - self.n);
        CGFloat vBase = (self.n - 1)*self.k*during - c*(self.n - 1);
        CGFloat vExp = 1.0/(1.0 - self.n);
        CGFloat v = pow(vBase, vExp);
        CGFloat l = (1.0/(self.k*(2.0 - self.n)))*(pow(self.velocity, 2.0 - self.n) - pow(v, 2.0 - self.n));
        self.velocity = v;
        self.position = self.position + l;
    } else {
        CGFloat positiveVelocity = -self.velocity;
        CGFloat c = pow(positiveVelocity, 1.0 - self.n)/(1.0 - self.n);
        CGFloat vBase = (self.n - 1)*self.k*during - c*(self.n - 1);
        CGFloat vExp = 1.0/(1.0 - self.n);
        CGFloat v = pow(vBase, vExp);
        CGFloat l = (1.0/(self.k*(2.0 - self.n)))*(pow(positiveVelocity, 2.0 - self.n) - pow(v, 2.0 - self.n));
        self.velocity = -v;
        self.position = self.position - l;
    }
}

<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 2 n=2 </math>n=2时,使用ln形式的累积:

ini 复制代码
- (void)accumulateNonNewtonianLn:(NSTimeInterval)during {
    self.currentTime += during;
    if (self.velocity == 0) {
        return;
    } else if (self.velocity > 0) {
        CGFloat v = self.velocity/(1.0 + self.k * self.velocity * during);
        CGFloat l = (1.0/self.k)*log(1.0 + self.k * self.velocity * during);
        self.velocity = v;
        self.position = self.position + l;
    } else {
        CGFloat positiveVelocity = -self.velocity;
        CGFloat v = positiveVelocity/(1.0 + self.k * positiveVelocity * during);
        CGFloat l = (1.0/self.k)*log(1.0 + self.k * positiveVelocity * during);
        self.velocity = -v;
        self.position = self.position - l;
    }
}

<math xmlns="http://www.w3.org/1998/Math/MathML"> n = 1 n=1 </math>n=1时,使用牛顿流体的累积:

ini 复制代码
- (void)accumulateNewtonian:(NSTimeInterval)during
{
    self.currentTime += during;
    CGFloat v = self.velocity * exp(-self.k * during);
    CGFloat l = (-1.f/self.k) * self.velocity * exp(- self.k * during) - (-1.f/self.k) * self.velocity;
    if (self.velocity < 0.01) {
        self.velocity = 0;
    }
    self.velocity = v;
    self.position = self.position + l;
}

完成(速度降低到很小):

objectivec 复制代码
- (BOOL)isFinished
{
    return fabs(self.velocity) < 1.0;
}

接入LNScrollView

在LNScrollView的代理中返回新的Simulator,它会替换掉DecelerateSimulator:

ini 复制代码
- (LNScrollViewDecelerateSimulator *)ln_scrollViewHorizontalDecelerateSimulatorForPosition:(CGFloat)position velocity:(CGFloat)velocity {
    LNScrollViewPowerLawDecelerateSimulator *simulator = [[LNScrollViewPowerLawDecelerateSimulator alloc] initWithPosition:position velocity:velocity k:2 n:1.2];
    return simulator;
}

- (LNScrollViewDecelerateSimulator *)ln_scrollViewVerticalDecelerateSimulatorForPosition:(CGFloat)position velocity:(CGFloat)velocity {
    LNScrollViewPowerLawDecelerateSimulator *simulator = [[LNScrollViewPowerLawDecelerateSimulator alloc] initWithPosition:position velocity:velocity k:2 n:1.2];
    return simulator;
}

总结

本文讲述了如何实现一个自定义的Simulator用来替换LNScrollView中已经定义好的一些Simulator,只需要实现好环境初始化、时间累积函数和结束的阈值就可以正常工作。

相关推荐
eternal__day38 分钟前
数据结构十大排序之(冒泡,快排,并归)
java·数据结构·算法
姚先生971 小时前
LeetCode 35. 搜索插入位置 (C++实现)
c++·算法·leetcode
Theodore_10221 小时前
3 需求分析
java·开发语言·算法·java-ee·软件工程·需求分析·需求
阿华的代码王国2 小时前
【算法】栈
数据结构·算法·leetcode·
SpongeG2 小时前
数据结构_平衡二叉树
数据结构·算法
arnold662 小时前
华为OD E卷(100分)31-敏感字段加密
算法·华为od
AC使者3 小时前
2110 加分二叉树
算法
shentuyu木木木(森)3 小时前
入门STL(map/multiset)
开发语言·数据结构·c++·算法·map·multiset
破-风3 小时前
leetcode----mysql
算法·leetcode·职场和发展
Psycho_MrZhang3 小时前
常见的数据结构和应用场景
数据结构·算法·哈希算法