2017-02-16 5 views
2

iOSでSiriの波効果を描く方法を理解しようとしていて、this素晴らしいリポジトリを見つけました。 最終的な結果は次のようになります。SiriのWaveFormエフェクトを描画する

enter image description here

私は、単一の静的な正弦波を生成することができますwaves.Iを生成するコードで何が起こっているか理解する難しさを持っていますしかしこれ、私はしないでくださいかなり理解しているようです。私たちは、yの値を計算する際

特に、なぜそれがあることを持っています:

let y = scaling * maxAmplitude * normedAmplitude * sin(CGFloat(2 * M_PI) * self.frequency * (x/self.bounds.width) + self.phase) + self.bounds.height/2.0

ソースコード:

//MARK : Properties 


let density : CGFloat =  1 
let frequency : CGFloat =  1.5 
var phase :CGFloat =   0 
var phaseShift:CGFloat =  -0.15 
var numberOfWaves:Int =  6 
var primaryLineWidth:CGFloat = 1.5 
var idleAmplitude:CGFloat = 0.01 
var waveColor:UIColor =  UIColor.white 
var amplitude:CGFloat =  1.0 { 
    didSet { 
     amplitude = max(amplitude, self.idleAmplitude) 
     self.setNeedsDisplay() 
    } 
} 

方法

override open func draw(_ rect: CGRect) { 
    // Convenience function to draw the wave 
    func drawWave(_ index:Int, maxAmplitude:CGFloat, normedAmplitude:CGFloat) { 
     let path = UIBezierPath() 
     let mid = self.bounds.width/2.0 

     path.lineWidth = index == 0 ? self.primaryLineWidth : self.secondaryLineWidth 

     for x in Swift.stride(from:0, to:self.bounds.width + self.density, by:self.density) { 
      // Parabolic scaling 
      let scaling = -pow(1/mid * (x - mid), 2) + 1 

    // The confusing part ///////////////////////////////////////// 
      let y = scaling * maxAmplitude * normedAmplitude * 
    sin(CGFloat(2 * M_PI) * self.frequency * (x/self.bounds.width) + self.phase) 
+ self.bounds.height/2.0 

    ////////////////////////////////////////////////////////////////// 
      if x == 0 { 
       path.move(to: CGPoint(x:x, y:y)) 
      } else { 
       path.addLine(to: CGPoint(x:x, y:y)) 
      } 
     } 

     path.stroke() 
    } 

    let context = UIGraphicsGetCurrentContext() 
    context?.setAllowsAntialiasing(true) 

    self.backgroundColor?.set() 
    context?.fill(rect) 

    let halfHeight = self.bounds.height/2.0 
    let maxAmplitude = halfHeight - self.primaryLineWidth 

    for i in 0 ..< self.numberOfWaves { 
     let progress = 1.0 - CGFloat(i)/CGFloat(self.numberOfWaves) 
     let normedAmplitude = (1.5 * progress - 0.8) * self.amplitude 
     let multiplier = min(1.0, (progress/3.0*2.0) + (1.0/3.0)) 
     self.waveColor.withAlphaComponent(multiplier * self.waveColor.cgColor.alpha).set() 
     drawWave(i, maxAmplitude: maxAmplitude, normedAmplitude: normedAmplitude) 
    } 
    self.phase += self.phaseShift 
} 

の両方をループは非常に数学的に見えますが、私は何が起こっているのか手がかりがありませんe。 ありがとうございます。

+0

私はここに何があるのか​​よく分かりません... – Abizern

+0

私は 'draw(rect)'メソッドの中でコードの簡単な説明をしたかったのですが、質問はあいまいです。だから、私はこれを尋ねる。なぜ、 'let y = scaling * maxAmplitude * normedAmplitude * sin(CGFloat(2 * M_PI)* self.frequency *(x/self.bounds.width)+ self.phase)+ self.bounds。高さ/ 2.0' –

答えて

1

ここでは、最も内側のループの内訳を示します。このループは、xをループして波形を描画します。追加情報の一部が他の人に役立つことを期待して私の説明に少し詳しく説明します。

 for x in Swift.stride(from:0, to:self.bounds.width + self.density, by:self.density) 
     { 

density増分だけのUIViewの幅をループ反復。これにより、(1)波形の「解像度」と(2)描画に費やされる時間を費やす時間の2つのプロパティを制御できます。 density2ViewController.swift)に設定するだけで、計算の数を半分に削減し、描画する要素の半分のパスを生成します。 densityを完全なオーダー(10)増やしすぎるように見えるかもしれませんが、視覚的な違いに気づくのは難しいでしょう。三角波を表示する場合は、値を100に設定してみてください。

サイドノート:起因stride(from:to:by:)の使用ビューの幅は、densityによって割り切れない場合は、波形は図の右側の短い停止してもよいので、+ self.densityを添加しました。

  // Parabolic scaling 
      let scaling = -pow(1/mid * (x - mid), 2) + 1 

波形が画面の両側のアンカーポイントにどのように付いているように見えますか?それがこの放物線のスケーリングがやっていることです。はい、

その範囲内enter image description here

yが曲線に従うが、0でy開始が、中に正確に1.0に上昇する様子がわかり:より明確にそれを表示するには、Googleのグラフ作成機能にplug this formulaはこれを取得することができます中心を0に戻します。具体的には、xから0〜1の範囲で行います。これは、この曲線をビューの幅にマッピングするため、重要です。画面の左端x=0にマップされ、画面の右端がx=1にマップされます。

この曲線を画面上の波形にマッピングし、それを振幅(振幅:波形の中心線に対するサイズ)に合わせると、波形の左右の端点波形の大きさが中央でフルサイズ(1.0)に徐々に増加する0の振幅(アンカーポイント)を有するであろう。

このスケーリングの効果を完全に確認するには、その行をlet scaling = CGFloat(1.0)に変更してみてください。

この時点で波形をグラフ化する準備が整いました。 OPが尋ねていた元のコード行は次のとおりです。

let y = scaling * maxAmplitude * normedAmplitude * 
sin(CGFloat(2 * M_PI) * self.frequency * (x/self.bounds.width) + self.phase) 
+ self.bounds.height/2.0 

これは一度に多くのことを取り入れています。このコードはまったく同じことをしますが、私は何が起こっているのか理解を助けるための適切な名前を持つ一時的な変数にそれを離れて壊れました:

let unitWidth = x/self.bounds.width 

var wave = CGFloat(2 * M_PI) 
wave *= unitWidth 
wave *= self.frequency 

let wavePosition = wave + self.phase 

let waveUnitValue = sin(wavePosition) 

var amplitude = waveUnitValue * maxAmplitude 
amplitude *= scaling 
amplitude *= normedAmplitude 

let y = amplitude + self.bounds.height/2.0 

さて、一度、この1ビットに取り組むみましょう。 unitWidthから始めます。カーブを画面の幅にマッピングすると言いましたか?つまり、このunitWidth計算がやっているものです:0からself.bounds.widthunitWidthからx範囲は0からアップしているwave 1.

次の範囲であろうと。この値は正弦波を計算するためのものであることに注意してください。 sin関数はRadiansで機能します。つまり、正弦波の全期間が0〜2πの範囲になるので、そこで開始します(CGFloat(2 * M_PI))。

次にunitWidthwaveに適用し、ビュー内の指定されたxの位置の正弦波内の位置を決定します。ビューの左側に沿って、unitWidthは0なので、この乗算の結果は0(正弦波の始まり)になります。ビューの右側に沿って、unitWidthは1.0です値2π - 正弦波の終わり)。私たちがビューの真ん中にいる場合、unitWidthは0.5になり、完全な正弦波期間の途中になります。そして、その間のすべて。これは補間と呼ばれます。正弦波を動かすわけではないことを理解することが重要です。私たちはそれを進んでいます。

次は、self.frequencywaveに適用します。正弦波は、高い値には丘や谷が多くなるようにスケールされます。周波数は1で何もしないと、自然な正弦波に従います。しかしそれは退屈なので、視覚的な外観を良くするために周波数を少し上げます(1.5)。塩のように、味に合わせます。ここでは、3倍の周波数である:

enter image description here

これまでのところ、我々は正弦波は、我々はそれを描いているビューに対するどのように見えるかを定義しました。私たちの次の仕事はそれを動かすことです。そのためにself.phasewaveに追加します。これは、位相が波形内の明確な周期であるため、「位相」と呼ばれます。アニメーションのフレームごとに連続的にself.phaseを変更することにより、波形内の別の位置から描画が開始され、画面を超えて移動するように見えます。

最後に、実際の正弦波の値(let waveUnitValue = sin(wavePosition))を計算するためにwavePositionを使用します。 sin()の結果が-1から+1までの範囲の値であるため、これはwaveUnitValueと呼ばれています。

enter image description here

「私は必要...振幅の必要性を持っている」:私たちはそのままで、それを描いた場合、私たちの波は、ほぼ平坦な線に似ている、かなり退屈だろう

- 誰も

垂直に伸ばし、maxAmplitudeからwaveUnitValueを適用することにより、当社のamplitude開始。なぜ最大から始めるのですか? scaling変数の計算に戻ると、これは単位の値であり、0から1の範囲の値であることを思い出してください。これは、振幅を減少させるだけ(またはそれを変えない)それを増やす。

これはまさに私たちが次に行うことです。scalingの値を適用します。これにより、波形は両端で振幅が0になり、中央で徐々に最大振幅まで増加します。

enter image description here

最後に、我々はnormedAmplitudeを持っています。これがなければ、我々はこのようなものを持っているでしょう。コードに従うと、drawWave関数が複数の波をビューに描画するためにループ内で呼び出されていることがわかります(これはセカンダリまたは「シャドウ」波形が入る場所です)。normedAmplitudeは、全体的なエフェクトの一部として描画された各波形の振幅が異なります。

normedAmplitudeが負になり、シャドウ波形を垂直方向に反転して波形の空白を埋めることができます。 abs(normedAmplitude)に元のコードでnormedAmplitudeの使用を変更してみてください、あなたは(違いを強調するために3倍の周波数の一例と組み合わせて)このような表示されます:最後のステップは、波形を中央にある

enter image description here

をビュー(amplitude + self.bounds.height/2.0)は、波形を描画するために使用する最後のyの値になります。

だから、それでおしまい。

+0

すばらしい答えをありがとう。 –

関連する問題