数行のコードでActionscript3 / Flashでフォレストを成長させる方法

このコメントでは、 200行のコードで「まともな」フォレストを作成するプログラムを作成したことを自慢しました。 残念ながら、現実にはサイズがわずかに大きいことが判明しました-発掘されたソースには約2100行のコードが含まれており、そのうち約700行はコメント、声高な考え、古い破棄コード、メソッドの文書化の試みです。 ただし、SWF実行可能ファイルのサイズは13112バイトでした。



それはすべて、私が当時活発に活動していたKongregate.comフォーラムで、参加者の1人が何かの手続き型世代に参加することを提案したという事実から始まりました。最初のトピックは「森林」でした。







当然のことながら、誰もが自分たちが成長する森はどうあるべきか自分の考えを持っていました。 当時、私はあらゆる種類の魔法に関する本を読んでいたので、その結果、森を育てたかったのです。 フォレストは木で構成されています-クラスTree {...}を記述します。 ツリーは枝と葉で構成されます-クラスBranch {...}を記述し、ツリー上の各葉を実際に考慮する必要があると思いますか? その結果、「枝」は「葉あり」パラメータを取得し、ツリーは枝と幹、葉用のテクスチャのペアを取得しました。 「木の下」のテクスチャは比較的簡単に作成できました。パーリンノイズがあり、ストレッチ、ラップ、ペイント、準備ができていると考えることができますが、葉をいじる必要がありました。



しかし、私は木のテクスチャのホーサーノイズだけでは満足していませんでしたが、代わりにバンプマッピングを思いつきました-つまり。 高さマップを作成し、側面から見える枝の半円の下でそれを微調整し、メインテクスチャを茶色で塗りつぶし、サイドキークに合わせて照明を調整した高さマップを重ねました。 結果のコードは次のとおりです。



private function generateBranch():void { branchBitmap = new BitmapData(512, 512, true, 0xff8080ff); //branchBitmap.perlinNoise(32, 256, 2, 100 + Math.round(Math.random() * 900), true, true, 7, true); var hm:BitmapData = new BitmapData(512, 512, false, 0); var seed:int = 1000 + Math.random() * 2000; hm.perlinNoise(24,192, 2, seed, true, true, BitmapDataChannel.BLUE, false); // blue only. Is a heightmap var i:int; var j:int; for (i = 0; i < 512; i++) { if (Math.abs(i - 256) > 100) r = 0; else r = 200 * Math.sqrt(1 - (i - 256) * (i - 256) / 10000);// square curve //r = 200 * Math.sin(Math.PI * (i - 128) / 256); // sine curve for (j = 0; j < 512; j++) hm.setPixel(i, j, Math.round(r*(500.0 + (hm.getPixel(i, j)-128))*0.002)); // now, r means position on the "log", and initial perlin noise is log's texture. // perlinNoise median 128, highest offset taking as 100, the result offset needs to be ~0.2 in multiplier } var v:Vector.<int> = new Vector.<int>(); var vv:Vector.<Number> = new Vector.<Number>(3); for (i = 1; i < 511; i++) { v.length = 0; v.push(hm.getPixel(0, i-1), hm.getPixel(1, i-1), hm.getPixel(2, i-1), hm.getPixel(0, i), hm.getPixel(1, i), hm.getPixel(2, i), hm.getPixel(0, i+i), hm.getPixel(1, i+1), hm.getPixel(2, i+1)); for (j = 1; j < 510; j++) { var g:int = -1 * v[0] - 2 * v[1] - 1 * v[2] + 1 * v[8] + 2 * v[7] + 1 * v[6]; // gradient by Y var r:int = -1 * v[0] - 2 * v[3] - 1 * v[6] + 1 * v[2] + 2 * v[5] + 1 * v[8]; // gradient by X //if ((i > 50) && (i < 55) && (j > 50) && (j < 55)) trace(g, r); var b:int = v[5]; r += 128; g += 128; var p:uint = r *0x10000 + g*0x100 + b; branchBitmap.setPixel(j, i, p); v.shift(); v.push(hm.getPixel(j + 2, i + 1)); v[2] = hm.getPixel(j + 2, i - 1); v[5] = hm.getPixel(j + 2, i); } } var bf:BlurFilter = new BlurFilter(2,8); // ___ // bevelFilter is not what I need, it bevels a rectangle and just that [___] // dropShadowFilter requires empty alpha I believe // convolution filter works best on self, while it can do what I need branchBitmap.applyFilter(branchBitmap, branchBitmap.rect, P0, bf); hm.copyPixels(branchBitmap, branchBitmap.rect, P0); //branchBitmap.perlinNoise(32, 256, 0, seed, true, true, 7, true); // naked grayscale // 0 octaves means 50% gray filling branchBitmap.fillRect(branchBitmap.rect, 0xff808080); // it looks like I'll have enough details just by perlin-noising the heightmap var inc:Number = Math.PI / 3; var azi:Number = -Math.PI * 1 / 4; var cx:Number = Math.cos(inc) * Math.cos(azi); var cy:Number = Math.cos(inc) * Math.sin(azi); var cz:Number = Math.sin(inc); azi = 1 - 2 * cz; inc = 1 / cz; // cos(lighting) to be normalized into (0..1) via cz for (i = 0; i < 512; i++) for (j = 0; j < 512; j++) { p = branchBitmap.getPixel(j, i); var h:uint = hm.getPixel(j, i); // give a vector here somewhere vv[0]= (h >> 16)-128; vv[1] = ((h >> 8) & 255)-128; vv[2] = 26; // balance constant, a normal is always pointing upwards, Basis.Normalize(vv); var m:Number = inc*(cx * vv[0] + cy * vv[1] + cz * vv[2]); // cos(lightangle) r = (p >> 16) & 255; g = (p >> 8) & 255; b = p & 255; r = Math.max(0,Math.min(255, r * m)); g = Math.max(0,Math.min(255, g * m)); b = Math.max(0,Math.min(255, b * m)); branchBitmap.setPixel(j, i, 0x10000 * r + 0x100 * g + b); } branchBitmap.applyFilter(branchBitmap, branchBitmap.rect, P0,bf); // should be here, without blurring it's liney hm = new BitmapData(192, 512, false); hm.copyPixels(branchBitmap, new Rectangle(160, 0, 192, 512), P0); branchBitmap = hm; }
      
      





「ベーシス」はVector3Dのようなベクターのヘルパークラスですが、コードはFlash 10.1で書かれていたため、そのようなベクターはまだ存在しなかったか、自分の自転車を作るほうが好きでした。 葉のある枝のテクスチャは次のように描かれました:最初に1枚のシートが作成され、次に枝に中央のシートがあるかどうかが決定され、これが葉が取り付けられた枝の断片の長さを決定し、シートの計算された幅によって枝に角度で取り付けられました(テクスチャで計算されました) 。 葉の形状は、円から半シートの半径だけオフセットされたいくつかの参照ポイントを持つ歪んだ円として設定され、茎の​​長さは個別に設定されました。これらはすべて葉のテクスチャに白黒で描かれ、将来のために保存されました。 (より正確には、2つの「葉のある枝」テクスチャがあり、1つは端部用、つまり「端部」から何も成長しない枝ですが、葉では、枝の端に葉が描かれ、 「エンドシートなし。)



次に、最も難しい部分は、ツリーがどのように見えるかです。 ここで私は長い間考え、実験しました。 私は木を本当に成長させることにしました-枝は長さで伸び(実際には端から伸びます)、時には側に枝を産み、枝は太陽に伸び(上)、さらにいくつかの条件があります。 それはひどいハッシュであり、私たちが共有することができた最良のオプションは次のように見えました。



画像

(奇妙なことに、diary.ruは優れた写真ホスティングです;今のところ何も腐っていません!)



枝の密度をどうにかして減らす必要があるという結論に達しました。 当初、アイデアはそれらを重力的に制限することでした-つまり あまりにも「重い」枝は、単に壊れて落ちます。 私は曲げ力の瞬間を数え始め、それを木の強さと比較しました(どこかから値をドラッグし、定数として記録し、テストを開始しました)-ひどく判明しましたが、実際にはそうではありませんでしたが、木は安全に曲がっていました、時には大きな枝が最初に壊れて、結果は不均衡なトランクになり、今度は垂直バランスが失われたために再び壊れました。時には、構造が非常に正常で、太さが成長し、最初はその重量で曲げられた枝壊れた、販売 その中には何ももはや成長していない場合。 チャレンジは締め切りだったため、彼は得点しました。



2番目の試みは、照明を使用して、新しいブランチの成長と古い/前のブランチの生存の両方を制限することでした。 実装の3回目の試行(最初の2つはコメントアウトされた関数の形のまま)から次のようになりました:0.5メートルの辺を持つ3次元のボクセルラティスを構築しました(はい、すべての値はメートルとキログラムでした-実際の森林の実際の物理学が本当に欲しかったです)最初のゼロで、次にツリーを回るとき、各ブランチは、ボリュームを1つまたは2つのボクセルで割った形で格子の充填に寄与しました。 実際、計算されたフレームの個別の部分としてのすべてのブランチ(いずれの場合も、ほとんどすべて)は0.5mよりも短いため、大まかな近似を使用できます。 塗りつぶしに加えて、各ブランチは、ボクセルの下とボクセルの少し周りのボクセルにブランチを追加して塗りつぶすという形で、下にあるボクセルに「影を落とします」(最終形状は四角錐ですが、円でぐるぐる回っていると壊れていたので、とにかく点灯しませんでした)。 このラティスはリミッターとして使用されました。枝の1つが木の真ん中で成長し始めた場合-そこの光が少なくなり、短くなり、まったく成長しないか、照明不足で死ぬ可能性があります。 枯れた枝が落ちました。



このオプションを使用すると、表示時に比較的透明で、範囲が比較的コンパクトなツリーを取得できました。最初の作業バージョンは次のようになりました。







このバージョンでは、ツリー成長メカニズム自体をまだデバッグしており、ツリーをあらゆる側面から表示できました。 1996年の3次元グラフィックスに関する古き良きVMXコースのように、ツリーは一度に1ブランチずつ描かれ、オブザーバーからの距離によってソートされました。「draw me a tree」という呼び出しごとにHSB範囲からコスメドローの色を選択しました森が単調でないように、木の骨格もランダムに回転して描画します。 描画用の木のモデルは6から8種類あり、それぞれが独自のRNGの影響下で成長し、地球の風景が別のホーサーノイズを設定し、側面に移動する際の成長の許容ポイントの範囲を使用して、木の成長場所をランダムに選択しました距離オブザーバー。 ツリーがポイントAに植えられ、ツリーの半径Rが「成長」に選択されている場合、次の間隔(-0.05)に移動すると、値(AR、A + R)は現在の距離での成長が禁止されます。 0.1、およびゼロに減少すると削除されます。



アルゴリズム全体の最後の(そして実際には最初ですぐに考慮される)ニュアンスは、非常に長いということです。 「アダルト」ツリーを移動するには、描画に数秒、1つのツリーのテクスチャを描画するのにさらに数秒かかり、0.5秒から2秒かかります。AdobeFlashは、画面を更新せずに(より正確に、エンジンに制御を戻すことなく)長い計算間隔用に設計されていません。 したがって、呼び出し間で状態を維持し、中断された場所から作業を継続し、実行時間を制御し、同時にフラッシュエンジンをパニックに陥らせないようにする方法を知っているアルゴリズムが必要でした。 状態の保存は、メインクラスのプロパティのペアとして実装され、「木を1回成長させる」、「完成した木を描く」、「土地を1つ描く」という機能を選択することで段階に分け、それぞれ次の「1回」になるとすぐに費やした時間を測定します樹木が数秒以上かかったため、樹木は「準備ができた」とみなされ、脇に置かれました。 テクスチャの作成、ツリーの「成長」、完成したツリーの画面への配置という3つの大きなフェーズが判明しました。



結果は次のようになります。







ここでプレイできます 。 Flash 10.1向けに最適化(より正確に記述)すると、セキュリティの観点から多数のFlashアップデートを考慮すると、非常に遅くなる可能性があります。この場合、Adobe Flash Player 11.5のデバッグバージョンをダウンロードしてオフラインで開くことをお勧めします。 描画全体は、画面上の最初の2つが動き始めてから5〜6分かかります。 描画後、Ctrlキーを押しながらクリックすると、ウィンドウのサイズと比較して、結果を4倍のPNGファイルとして保存できます。



All Articles