Какой самый простой метод генерации перлин-шума в Lucee? - PullRequest
0 голосов
/ 11 декабря 2018

Я пишу простую веб-игру, которая требует от меня создания случайных «зон» в верхнем мире, состоящих из нескольких тысяч плиток ландшафта (вероятно, между 100x100 и 500x500).Большинство онлайн-советов предлагают начать с создания шума Перлина и использовать его в качестве карты высот, затем другой экземпляр для влажности, другой для температуры и т. Д., А затем назначить значения рельефа на основе их комбинации.

Я бы предпочел не полагаться на установку каких-либо других языков или программ для этого.Тем не менее, похоже, что нет встроенной функции для генерации карты шума perlin с CFML напрямую.Какой самый простой способ сделать это с минимальными внешними зависимостями?

Есть ли какой-нибудь "perlinNoise" java-метод, который я могу использовать для создания массива, с которым я затем смогу работать в CFML?Существует ли исходный код cfscript / cfml или cfc, доступный онлайн для реализации функции perlin (я не знаю, смогу ли я сам перевести что-то с другого языка)?Или же самый простой способ - установить и использовать что-то вроде ImageMagick для генерации / чтения файла изображения через cfexecute?

Что я пробовал

Сначала я попытался преобразоватькод 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.Я понятия не имел, что означает «градиент».Я предполагаю, что это математическая функция, но я не уверен, как реализовать это здесь.Мои поиски в Google продолжают давать мне другой код, а также некоторые объяснения, которые не являются интуитивно понятными для меня.

В этот момент использование IM становилось все более привлекательным.Это кажется более мощным, и я просто избегал этого, поскольку выяснить это и иметь еще одну вещь для установки на каждом шаге сервера казалось более трудоемким, чем иметь что-то в коде.Поскольку кодовый подход оказался более сложным, чем я ожидал, я взял перерыв, чтобы попытаться сфокусироваться на IM.

Для этого я начал с создания засеянного плазмы или фрактального холста, который отлично работал.Затем я попробовал много разных способов извлечения информации для каждого пикселя с ограниченным успехом:

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

Так что мой лучший метод до сих пор - 27-й, чтобы получить данные для 400 пикселей, и это ничего не делать с этими данными.Если мне нужно обработать изображение с разрешением 160 тыс. Пикселей (400х400) в реальном сценарии, это сработает примерно за 3 часа разгрузки моего процессора.Итак, если мне нужно 3 карты (для высоты, влажности и температуры), это ... нецелесообразно.

1 Ответ

0 голосов
/ 14 декабря 2018

Мне не удалось найти решение, которым я полностью доволен в плане эффективности, но у меня не хватило времени, чтобы решить эту проблему и двигаться дальше.Я, вероятно, вернусь в будущем для оптимизации, но сейчас у меня есть решение, которое работает, хотя и медленно.

За отличный ответ Марка Сетчелла в https://stackoverflow.com/a/26629083/762721, Я обнаружил, что, на удивлениесамый эффективный подход к моей проблеме - это сгенерировать фрактал с помощью Image Magic, использовать IM, чтобы записать всю информацию о цвете в файл, а затем использовать 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)>

Мне удалось запустить его на изображении размером 250 тыс. Пикселей (500x500) за 7,1 минуты, что почти в 40 раз быстрее, чем моя попытка получить информацию о пикселях.непосредственно.Я думаю, что есть много возможностей как для оптимизации, так и для проверки, чтобы избежать ошибок, и как только я немного его затяну, я вернусь и обновлю этот ответ.

Пока, используя это, для генерации 3 500x500 изображенийРазобрать информацию и записать ее в базу данных можно за 30 минут.Который пока неоптимален, но практичен.

...