在 Lucee 中生成 perlin 噪声的最简单方法是什么?

What is the easiest method of generating perlin noise in Lucee?

我正在编写一个简单的基于网络的游戏,它要求我创建随机世界 'zones',其中包含几千个地形图块(可能在 100x100 到 500x500 之间)。大多数在线建议建议我首先生成柏林噪声并将其用作高度图,然后是湿度的另一个实例,温度的另一个实例,等等,然后根据这些的组合分配地形值。

我宁愿不依赖安装任何其他语言或程序来执行此操作。但是,似乎没有任何内置函数可以直接使用 CFML 生成 perlin 噪声图。以最少的外部依赖性执行此操作的最简单方法是什么?

是否有一些 "perlinNoise" java 方法可以用来构建我可以在 CFML 中使用的数组?是否有cfscript/cfml在线提供的源代码或cfc来实现perlin功能(我不知道我是否可以自己翻译另一种语言的东西)?或者最简单的方法是通过 cfexecute 安装和使用 ImageMagick 之类的东西 generate/read 图像文件?

我试过的

我首先尝试转换维基百科上显示的 C++ 代码。如果我一生中使用过 C++,这可能很容易。不幸的是,我没有。我做到了这一点:

<cffunction name="lerp" access="public" output="no" returntype="numeric" description="Function to linearly interpolate between a0 and a1">
    <cfargument name="a0"       type="numeric"  required="yes">
    <cfargument name="a1"       type="numeric"  required="yes">
    <cfargument name="weight"   type="numeric"  required="yes">

    <cfset returnVal    = (1.0 - weight) * a0 + weight * a1>

    <cfreturn returnVal>
</cffunction>

<cffunction name="dotGridGradient" access="public" output="no" returntype="numeric" description="Computes the dot product of the distance and gradient vectors.">
    <cfargument name="ix"   type="numeric"  required="yes">
    <cfargument name="iy"   type="numeric"  required="yes">
    <cfargument name="x"    type="numeric"  required="yes">
    <cfargument name="y"    type="numeric"  required="yes">

    <!--- Precomputed (or otherwise) gradient vectors at each grid node --->
    <!--- <cfset test       = Gradient[IYMAX][IXMAX][2]> --->

    <!--- Compute the distance vector --->
    <cfset dx       = x - ix>
    <cfset dy       = y - iy>

    <!--- Compute the dot-product --->
    <cfset returnVal= (dx*Gradient[iy][ix][0] + dy*Gradient[iy][ix][1])>

    <cfreturn returnVal>
</cffunction>

<cffunction name="perlin" access="public" output="no" returntype="numeric" description="Compute Perlin noise at coordinates x, y">
    <cfargument name="x"        type="numeric"  required="yes">
    <cfargument name="y"        type="numeric"  required="yes">

    <!--- Determine grid cell coordinates --->
    <cfset x1       = int(x) + 1>
    <cfset y1       = int(y) + 1>

    <!--- Determine interpolation weights --->
    <!--- Could also use higher order polynomial/s-curve here --->
    <cfset sx       = x - x0>
    <cfset sy       = y - y0>

    <!--- Interpolate between grid point gradients --->
    float n0, n1, ix0, ix1, value;
    <cfset n0       = dotGridGradient(x0, y0, x, y)>
    <cfset n1       = dotGridGradient(x1, y0, x, y)>
    <cfset ix0      = lerp(n0, n1, sx)>
    <cfset n0       = dotGridGradient(x0, y1, x, y)>
    <cfset n1       = dotGridGradient(x1, y1, x, y)>
    <cfset ix1      = lerp(n0, n1, sx)>
    <cfset returnVal= lerp(ix0, ix1, sy)>

    <cfreturn returnVal>
</cffunction>

但是,实际上只有 lerp 函数在运行。我不知道 'gradient' 是什么意思。我假设它是一个数学函数,但我不确定如何在这里实现它。我的 Google 搜索不断给我不同的代码,以及一些对我来说不直观的解释。

此时使用 IM 变得更有吸引力了。它似乎更强大,我只是在避免它,因为弄清楚它并且在每次服务器移动时再安装一个东西似乎比在代码中安装一些东西更工作。由于代码方法似乎比我预期的要复杂,我休息了一下,尝试专注于 IM。

为此,我首先创建了种子等离子体或分形 canvas,效果很好。然后我尝试了许多不同的方法来为每个像素提取信息,但收效甚微:

    <cfexecute  name="#ImageMagick#\magick.exe"
                variable="imgResult"
                timeout="60"
                arguments="convert -size 500x500 -seed #seed# plasma:fractal -blur #blur# -shade 120x45 -auto-level #imgRoot#/temp/#fname#.png" />

    <cfloop from="1" to="20" index="x">
        <cfloop from="1" to="20" index="y">
            <!--- <cfexecute    name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="convert '#imgRoot#/temp/#fname#.png[1x1+#x#+#y#]' #imgRoot#/temp/temp.png" /> --->

            <!--- Works; takes 27s for 400 pixels.  Will take hours for full size maps.
            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="identify -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#]" />
            <cfset imgResult    = ListFirst(ListLast(imgResult, "gray("), "%")>
            --->

            <!--- Returns blank; probably because of u.r not being defined in a grayscale image? 
            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="convert #imgRoot#/temp/#fname#.png[1x1+#x#+#y#] -format ""%[fx:floor(255*u)]"" info" />
            --->

            <!--- Errors with some decode delegate error
            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="convert #imgRoot#/temp/#fname#.png: -format '%[pixel:p{#x#,#y#}]' info" /> --->
            <!--- Errors with some decode delegate error
            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="convert #imgRoot#/temp/#fname#.png: -crop 1x1+#x#+#y# -depth 8 txt" />
                         --->

            <!--- Returns the same value for every pixel
            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="convert -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#] txt" />
                         --->

            <cfexecute  name="#ImageMagick#\magick.exe"
                        variable="imgResult"
                        timeout="60"
                        arguments="identify -verbose #imgRoot#/temp/#fname#.png[1x1+#x#+#y#]" />
            <cfset imgResult    = ListFirst(ListLast(imgResult, "gray("), "%")>
            <cfset returnVal[x][y]  = imgResult>
        </cfloop>
    </cfloop>

到目前为止,我最好的方法是要求 27s 提取 400 像素的数据,并且不对该数据进行任何操作。如果我需要在真实场景中处理 160k 像素的图像 (400x400),那么我的处理器大约需要 3 个小时。所以假设我需要 3 张地图(高度、湿度和温度),那是……不切实际的。

我一直没能找到一个我在效率方面完全满意的解决方案,但我 运行 没时间解决这个问题并继续前进。以后我可能会回来进行优化,但就目前而言,我有一个可行的解决方案,尽管速度很慢。

根据 Mark Setchell 在 的出色回答,我发现,令人惊讶的是,解决我的问题的最有效方法是使用 Image Magic 生成分形,使用 IM 写入所有颜色信息out 到一个文件,然后使用 Lucee 读入该文件并解析每一行以获得亮度信息。这是我正在使用的代码:

    <cfexecute  name="#ImageMagick#\magick.exe"
                variable="imgResult"
                timeout="60"
                arguments="convert -size 500x500 -seed #seed# plasma:fractal -blur #blur# -shade 120x45 -auto-level #imgRoot#/temp/#fname#.png" />

    <cfexecute  name="#ImageMagick#\magick.exe"
                variable="imgResult"
                timeout="60"
                arguments="convert #imgRoot#/temp/#fname#.png -depth 8 #imgRoot#/temp/test.txt" />

    <cfset myfile       = FileOpen("#imgRoot#/temp/test.txt", "read")>
    <cfloop condition="NOT FileisEOF(myfile)">
        <cfset thisLine = FileReadLine(myfile)>
        <cfset x        = listFirst(thisLine, ",")>
        <cfset y        = listGetAt(thisLine, 2, ",")>
        <cfset y        = listFirst(y, ":")>
        <cfif isNumeric(x) and isNumeric(y)>
            <cfset thisStart    = FindNoCase("gray(", thisLine)>
            <cfif thisStart is not 0>
                <cfset thisVal      = Mid(thisLine, thisStart+5, 99999)>
                <cfset thisVal      = listFirst(thisVal, ")")>
                <cfset returnVal[x+1][y+1] = "#thisVal#">
            </cfif>
        </cfif>
    </cfloop>
    <cfset FileClose(myfile)>

我能够在 7.1 分钟内 运行 在 250k 像素的图像 (500x500) 上完成此操作,这比我尝试直接获取像素信息快了近 40 倍。我认为优化和验证都有很大的空间来避免错误,一旦我稍微加强一点,我会回来更新这个答案。

目前用这个生成3张500x500的图片,解析信息,写入数据库,30分钟就可以搞定。虽然不是最理想的,但很实用。