池でのフロッガーHDと数値波モデリング

画像



SLY_GからCGAに関する記事を読んだ後私は非常に興奮しました。 彼は若さ、IBM PC / XT、そしてカエルが猛烈なレーシングバイクの車輪を避けて道路を横断しなければならなかったfrogger jrゲームを思い出しました。 次に、丸太で静かな背水に飛びます。 そして、死に、それは4個を与えました。 フライには666が与えられましたが、私はマックスではありません。

取り返しのつかないほど失われた年について泣いて、私は数日を失うことに決め、iPad用のゲームのリメイクを行いました。



差分スキームを使用して、川の水の動きを正しい方法でシミュレートすることにしました。

湖の波をモデル化するための数値アルゴリズムと何が起こったかについての詳細をお読みください。

はい! 言うのを忘れました。

シーケンスを継続できる人へ

TTFS E ...

読書は特に興味深いものではありません。





画像



だから水。 電話でのナビエ・ストークス方程式を数値的に解くのは時期尚早です。 したがって、私はモデルを取りました。親愛なるblind_designer氏の記事で親切に見られました。 盲目のPughの研究では、1次元のアルゴリズムが説明されています。 二次元に拡張しました。



水面モデル。



サイズがM * Nの長方形のメッシュを想像してください。 グリッドは地面にあり、初期長さLx0 [i、j]のスプリングに沿って各ノードから突き出ています。 バネの弾性は、その係数kx [i、j]によって決まります。



スプリングに軽い毛布を投げます-この毛布は貯水池の鏡をシミュレートします。



外力の影響下で(石が落ちた)、スプリングLx [i、j]の長さは変化します。 私たちが人生から覚えているように、投げられた石からの波は遅かれ早かれ落ち着きます。



したがって、バネ粘度mx [i、j]の別の配列があります。 ばねの粘度が0に設定されている場合、波は停止せず、無限に長い時間海岸から反射されます。



ばねの数式は非常に単純です(散逸を伴うフックの法則)



for (int i=0; i<n; i++) { float x = Lx[i] - Lx0[i]; float acceleration = -kx[i] * x - mx[i]*Wx[i]; Lx[i] += Wx[i]*dt; Wx[i] += acceleration*dt; }
      
      







ここで、配列Wx [i、j]は各スプリングの垂直速度です。 簡単です-バネの加速度は、弾性係数に変位を乗じた値に等しくなります。 そして、速度は加速度の積分です。 そして、変位は速度の積分です。 厳密性のために導入されたタイムステップdt = 1。



ソリューションをこの形式のままにすると、隣接するスプリングに関係なく、各スプリングが自動的にスイングします。 人生ではそうではありません、隣人の間にはつながりがあります。 この関係は、拡散方程式または(設計者向け)幅9スプリングのフィルターを使用して説明します。 このフィルターは、各スプリングの速度を世界の各サイドに隣接する4つのエリアに分散し、波の効果を作成します。



サイクルを追跡する



  float spread = 0.025; // do some passes where springs pull on their neighbours for (int iki = 0; iki < 4; iki++) { // 4        //     for (int j = 0; j < ny; j++) { for (int k = 1; k < nx-1; k++) { int i = k + j*nx; lp[i] = spread * (Lx[i] - Lx[i-1]); Wx[i - 1] += lp[i]; rp[i] = spread * (Lx[i] - Lx[i + 1]); Wx[i + 1] += rp[i]; } } for (int j = 0; j < ny; j++) { for (int k = 1; k < nx-1; k++) { int i = k + j*nx; Lx[i - 1] += lp[i]; Lx[i + 1] += rp[i]; } } //    y for (int j = 1; j < ny-1; j++) { for (int k = 0; k < nx; k++) { int i = k + j*nx; lp[i] = spread * (Lx[i] - Lx[i-nx]); Wx[i - nx] += lp[i]; rp[i] = spread * (Lx[i] - Lx[i + nx]); Wx[i + nx] += rp[i]; } } for (int j = 1; j < ny-1; j++) { for (int k = 0; k < nx; k++) { int i = k + j*nx; Lx[i - nx] += lp[i]; Lx[i + nx] += rp[i]; } } }
      
      







配列lp []およびrp []は一時的なものであり、アルゴリズムを自分の能力に合わせて最適化します。

nxはx軸に沿ったノードの数、nyはy軸に沿ったノードの数です。



すべてが明確ですか? 私の意見では、まったく、視覚化に移りましょう。



可視化





3次元の表面を描くことができます。 そして、私はOpenGLのリアリズムから遠ざかり、波を平らな絵で示します。 湖の上をホバリングするヘリコプターからの眺めのようなものです。 ピカソも同じことをするでしょう。 メッシュに比例する辺を持つテクスチャを作成します。

色の知覚がプールの水に似ているといいでしょう。



画像



サンプルテクスチャ。 ゼプトラブスで愚かに。



テクスチャをrawDataピクセルの2次元配列に変換します。各ピクセルは4バイトまたはRGBAコンポーネントを持ちます。

  myUIImage = [UIImage imageNamed:@"ground_2"]; n = nx*ny; CGImageRef image = [myUIImage CGImage]; NSUInteger width2 = CGImageGetWidth(image); NSUInteger height2 = CGImageGetHeight(image); CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); bytesPerPixel2 = 4; bytesPerRow2 = bytesPerPixel2 * width2; NSUInteger bitsPerComponent = 8; rawData = malloc(height2 * width2 * 4); CGContextRef context = CGBitmapContextCreate(rawData, width2, height2, bitsPerComponent, bytesPerRow2, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); CGColorSpaceRelease(colorSpace); CGContextDrawImage(context, CGRectMake(0, 0, width2, height2), image); CGContextRelease(context);
      
      







すべてがモデリングの準備ができています。

最初の図があります-rawData [i、j]。

各ポイントに現在の水面高さがあります-Lx [i、j]。

各ポイントに水面の垂直速度があります-Wx [i、j]。



速度によって混乱したテクスチャを描画するために残ります。 ピクセル[]配列に新しい画像を作成します。



 -(void) renderWater { size_t width = nx*2; size_t height = ny*2; size_t bytesPerRow = 4*width; memset(pixel, 0, bytesPerRow*height); float zz = -1.9; for (int j=0;j<height;j++) { for (int k=0;k<width;k++) { int i2 = (int) (k*4 + j*bytesPerRow); int k4 = k/2; int j4 = j/2; int s1 = k%2; int s2 = 2-s1; int s3 = j%2; int s4 = 2-s3; int i4 = k4 + nx * j4; float h2 = Lx[i4] - Lx0[i4]; h2 = (Wx[i4]*s2*s4 + Wx[i4+1]*s1*s4 + Wx[i4+nx+1]*s1*s3 + Wx[i4+nx]*s2*s3) / 4.0; int a = 255.0*h2*h2*0.15; if (a>255) a = 255; float x2 = (k4>0 && k4<nx-1) ? Lx[i4-1] - Lx[i4+1] : 0; float y2 = (j4>0 && j4<ny-1) ? Lx[i4-nx] - Lx[i4+nx] : 0; int k2 = k+zz*x2; int j2 = j+zz*y2; if (k2<1) k2 = 0; if (k2>width-1) k2 = (int) width-1; if (j2<1) j2 = 0; if (j2>height-1) j2 = (int) height-1; int byteIndex = (int) ((bytesPerRow2 * j2) + k2 * bytesPerPixel2); int red = rawData[byteIndex]; int green = rawData[byteIndex+1]; int blue = rawData[byteIndex+2]; pixel[i2+0] = red; pixel[i2+1] = green; pixel[i2+2] = blue; pixel[i2+3] = 255-a; } } CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB(); CGContextRef context=CGBitmapContextCreate(pixel, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Big |kCGImageAlphaPremultipliedLast); CGImageRef image=CGBitmapContextCreateImage(context); CGContextRelease(context); CGColorSpaceRelease(colorSpace); UIImage *resultUIImage=[UIImage imageWithCGImage:image]; CGImageRelease(image); water.image = resultUIImage; }
      
      







各ポイントで、初期画像からのオフセットが計算され、現在の画像に補間されます。 補間は、プログラムがiPhone 4Sで動作するために必要です。 これを行うために、各方向のテクスチャのサイズを半分にし、アルゴリズムの速度を4倍にしました。 6番目のiPhoneでは、これは必要ではなく、160 x 284のグリッドに対応しています。



さらに、特定のポイントでの水の速度に応じて、テクスチャの透明度を0から255に変更します。



それだけです このサイクルは、古いiPhone 4Sでも毎秒20フレームでうまく機能します。



おわりに





ウォーターフォークドライビング






シミュレーション結果は、iPad用の2つのアプリケーションとiPhone用の2つのアプリケーションでも見ることができます。



画像

ハケンアプリ。



画像

フロッガーアプリ。



すべての良い週末と祖先への明るい思い出。



All Articles