前兩天看到一個(gè)水波的動(dòng)畫(huà)圖,形如下面這樣子,突然波動(dòng)方程在記憶深處就復(fù)活了。發(fā)現(xiàn)這個(gè)方程可以很好玩,雖然只是二維的,但還是分享給大家。

* 波動(dòng)方程的推導(dǎo)
已知波源A的振動(dòng)方程:y=Acos(ωt+φA?)。假設(shè)波沿x軸正方向傳播速率記為u,沿x軸負(fù)方向傳播速率記為V。以O(shè)點(diǎn)為坐標(biāo)原點(diǎn),A點(diǎn)的坐標(biāo)為Xa,以水平向右為x軸的正方向建立坐標(biāo)系。
當(dāng)波沿x軸正方向傳播的時(shí)候,如下圖所示,在x軸正方向上任取一點(diǎn)P,與o點(diǎn)相距Xp。當(dāng)?shù)貌ㄏ蛴覀鞑r(shí),P點(diǎn)的振動(dòng)落后于A點(diǎn),落后的相位為ω (Xp-Xa)/u。可得P點(diǎn)在任意時(shí)刻t的位移:y=Acos[ωt-ω (Xp-Xa)/u+φa]= Acos[ωt- ω Xp/u+(φa+ ω Xa/u)]。可參考:如何求解平面簡(jiǎn)諧波的波動(dòng)方程。

得到的這個(gè)方程叫平面簡(jiǎn)諧波動(dòng)方程,形如:
y=Acos[ωt- ω Xp/u+(φa+ ω Xa/u)]
它能描述任意時(shí)刻任意點(diǎn)在Y軸方向上的位移y。加上一個(gè)微分的思想,取一個(gè)X+dx值,然后計(jì)算出對(duì)應(yīng)的y值,將這些n個(gè)y值連成一條線(xiàn)就是任意時(shí)刻的波形圖。當(dāng)t在流逝的時(shí)候這個(gè)波形圖不斷的在重畫(huà),就形成了一個(gè)波動(dòng)圖了!在代碼前先分析一下這個(gè)方程:y=Acos[ωt- ω Xp/u+(φa+ ω Xa/u)]
* 方程的物理意義
- y是y坐標(biāo)的位置,也就是我們要計(jì)算點(diǎn)的y值
- A是振幅,就這個(gè)波振動(dòng)幅度的大小
- ω是頻率,可以理解為是振動(dòng)的快慢,ω = 2 / T(T是周期)
- t就是時(shí)間了,時(shí)間是均勻(絕對(duì)時(shí)空觀)往前走的
- u描述的是波的傳播速率,波速=波長(zhǎng)/周期
- φa描述的波源的初相位,在圖上看就是這個(gè)點(diǎn)是在x軸(φa=0)上開(kāi)始振動(dòng),還是在x軸上多少或下多少(cosφa)的地方開(kāi)始振動(dòng)。
只是為了實(shí)現(xiàn)這種效果,我們就利用最簡(jiǎn)單的簡(jiǎn)諧波了。此時(shí):
- 波的初相為φa = 0
- 波源就在Xa處,即Xa = 0
此時(shí)波動(dòng)方程就變成了:
* 最簡(jiǎn)方程
y=Acos(ωt- ω Xp/u)
是不是簡(jiǎn)單了好多!這樣再設(shè)兩個(gè)變量,一個(gè)是周期T和波長(zhǎng)K,根據(jù) “波速=波長(zhǎng)/周期(u = K / T)” 和 ω = 2π / T。波動(dòng)方程就可以推導(dǎo)成這個(gè):
y = Acos2π(t/T - Xp/K)
這樣代碼的樣子就出來(lái)了,忍不住開(kāi)始代碼了吧!
*代碼實(shí)現(xiàn)
利用Quartz2D就可以簡(jiǎn)單實(shí)現(xiàn),用一個(gè)UIView來(lái)實(shí)現(xiàn),命名為WaveView(所有代碼都在WaveView.m文件里面)。
設(shè)置波的基本屬性:
@interface WaveView ()
{
CGFloat screenWidth;
float A; // 振幅
float t; // 時(shí)間變量
float T; // 周期
float K; // 波長(zhǎng)
}
@end
在init方法里面初始化波的基本屬性值:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// 設(shè)置波的基本屬性()
A = 6;
t = 0;
T = 1.0;
K = 80;
[self setBackgroundColor:[UIColor clearColor]];
// 設(shè)置刷新圖像的頻率,0.3秒人眼就很難分辨出來(lái)了
[NSTimer scheduledTimerWithTimeInterval:0.03 target:self selector:@selector(animateWave) userInfo:nil repeats:YES];
}
return self;
}
啟動(dòng)動(dòng)畫(huà):
-(void)animateWave
{
// 這里是時(shí)間機(jī)器,如果和刷新圖像的時(shí)間間隔一樣,那么就是正常時(shí)間的速度
// 如果大于刷新時(shí)間間隔,那么時(shí)間就走的很快,是平常的多少倍自己去計(jì)算
// 如果小于刷新時(shí)間間隔,那么時(shí)間就走的慢
t+=0.05; // 這里比實(shí)際時(shí)間快
[self setNeedsDisplay];
動(dòng)畫(huà)算法的實(shí)現(xiàn):
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGContextSetLineWidth(context, 2);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
float y=axleXOnScreenHeight;
CGPathMoveToPoint(path, NULL, 0, y);
// 這是將x軸微分單位為1pt,就是每隔1pt計(jì)算一個(gè)y值,將所有的y值連起來(lái)就是一個(gè)波圖
// 可以將微分單位設(shè)置大一點(diǎn),有不一樣的效果
// y 值的計(jì)算就用推導(dǎo)出來(lái)的公式:y = Acos2π(t/T - Xp/K)
for(float x=0;x <= screenWidth;x+=1){
y = A * cos(2*M_PI * (t / T - x / K)) + axleXOnScreenHeight;
CGPathAddLineToPoint(path, nil, x, y);
}
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathStroke);
CGPathRelease(path);
}
效果如下,波長(zhǎng)為80,屏幕寬了375剛好5個(gè)波峰的樣子:

其它好玩的效果(比如波動(dòng)過(guò)程中改變振幅的大小,就可以增加波的節(jié)奏感等):



是不是挺好玩的,可惜沒(méi)有接觸過(guò)3D引擎,不然整個(gè)世界都可以波動(dòng)起來(lái)。
趕緊試試!
源碼位置:https://github.com/stoull/Wave