如何在几行代码中在Actionscript3 / Flash上​​生长森林

在此评论中,我吹嘘自己编写了一个程序,该程序用两百行代码创建了一个“外观不错”的森林。 不幸的是,事实证明它的大小要稍大一些-挖掘的资源包含大约2100行代码,其中大约700行是注释,大声的思想,废弃的旧代码以及尝试记录方法的代码。 但是,SWF可执行文件的大小为13112字节。

一切始于一个事实,在我当时很活跃的Kongregate.com论坛上,一位参与者建议竞争程序生成的东西,第一个主题是“ Forest”



自然,每个人对于自己将要生长的森林都有自己的想法。 那时我读了有关各种魔术的书,因此,我想种一片森林。 森林由树木组成-我们编写类Tree {...}。 一棵树由树枝和叶子组成-我们编写类Branch {...},我们认为,我们是否真的需要考虑树上的每个叶子? 结果,“分支”获得了“有叶子”参数,而树获得了一对纹理,一个纹理用于​​分支,一个树干,一个用于叶子。 “树下”的纹理制作起来相对比较简单-存在perlin杂音,您可以拉伸,包裹,油漆,准备就绪,但必须修补树叶。

但是,我不仅仅对树的纹理上的钩子杂音感到满意,而是想出了颠簸的方法-即 他创建了一个高度图,在侧面可见的分支的半圆形下对其进行了调整,然后用棕色填充主纹理,并在高度图上叠加了调整为生动的侧面照明。 结果代码如下:

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”是Vector3D中向量的帮助类,但是由于代码是在Flash 10.1下编写的,所以现在还没有这样的向量,或者我更喜欢自己动手制作自行车。 绘制带有叶子的分支下面的纹理的方法如下:首先制作一张纸,然后确定分支是否有中央薄片,这确定了叶子所附着的那条分支的长度,然后通过计算出的薄片宽度将它们与分支成角度地附着(在纹理上计算) 。 叶子的形状设置为扭曲的圆,几个参考点从圆偏离半叶半径,并分别设置茎的长度,所有这些以黑色和白色绘制在叶子纹理上,以备将来使用。 (更确切地说,有两个“带有叶的分支”纹理,一个为末端,即分支从其“末端”开始没有生长,但是有叶子的是在分支的末端绘制了叶子,第二个为“中间” “没有底页。)

那么最困难的部分是那棵树是什么样的? 在这里,我思考并尝试了很长时间。 我决定让树真正长大-树枝的长度伸展(实际上是从末端开始伸展),有时将树枝生成到侧面,树枝伸展到太阳(向上),还有更多条件。 事实证明这是一个可怕的哈希,我们设法共享的最佳选择如下所示:

图片
(奇怪的是,diary.ru是出色的照片托管服务,到目前为止,一切都还不错!)

我得出的结论是,我们需要以某种方式降低分支的密度。 最初的想法是限制它们的引力-即 过于“沉重”的分支只是折断而倒下。 我开始计算弯曲力的力矩,并将其与树的强度进行比较(我将值从某个地方拖动,记为常数并开始测试)-结果很糟糕,有时树干折断了,即使实际上它不应该折断,树也安全地弯曲了,有时一开始大的树枝断裂,结果导致树干失衡,然后又再次断裂,这是由于垂直平衡的损失,有时是一个结构很正常的分支,厚度逐渐增大,最初在其重量的作用下弯曲,然后坏了,销售 如果没有在它不再增长。 他得分是因为挑战是最后期限。

第二种尝试是使用照明来限制新分支的增长和旧/旧分支的生存。 从第三次尝试实现(前两次尝试以注释函数的形式保留)来看,结果是这样的:我构建了一个边长为0.5米的三维体素晶格(是的,所有值都以米和千克为单位-我当时确实想要真实的物理学用于真实的森林),该逻辑被填充了首先是零,然后当绕树时,每个分支以其体积除以一或两个体素的形式对晶格进行填充。 事实是,作为计算框架的单独部分的所有分支(无论如何,几乎都是)都小于0.5m,这使我们可以使用粗略的近似值。 除了填充之外,每个分支都以附加的形式在分支下方的体素周围填充一个分支(最终形状是方形金字塔,但随着一个圆圈折腾而破裂,因此无论如何都不会发光),从而在基础体素上“投射阴影”。 此格子用作限制器,如果其中一个分支开始在树的中间生长-那里的光线较少,它将变短,可能根本不生长或因缺乏照明而死亡。 然后枯死的树枝掉下来了。

此选项使获得的树在查看时相对透明并且在范围方面相对紧凑成为可能,第一个工作版本如下所示:



在此版本中,我仍然调试了树增长机制本身,并且可以从各个角度查看树。 一次将树绘制成一个分支,首先按照与观察者的距离对分支数组进行排序,就像在1996年关于三维图形的老式VMX老课程中一样,我从HSB范围中选择了用于装饰目的的颜色,每次称为“画一棵树”为了使森林不是单调的,树木的骨骼也会随机旋转绘制。 总共有六到八棵树模型,每个树模型都是在其自己的RNG影响下生长的,地球的景观设置了另一根大吼大叫的噪音,树的生长位置是通过一系列允许移动到侧面的生长点的范围随机选择的距离观察者。 如果将树种在点A上,并且将树的半径R选择为“增长”,则值(AR,A + R)在当前距离处禁止增长,当移至下一个(-0.05)时,此间隔减小0.1,并减少到零时将其删除。

整个算法的最后一个(实际上是第一个并且立即考虑在内)的细微差别是它非常长。 要绕过“成人”树,绘制需要花费几秒钟,绘制一棵树的纹理需要花费更多的时间,需要半秒钟到两秒钟,并且Adobe Flash并非为不计算屏幕而设计的这么长的计算间隔(更确切地说,没有将控制权返回给引擎) 。 因此,我们需要一种算法,该算法可以保存两次调用之间的状态,从被中断的地方继续工作并控制其执行时间,同时又不要让自身感到惊慌,也不要让闪存引擎感到惊慌。 保存状态被实现为Main类的一对属性,分为两个阶段-通过选择功能“一次种植一棵树”,“绘制一棵完成的树”和“绘制一块土地”,并分别测量下一次“一次”花费的时间一棵树花了超过几秒钟的时间,该树被认为是“准备就绪”并被搁置一旁。 结果分为三个主要阶段:创建纹理,“生长”树木,将完成的树木放置在屏幕上。

结果看起来像这样:



你可以在这里玩。 针对Flash 10.1进行优化(更精确地说,是书面的)时,考虑到安全性方面的大量Flash更新可能会非常慢-在这种情况下,我建议您下载Adobe Flash Player 11.5的调试版本并离线打开。 整个绘图需要5到6分钟,在屏幕上的前两个之后,开始出现一些移动,这可能很有趣。 绘制后,您可以按Ctrl +单击以将结果保存为与窗口大小相比的四倍PNG文件。

Source: https://habr.com/ru/post/zh-CN429256/


All Articles