كيفية زراعة غابة على Actionscript3 / Flash في بضعة أسطر من التعليمات البرمجية

في هذا التعليق ، تفاخرت بأنني كتبت برنامجًا أنشأ غابة "ذات مظهر لائق" في مائتي سطر من التعليمات البرمجية. لسوء الحظ ، تبين أن الواقع أكبر قليلاً في الحجم - تحتوي المصادر المحفورة على حوالي 2100 سطر من التعليمات البرمجية ، منها حوالي 700 تعليق وأفكار بصوت عالٍ ورمز قديم مهمل ومحاولات لتوثيق طرق. حجم SWF القابل للتنفيذ ، ومع ذلك ، تبين أن 13112 بايت.

بدأ كل شيء بحقيقة أنه في منتدى Kongregate.com ، حيث كنت أقضي وقتًا نشطًا في ذلك الوقت ، اقترح أحد المشاركين التنافس في الجيل الإجرائي لشيء ما ، كان الموضوع الأول هو "الغابة" .



وبطبيعة الحال ، كان لكل شخص فكرته الخاصة حول ما يجب أن تكون الغابة التي سينمونها. في ذلك الوقت قرأت كتبًا عن جميع أنواع السحر ، ونتيجة لذلك ، كنت أرغب في زراعة غابة. تتكون الغابة من الأشجار - نكتب فئة الشجرة {...}. تتكون الشجرة من أغصان وأوراق - نكتب فرع الفصل {...} ونعتقد ، هل نحن حقا بحاجة إلى أن تأخذ بعين الاعتبار كل ورقة على الشجرة؟ ونتيجة لذلك ، حصل "الفرع" على معلمة "بأوراق" ، واكتسبت الشجرة زوجًا من القوام ، أحدهما للأغصان والجذع ، والآخر للأوراق. كان النسيج "تحت الشجرة" بسيطًا نسبيًا - هناك ضجيج بيرلين ، يمكنك تمديده ، لفه ، طلائه ، اعتباره جاهزًا ، ولكن كان عليك أن تتلاعب بالأوراق.

ومع ذلك ، لم أكن راضيًا عن ضجيج الهاوسر على نسيج الشجرة ، وبدلاً من ذلك خرجت بخريطة - أي قام بإنشاء خريطة ارتفاع ، وقام بتعديلها تحت نصف دائرة الفرع المرئي من الجانب ، ثم ملأ النسيج الرئيسي باللون البني وفرض خريطة ارتفاع مع تعديل الإضاءة إلى الصرير الجانبي. الرمز الناتج هو كما يلي:

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; } 

"Basis" عبارة عن فئة مساعدة للمتجهات a la Vector3D ، ولكن نظرًا لكتابة الشفرة في ذلك الحين تحت Flash 10.1 ، لم يكن هناك مثل هذه المتجهات حتى الآن ، أو فضلت أن أصنع دراجتي الخاصة. تم رسم النسيج تحت الفرع بأوراق على النحو التالي: في البداية تم عمل ورقة واحدة ، ثم تم تحديد ما إذا كان للفروع ورقة مركزية ، وهذا يحدد طول قطعة الفرع التي تم إرفاق الأوراق بها ، ثم تم إرفاقها (محسوبة على النسيج) بزاوية إلى الفرع من خلال العرض المحسوب للورقة . تم تعيين شكل الورقة كدائرة مشوهة مع العديد من النقاط المرجعية التي تم تعويضها من الدائرة بنصف قطر نصف ورقة ، وتم تعيين طول القطع بشكل منفصل ، كل هذا تم رسمه على نسيج الورقة باللون الأسود والأبيض وحفظه للمستقبل. (بتعبير أدق ، كان هناك نسيجان "فرع بأوراق" ، أحدهما للنهايات ، أي الفروع التي لا ينمو فيها شيء من "النهاية" ، ولكن بالأوراق ، تم رسم ورقة عليها في نهاية الفرع ، والثانية لـ "منتصف "بدون ورقة نهاية.)

ثم الجزء الأصعب هو كيف ستبدو الشجرة؟ هنا فكرت وجربت لفترة طويلة. قررت أن أجعل الشجرة تنمو حقًا - تمتد الفروع في الطول (تنمو فعليًا من النهاية) ، وأحيانًا تفرخ الفروع إلى الجانب ، وتمتد الفروع إلى الشمس (أعلى) وحالتين إضافيتين. اتضح تجزئة رهيبة ، أفضل خيار تمكنا من مشاركته بدا كما يلي:

الصورة
(من الغريب أن diary.ru هي خدمة ممتازة لاستضافة الصور ، حتى الآن لم يحدث شيء سيئ!)

توصلت إلى استنتاج مفاده أننا بحاجة إلى تقليل كثافة الفروع بطريقة أو بأخرى. في البداية ، كانت الفكرة هي الحد من جاذبيتها - أي الفروع "الثقيلة" جدا تنقطع وتسقط. بدأت في حساب لحظة القوة في الانحناء ، ومقارنتها بقوة الشجرة (قمت بسحب القيم من مكان ما ، وسجلتها كثوابت وبدأت في الاختبار) - اتضح بشكل سيئ ، وأحيانًا انكسر الجذع ، على الرغم من أنه لم يكن يجب أن يحدث ، وكانت الشجرة مثنية بأمان ، في بعض الأحيان انكسر فرع كبير في البداية ، أدت النتيجة إلى جذع غير متوازن وانكسر مرة أخرى ، هذه المرة بسبب فقدان التوازن الرأسي ، وأحيانًا كان فرعًا طبيعيًا تمامًا في الهيكل ، ينمو في السمك ، ينحني في البداية تحت وزنه ، ثم كسر ، المبيعات إذا لم يعد نمت شيء في ذلك. سجل لأن التحدي كان الموعد النهائي.

كانت المحاولة الثانية للحد من نمو الفروع الجديدة وبقاء الفروع القديمة / السابقة باستخدام الإضاءة. من المحاولة الثالثة للتنفيذ (بقيت الأولين في شكل وظائف تم التعليق عليها) اتضح الأمر على هذا النحو: لقد بنيت شبكة شعرية ثلاثية الأبعاد مع جانب 0.5 متر (نعم ، جميع القيم الموجودة بالأمتار والكيلوغرامات - كنت أرغب حقًا في الفيزياء الحقيقية لغابة حقيقية في ذلك الوقت) ، والتي تم ملؤها في الأصفار الأولى ، ثم عند الالتفاف حول الشجرة ، ساهم كل فرع في ملء الشبكة في شكل حجمه مقسومًا على واحد أو اثنين من voxels. والحقيقة هي أن جميع الفروع (على أي حال تقريبًا) كقطع منفصلة من الإطار المحسوب كانت أقصر من 0.5 متر ، مما سمح لنا باستخدام تقريب تقريبي. بالإضافة إلى الحشو ، فإن كل فرع "يلقي بظلاله" على voxels الأساسية في شكل حشو إضافي لل voxels تحت و voxel قليلاً مع voxel مع فرع (الشكل النهائي هو هرم مربع ، ولكن اللعب مع دائرة كان ينكسر ، وبالتالي لم يكن إضاءة على أي حال). تم استخدام هذه الشبكة كمحدد ، إذا بدأ أحد الفروع في النمو في منتصف الشجرة - سيكون هناك ضوء أقل هناك ، سيكون أقصر وقد لا ينمو على الإطلاق أو يموت من نقص الإضاءة. ثم سقطت الفروع الميتة.

جعل هذا الخيار من الممكن الحصول على الأشجار التي كانت شفافة نسبيًا عند عرضها وضغطها نسبيًا من حيث النطاق ، بدت النسخة العاملة الأولى على النحو التالي:



في هذا الإصدار ، ما زلت أراجع آلية نمو الشجرة نفسها ، ويمكن عرض الشجرة من جميع الجهات. تم رسم الشجرة فرعًا واحدًا في كل مرة ، وتم فرز مجموعة الفروع أولاً حسب المسافة من المراقب ، كما هو الحال في دورة VMX القديمة الجيدة على الرسومات ثلاثية الأبعاد من عام 1996 ، اخترت الألوان لأغراض التجميل من مجموعة HSB لكل مكالمة "ارسم لي شجرة" حتى لا تكون الغابة رتيبة ، يتم أيضًا تدوير الهيكل العظمي للشجرة بشكل عشوائي للرسم. في المجموع ، كان هناك ستة إلى ثمانية نماذج شجرة للرسم ، ينمو كل منها تحت تأثير RNG الخاص بها ، وتضبط المناظر الطبيعية للأرض ضوضاء أخرى للهاوسر ، ويتم اختيار المكان الذي تنمو فيه الشجرة بشكل عشوائي باستخدام مجموعة من النقاط المسموح بها للنمو عند الانتقال إلى الجانب مراقب المسافة. إذا كانت الشجرة مزروعة عند النقطة A ، وكان نصف قطر الشجرة R المحدد لـ "النمو" ، فإن القيم (AR ، A + R) ممنوعة للنمو عند المسافة الحالية ، عند الانتقال إلى المرحلة التالية (-0.05) ، انخفض هذا الفاصل بمقدار 0.1 ، وتمت إزالته عندما تم تخفيضه إلى الصفر.

آخر (وفي الواقع الأول والأخذ في الاعتبار فورًا) الفروق الدقيقة في الخوارزمية بأكملها هي أنها طويلة جدًا. للتجول في شجرة "البالغين" ، يستغرق الرسم بضع ثوانٍ ، وعدد أقل قليلًا لرسم مواد شجرة واحدة ، ويستغرق الأمر من نصف ثانية إلى اثنتين ، ولم يتم تصميم Adobe Flash لمثل هذه الفترات الزمنية الطويلة للحساب دون تحديث الشاشة (بشكل أكثر دقة ، دون إعادة التحكم في المحرك) . لذلك ، كنا بحاجة إلى خوارزمية يمكنها حفظ الحالة بين المكالمات ، ومواصلة العمل من المكان الذي تمت مقاطعته فيه والتحكم في وقت تنفيذه ، وفي الوقت نفسه لا داعي للذعر ولا تنزعج من محرك الفلاش. تم تنفيذ إنقاذ الولاية كزوج من خصائص الفئة الرئيسية ، مقسمة إلى مراحل - من خلال تحديد الوظائف "زراعة شجرة مرة واحدة" ، و "رسم شجرة منتهية" و "رسم قطعة أرض" وقياس الوقت المستغرق ، على التوالي ، بمجرد "المرة" التالية استغرقت الشجرة أكثر من بضع ثوان ، واعتبرت الشجرة "جاهزة" وتم وضعها جانباً. اتضح أن هناك ثلاث مراحل كبيرة: إنشاء مواد ، "زراعة" الأشجار ، ووضع الأشجار النهائية على الشاشة.

تبدو النتيجة كالتالي:



يمكنك اللعب هنا . يمكن أن يكون تحسين برنامج Flash 10.1 المحسن (بشكل أكثر دقة ، مكتوبًا) ، مع الأخذ في الاعتبار مجموعة من تحديثات الفلاش من حيث الأمان ، بطيئًا للغاية - في هذه الحالة ، أنصحك بتنزيل إصدار تصحيح برنامج Adobe Flash Player 11.5 وفتحه في وضع عدم الاتصال. يستغرق الرسم بأكمله 5-6 دقائق ، بعد أول دورتين على الشاشة تبدأ بعض الحركة في الحدوث ، وهو ما قد يكون مثيرًا للاهتمام للملاحظة. بعد الرسم ، يمكنك الضغط على Ctrl + النقر لحفظ النتيجة كملف PNG رباعي مقارنة بحجم النافذة.

Source: https://habr.com/ru/post/ar429256/


All Articles