YOUR ACCOUNT

Login or Register to post new topics or replies
David Roberson
Artist
Posts: 404
Filters: 36
Greetings fellow script authors!

As we all know, writing a script can be a daunting task. Whether you're an experienced programmer or a novice, there are always hurdles to overcome. Sometimes you may get stuck on a particular section of code, or maybe you're having trouble getting your script to work the way you want it to. Fear not, for this forum thread is dedicated to providing assistance with completing your work-in-progress scripts.

Here's how it works:

Post your script project in the comments section. Be sure to provide as much detail as possible, including what the script is supposed to do, what language it's written in (if, for example, you are trying to convert some python into lua), and what specific issues you're having trouble with.

Other members of the community will read your post and offer their assistance. They may provide suggestions on how to fix the issue, offer code snippets that can help, or even offer to collaborate with you on the project.

If you're feeling generous, take a look at other people's projects and offer your own expertise. You never know when you might be able to help someone else out.

A few ground rules:

Be respectful of others. This is a space for constructive criticism and collaboration, not negativity.

Do not post any malicious code or attempt to exploit vulnerabilities in others' code.

Keep the discussion focused on script debugging and development.

Now, I know the FF Forum are pretty lightly trafficked these days, but I'd like to encourage a bit more interest in and activity on them. I am in the process of expanding my Lua scripting knowledge, recently with the help of ChatGPT. I've found it a good source of information that is difficult to find just searching online. It's a bit of work to get what you're looking for there, as well, so I plan to collect what insights I can and compile them in a more accessible form to share here. So, I'll be posting whatever I can as I go. I hope to lure in a few more with an interest in or more experience with scripting.

With that said, I invite you all to start posting your script projects and let's get to work!
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I'll get the ball rolling with this: I converted Simplex noise written in java to lua and found myself caught on a potential out of bounds error being reported as an attempt to index a `nil` value. I've commented out most of the code and focused in on one point, where I could no longer adequately test the problem. I've put a couple day into this, so it's time to put it out there for someone else to look at it. It could be obvious to a more experienced author what I'm missing. If you give it a look, you have my gratitude!

Code

--
-- simplex noise proto.lua
-- v.0.0.1 revision and testing
-- generates simplex noise in 2d, 3d and 4d
-- source: https://weber.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
-- used in filter forge map script component

--[[
   Simplex noise is a type of gradient noise that is used for generating natural-looking textures, terrain,
   and other procedural content.
   A speed-improved simplex noise algorithm for 2D, 3D and 4D in Lua.

   Based on example code by Stefan Gustavson (stegu@itn.liu.se).
   Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).
   Lua conversion by David Bryan Roberson (https://github.com/eyeofparadox) in 2023
   Better rank ordering method for 4D by Stefan Gustavson in 2012.

   This could be speeded up even further, but it's useful as it is.

   Version 2012-03-09-2023

   This code was placed in the public domain by its original author,
   Stefan Gustavson. You may use it as you see fit, but attribution is appreciated.
--]]

function prepare()
   -- inputs and precalculation.
   noiseScale = 50
   noiseOctaves = 4
   noiseLacunarity = 2.0
   noisePersistence = 0.5

   grad3 = {
      {1,1,0},{-1,1,0},{1,-1,0},{-1,-1,0},
      {1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1},
      {0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1}
   }

   grad4 = {
      {0, 1, 1, 1},   {0, 1, 1, -1},   {0, 1, -1, 1},   {0, 1, -1, -1},
      {0, -1, 1, 1}, {0, -1, 1, -1}, {0, -1, -1, 1}, {0, -1, -1, -1},
      {1, 0, 1, 1},   {1, 0, 1, -1},   {1, 0, -1, 1},   {1, 0, -1, -1},
      {-1, 0, 1, 1}, {-1, 0, 1, -1}, {-1, 0, -1, 1}, {-1, 0, -1, -1},
      {1, 1, 0, 1},   {1, 1, 0, -1},   {1, -1, 0, 1},   {1, -1, 0, -1},
      {-1, 1, 0, 1}, {-1, 1, 0, -1}, {-1, -1, 0, 1}, {-1, -1, 0, -1},
      {1, 1, 1, 0},   {1, 1, -1, 0},   {1, -1, 1, 0},   {1, -1, -1, 0},
      {-1, 1, 1, 0}, {-1, 1, -1, 0}, {-1, -1, 1, 0}, {-1, -1, -1, 0}
   }

   p = {
      151,160,137,91,90,15,
      131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
      190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
      88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,
      77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
      102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,
      135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,
      5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
      223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
      129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,
      251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,
      49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,
      138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
   }

   n = #p

   perm = {}
   -- initialize permutation table with random values
   for i=1,256 do
      perm[i] = math.random(0,255)
   end

   -- extend the permutation table to avoid overflow
   for i=257,512 do
      perm[i] = perm[i - 256]
   end

   permMod12 = {}
   -- initialize permMod12 table with 0 values
   for i = 1, 512 do
      permMod12[i] = 0
   end
   
   for i=1,512 do
      permMod12[i] = perm[i] % 12
   end

   -- update grad3 and grad4 with the new permutation table
   -- for i=1,256 do
      -- grad3[i+1] = {grad3[perm[i+1] % 12 + 1][1], grad3[perm[i+1] % 12 + 1][2], grad3[perm[i+1] % 12 + 1][3]}
      -- grad4[i+1] = {grad4[perm[i+1] % 32 + 1][1], grad4[perm[i+1] % 32 + 1][2], grad4[perm[i+1] % 32 + 1][3], grad4[perm[i+1] % 32 + 1][4]}
   -- end

   F2 = 0.5*(math.sqrt(3.0)-1.0)
   G2 = (3.0-math.sqrt(3.0))/6.0
   F3 = 1.0/3.0
   G3 = 1.0/6.0
   F4 = (math.sqrt(5.0)-1.0)/4.0
   G4 = (5.0-math.sqrt(5.0))/20.0
end;


function get_sample(x, y)
               if x < 0 or x > 1 or y < 0 or y > 1 then
                  return 0.5, 0.5, 0.5, 1
               end
   local xin, yin = x/noiseScale, y/noiseScale
   local noise2D = get_2d_noise(xin, yin, noiseOctaves, noiseLacunarity, noisePersistence)
   -- local noise3D = 0 -- get_3d_noise(xin, yin, 0, noiseOctaves, noiseLacunarity, noisePersistence)
   -- local noise4D = 0 -- get_4d_noise(xin, yin, 0, 0, noiseOctaves, noiseLacunarity, noisePersistence)

   local r = noise2D
   -- local g = noise3D
   -- local b = noise4D
   local a = 1

   -- return r, g, b, a
   return r, 0, 0, a
   -- return 0, g, 0, a
   -- return 0, 0, b, a
end

function test_variables(r, g, b, a)
  local variables = {r, g, b, a}
  local nil_values = false
  
  for i=1, 4 do
    if variables[i] == nil then
      variables[i] = 0
      nil_values = true
    elseif variables[i] ~= nil then
      variables[i] = variables[i]
    else
      variables[i] = 1
      nil_values = true
    end
  end
  
  if nil_values then
    return unpack(variables)
  end
end

--[[
function grad3(...)
   local args = {...}
   for i, x in ipairs(args) do
      args[i] = x % n
   end
   local a = args[1] + args[2] + args[3]
   local gi = permMod12[a % n]
   -- for i in `grad3`
   if grad3[i] == nil then
     return 1, 0, 1, 1
   end
   return dot({grad3[gi][1], grad3[gi][2], grad3[gi][3]}, args[1] - grad3[a % 12 + 1][1], args[2] - grad3[a % 12 + 1][2], args[3] - grad3[a % 12 + 1][3])
end

function grad4(...)
   local args = {...}
   for i, x in ipairs(args) do
      args[i] = x % n
   end
   local a = args[1] + args[2] + args[3] + args[4]
   local gi = permMod12[a % n]
   -- for i in `grad4`
   if grad4[i] == nil then
     return 1, 0, 1, 1
   end
   return dot({grad4[gi][1], grad4[gi][2], grad4[gi][3], grad4[gi][4]}, args[1] - grad4[a % 32 + 1][1], args[2] - grad4[a % 32 + 1][2], args[3] - grad4[a % 32 + 1][3], args[4] - grad4[a % 32 + 1][4])
end
]]--

function fastfloor(x)
   local xi = math.floor(x)
   return (x < xi) and (xi-1) or xi
end

function dot(grd, x, y)
   return (grd.x*x) + (grd.y*y) -- debug "Script error (line 141) : attempt to index a nil value (local 'grd')" >>> called from: lines 215,222,229
end

-- function dot(grd, x, y, z)
   -- return (grd.x*x) + (grd.y*y) + (grd.z*z)
-- end

-- function dot(grd, x, y, z, w)
   -- return (grd.x*x) + (grd.y*y) + (grd.z*z) + (grd.w*w)
-- end

function get_2d_noise(xin, yin)
   local n0, n1, n2
   
   local s = (xin+yin)*F2
   local i = fastfloor(xin+s)
   local j = fastfloor(yin+s)
   local t = (i+j)*G2
   local x0 = i-t
   local y0 = j-t
   local x0 = xin-x0
   local y0 = yin-y0
   
   local i1, j1
   if x0>y0 then
      i1 = 1
      j1 = 0
   else
      i1 = 0
      j1 = 1
   end
   
   local x1 = x0 - i1 + G2
   local y1 = y0 - j1 + G2
   local x2 = x0 - 1.0 + 2.0 * G2
   local y2 = y0 - 1.0 + 2.0 * G2
   
   local ii = (i + 1) & 255
   local jj = (j + 1) & 255
   test_variables(ii,jj)

   local gi0 = permMod12[ii+perm[jj]]
   local gi1 = permMod12[ii+i1+perm[jj+j1]]
   local gi2 = permMod12[ii+1+perm[jj+1]]
   test_variables(gi0,gi1,gi2)
   
   local t0 = 0.5 - x0*x0-y0*y0
   if t0<0 then
      n0 = 0.0
   else
      t0 = t0 * t0
      n0 = t0 * t0 * dot(grad3[gi0], x0, y0) -- <<< grad3[gi0],grad3[gi1],grad3[gi2] may be `nil`: points at lines 206-208 again
   end
   local t1 = 0.5 - x1*x1-y1*y1
   if t1<0 then
      n1 = 0.0
   else
      t1 = t1 * t1
      n1 = t1 * t1 * dot(grad3[gi1], x1, y1)
   end
   local t2 = 0.5 - x2*x2-y2*y2
   if t2<0 then
      n2 = 0.0
   else
      t2 = t2 * t2
      n2 = t2 * t2 * dot(grad3[gi2], x2, y2)
   end
   
   return  (n0 + n1 + n2)
   -- return 70.0 * (n0 + n1 + n2)
end

   --[[
function get_3d_noise(xin, yin, zin)
   local n0, n1, n2, n3
   local s = (xin+yin+zin)*F3
   local i = math.floor(xin+s)
   local j = math.floor(yin+s)
   local k = math.floor(zin+s)
   local t = (i+j+k)*G3
   local x0 = i-t
   local y0 = j-t
   local z0 = k-t
   local x0 = xin-x0
   local y0 = yin-y0
   local z0 = zin-z0
   local i1, j1, k1
   local i2, j2, k2
   if x0>=y0 then
      if y0>=z0 then
         i1=1; j1=0; k1=0; i2=1; j2=1; k2=0
      elseif x0>=z0 then
         i1=1; j1=0; k1=0; i2=1; j2=0; k2=1
      else
         i1=0; j1=0; k1=1; i2=1; j2=0; k2=1
      end
   else -- x0<y0
      if y0<z0 then
         i1=0; j1=0; k1=1; i2=0; j2=1; k2=1
      elseif x0<z0 then
         i1=0; j1=1; k1=0; i2=0; j2=1; k2=1
      else
         i1=0; j1=1; k1=0; i2=1; j2=1; k2=0
      end
   end
   local x1 = x0 - i1 + G3
   local y1 = y0 - j1 + G3
   local z1 = z0 - k1 + G3
   local x2 = x0 - i2 + 2.0*G3
   local y2 = y0 - j2 + 2.0*G3
   local z2 = z0 - k2 + 2.0*G3
   local x3 = x0 - 1.0 + 3.0*G3
   local y3 = y0 - 1.0 + 3.0*G3
   local z3 = z0 - 1.0 + 3.0*G3
   local ii = (i + 1) & 255
   local jj = (j + 1) & 255
   local kk = (k + 1) & 255
   -- in `get_3d_noise`
   if not ii or jj or kk then
      return 0, 1, 0, 1
   end
   -- local gi0 = permMod12[ii+perm[jj+perm[kk+1] ]
   local gi1 = permMod12[ii+i1+perm[jj+j1+perm[kk+k1+1] ]
   local gi2 = permMod12[ii+i2+perm[jj+j2+perm[kk+k2+1] ]
   local gi3 = permMod12[ii+1+perm[jj+1+perm[kk+1+1] ]
   local t0 = 0.6 - x0*x0 - y0*y0 - z0*z0
   if t0<0 then n0 = 0.0 else
      t0 = t0 * t0
      n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0)
   end
   local t1 = 0.6 - x1*x1 - y1*y1 - z1*z1
   if t1<0 then n1 = 0.0 else
      t1 = t1 * t1
      n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1)
   end
   local t2 = 0.6 - x2*x2 - y2*y2 - z2*z2
   if t2<0 then n2 = 0.0 else
      t2 = t2 * t2
      n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2)
   end
   local t3 = 0.6 - x3*x3 - y3*y3 - z3*z3
   if t3<0 then n3 = 0.0 else
      t3 = t3 * t3
      n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3)
   end
   return 32.0*(n0 + n1 + n2 + n3)
end

function get_4d_noise(x, y, z, w)
   local n0, n1, n2, n3, n4
   local s = (x + y + z + w) * F4
   local i = math.floor(x + s)
   local j = math.floor(y + s)
   local k = math.floor(z + s)
   local l = math.floor(w + s)
   local t = (i + j + k + l) * G4
   local x0 = i - t -- unskew the cell origin back to (x,y,z,w) space
   local y0 = j - t
   local z0 = k - t
   local w0 = l - t
   local x0 = x - x0
   local y0 = y - y0
   local z0 = z - z0
   local w0 = w - w0
   local rankx = 0
   local ranky = 0
   local rankz = 0
   local rankw = 0
   if x0 > y0 then
      rankx = rankx + 1
   else
      ranky = ranky + 1
   end
   if x0 > z0 then
      rankx = rankx + 1
   else
      rankz = rankz + 1
   end
   if x0 > w0 then
      rankx = rankx + 1
   else
      rankw = rankw + 1
   end
   if y0 > z0 then
      ranky = ranky + 1
   else
      rankz = rankz + 1
   end
   if y0 > w0 then
      ranky = ranky + 1
   else
      rankw = rankw + 1
   end
   if z0 > w0 then
      rankz = rankz + 1
   else
      rankw = rankw + 1
   end
   local i1, j1, k1, l1
   local i2, j2, k2, l2
   local i3, j3, k3, l3
   i1 = (rankx >= 3) and 1 or 0
   j1 = (ranky >= 3) and 1 or 0
   k1 = (rankz >= 3) and 1 or 0
   l1 = (rankw >= 3) and 1 or 0
   i2 = (rankx >= 2) and 1 or 0
   j2 = (ranky >= 2) and 1 or 0
   k2 = (rankz >= 2) and 1 or 0
   l2 = (rankw >= 2) and 1 or 0
   i3 = (rankx >= 1) and 1 or 0
   j3 = (ranky >= 1) and 1 or 0
   k3 = (rankz >= 1) and 1 or 0
   l3 = (rankw >= 1) and 1 or 0
   local x1 = x0 - i1 + G4
   local y1 = y0 - j1 + G4
   local z1 = z0 - k1 + G4
   local w1 = w0 - l1 + G4
   local x2 = x0 - i2 + 2.0*G4
   local y2 = y0 - j2 + 2.0*G4
   local z2 = z0 - k2 + 2.0*G4
   local w2 = w0 - l2 + 2.0*G4
   local x3 = x0 - i3 + 3.0*G4
   local y3 = y0 - j3 + 3.0*G4
   local z3 = z0 - k3 + 3.0*G4
   local w3 = w0 - l3 + 3.0*G4
   local x4 = x0 - 1.0 + 4.0*G4
   local y4 = y0 - 1.0 + 4.0*G4
   local z4 = z0 - 1.0 + 4.0*G4
   local w4 = w0 - 1.0 + 4.0*G4
   local ii = (i + 1) & 255
   local jj = (j + 1) & 255
   local kk = (k + 1) & 255
   local ll = (l + 1) & 255
   -- in `get_4d_noise`
   if not ii or jj or kk or ll then
      return 0, 0, 1, 1
   end
   local gi0 = perm[ii+perm[jj+perm[kk+perm[ll] ] ] ] & 31
   local gi1 = perm[ii+i1+perm[jj+j1+perm[kk+k1+perm[ll+l1] ] ] ] & 31
   local gi2 = perm[ii+i2+perm[jj+j2+perm[kk+k2+perm[ll+l2] ] ] ] & 31
   local gi3 = perm[ii+i3+perm[jj+j3+perm[kk+k3+perm[ll+l3] ] ] ] & 31
   local gi4 = perm[ii+1+perm[jj+1+perm[kk+1+perm[ll+1] ] ] ] & 31
   local t0 = 0.6 - x0*x0 - y0*y0 - z0*z0 - w0*w0
   if t0 < 0 then
      n0 = 0.0
   else
      t0 = t0 * t0
      n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0);
      -- n0 = t0 * t0 * (x0 * grad4[gi0].x + y0 * grad4[gi0].y + z0 * grad4[gi0].z + w0 * grad4[gi0].w)
   end
   local t1 = 0.6 - x1*x1 - y1*y1 - z1*z1 - w1*w1
   if t1 < 0 then
      n1 = 0.0
   else
      t1 = t1 * t1
      n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1);
      -- n1 = t1 * t1 * (x1 * grad4[gi1].x + y1 * grad4[gi1].y + z1 * grad4[gi1].z + w1 * grad4[gi1].w)
   end
   local t2 = 0.6 - x2*x2 - y2*y2 - z2*z2 - w2*w2
   if t2 < 0 then
      n2 = 0.0
   else
      t2 = t2 * t2
      n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2);
      -- n2 = t2 * t2 * (x2 * grad4[gi2].x + y2 * grad4[gi2].y + z2 * grad4[gi2].z + w2 * grad4[gi2].w)
   end
   local t3 = 0.6 - x3*x3 - y3*y3 - z3*z3 - w3*w3
   if t3 < 0 then
      n3 = 0.0
   else
      t3 = t3 * t3
      n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3);
      -- n3 = t3 * t3 * (x3 * grad4[gi3].x + y3 * grad4[gi3].y + z3 * grad4[gi3].z + w3 * grad4[gi3].w)
   end
   local t4 = 0.6 - x4*x4 - y4*y4 - z4*z4 - w4*w4
   if t4 < 0 then
      n4 = 0.0
   else
      t4 = t4 * t4
      n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4);
      -- n4 = t4 * t4 * (x4 * grad4[gi4].x + y4 * grad4[gi4].y + z4 * grad4[gi4].z + w4 * grad4[gi4].w)
   end
   return 27.0 * (n0 + n1 + n2 + n3 + n4)
end
   ]]--

local grad = {
   x = 0, y = 0, z = 0, w = 0
}

function grad:new(x, y, z)
   local obj = {
      x = x, y = y, z = z
   }
   setmetatable(obj, self)
   self.__index = self
   return obj
end

function grad:new(x, y, z, w)
   local obj = {
      x = x, y = y, z = z, w = w
   }
   setmetatable(obj, self)
   self.__index = self
   return obj
end
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I had a thought, but it escaped me, Oh, well...
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Hmm. I have a long post written up with formatting tags, but it doesn't show up when I post it here. I tried to post it again, but nothing shows. Let's see if breaking it up a bit helps...

Nope. No luck.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Since I've been having problems with proper indexing and `nil` values in my 2-4D Simplex and Perlin noise scripts, I took a day off to do a breakdown of the `perlin` noise function I'm using in my spherical Perlin noise, describing what each part does:

`perlin`: This section of the `perlin:noise` function initializes the empty `perlin` table and its empty `p` field using the syntax `perlin = {}`, `perlin.p = {}`., the latter of which is used to store 512 pseudorandom gradient values. In `perlin.p = {}`, `p` is a field (also called a key or a member) of the `perlin` table, which is being assigned a new empty table `{}` as its value. So `p` is a field that consists of a table. If you have a table `perlin` containing three tables `p1`, `p2`, `p3`, the fields should be accessed as `perlin.p1`, `perlin.p2`, `perlin.p3`.
Code

   perlin = {}
   perlin.p = {}
                                                                                             .

Next, an arbitrary seed value is obtained using the `get_intslider_input()` function of the Filter Forge Scripting API. This value is used to seed the random number generator using `math.randomseed()`.
Code

   math.randomseed(get_intslider_input(SEED))
                                                                                             .

A `for` loop is then used to fill the `perlin.p` table with 256 randomly generated values between 0 and 255. These values are generated using the `math.random()` function.
Code

   for i = 0, 255 do
      perlin.p[i] = math.random(255)
      perlin.p[256 + i] = perlin.p[i]
   end
                                                                                             .

`function perlin:noise(x, y, z)`: The main function that returns the noise value for a given point (x,y,z). This section of the `perlin:noise` function takes in 3 input values: `x`, `y`, and `z`. It calculates the noise value at the given coordinate in 3D space. Here are the breakdowns of the operations:

The `y` and `z` values are set to 0 if they are not provided as input.
Code

   y = y or 0; z = z or 0 -- if y or z are not provided, set them to 0.
                                                                                             .

The function calculates the "unit cube" that the point will be located in by using the `math.floor` function to round down the input coordinates to integers and then performing a bitwise AND operation with 255 (which is equivalent to taking the modulo 256). This is done for each coordinate (x, y, and z) to obtain the indices of the cube vertices surrounding the input point.
Code

   local xi = bit32.band(math.floor(x), 255);
   local yi = bit32.band(math.floor(y), 255);
   local zi = bit32.band(math.floor(z), 255)
                                                                                             .

After calculating the unit cube that the point will be located in, the function calculates the relative positions of the input point within the unit cube by subtracting the floored values fr om the input values. This gives `x`, `y`, and `z` values in the range [0, 1] that represent the position of the input point within the unit cube.
Code

   local xf = x - math.floor(x);
   local yf = y - math.floor(y);
   local zf = z - math.floor(z)
                                                                                             .

The fractional part of the coordinates of the point within the unit cube are calculated by subtracting the floored (integer part) coordinates fr om the original coordinates.
Code

   x = x - math.floor(x);
   y = y - math.floor(y);
   z = z - math.floor(z)
                                                                                             .

The `fade` function is applied to each of `x`, `y`, and `z` to smooth the result. The `fade` function is a 6th-degree polynomial that interpolates smoothly between 0 and 1.
Code

   local u = self.fade(x); local v = self.fade(y); local w = self.fade(z)
                                                                                             .

The function then declares several variables that will be used later: `u`, `v`, and `w` are the smoothed `x`, `y`, and `z` values, respectively. `p` is a lookup table that stores the permutation of 256 integers used in the calculation of the `noise` function. The remaining variables (`A`, `AA`, `AB`, etc.) will hold the pseudorandom gradient vectors corresponding to the 8 unit cube vertices surrounding the input point.

Note that the function has not yet calculated the gradient vectors or the noise value itself. These will be calculated in the following steps of the function.

We can now calculate the dot product of each corner vector and the distance vector from the point to that corner. We can also use the fade curve to blend the contributions of each corner vector together.

In this section of the `perlin:noise` function, the eight corner points of the unit cube surrounding the input point are identified using a hash function. The operations involved are:

Retrieve the permutation table from the `self` table.
Code

   local p = self.p:
                                                                                             .

Declare 14 local variables to store the values of the corner points.
Code

   local A, AA, AB, AAA, ABA, AAB, ABB, B, BA, BB, BAA, BBA, BAB, BBB:

   A = p[xi] + yi
      -- retrieve the value of the permutation table at the position [xi],
      -- and add yi to it.
      -- Store the result in A.
   AA = p[A] + zi
      -- retrieve the value of the permutation table at the position [A],
      -- and add zi to it.
      -- Store the result in AA.
   AB = p[A + 1] + zi
      -- retrieve the value of the permutation table at the position [A+1],
      -- and add zi to it.
      -- Store the result in AB.
   AAA = p[AA]
      -- retrieve the value of the permutation table at the position [AA].
      -- Store the result in AAA.
   ABA = p[AB]
      -- retrieve the value of the permutation table at the position [AB].
      -- Store the result in ABA.
   AAB = p[AA + 1]
      -- retrieve the value of the permutation table at the position [AA+1].
      -- Store the result in AAB.
   ABB = p[AB + 1]
      -- retrieve the value of the permutation table at the position [AB+1].
      -- Store the result in ABB.

   B = p[xi + 1] + yi
      -- retrieve the value of the permutation table at the position [xi+1],
      -- and add yi to it.
      -- Store the result in B.
   BA = p[B] + zi
      -- retrieve the value of the permutation table at the position [B],
      -- and add zi to it.
      -- Store the result in BA.
   BB = p[B + 1] + zi
      -- retrieve the value of the permutation table at the position [B+1],
      -- and add zi to it.
      -- Store the result in BB.
   BAA = p[BA]
      -- retrieve the value of the permutation table at the position [BA].
      -- Store the result in BAA.
   BBA = p[BB]
      -- retrieve the value of the permutation table at the position [BB].
      -- Store the result in BBA.
   BAB = p[BA + 1]
      -- retrieve the value of the permutation table at the position [BA+1].
      -- Store the result in BAB.
   BBB = p[BB + 1]
      -- retrieve the value of the permutation table at the position [BB+1].
      -- Store the result in BBB.
                                                                                             .

Overall, these operations perform a series of table lookups to retrieve the values of the corner points of the unit cube surrounding the input point. These corner points are used in the subsequent interpolation steps to calculate the final `perlin:noise` value.

This section of the `perlin:noise` function performs the final step of calculating the noise value for the input coordinates by taking the weighted average between all 8 unit cube coordinates surrounding the input coordinate.

`self.lerp`: The function is used for linear interpolation. It takes two values `a` and `b`, and a weight `w` and returns the linearly interpolated value between `a` and `b` based on the weight `w`. The nested `self.lerp` functions are used to calculate the weighted average between all 8 unit cube coordinates. The inputs to these functions are the 8 unit cube coordinates and the input coordinate. These 8 unit cube coordinates are labeled with different variable names that correspond to their position in the unit cube, such as `AAA`, `AAB`, `ABA`, etc. Inside each `self.lerp` function, there are two more nested `self.lerp` functions, which are used to interpolate the gradient vectors at each of the 4 corners of the unit cube. The gradient vectors are calculated using the `self:grad` function, which takes a hash value and the input coordinates, and returns a gradient vector.
Code

   return
      self.lerp(w,
         self.lerp(v,
            self.lerp(u,
               self:grad(AAA, x, y, z),
               self:grad(BAA, x - 1, y, z)
            ),
            self.lerp(u,
               self:grad(ABA, x, y - 1, z),
               self:grad(BBA, x - 1, y - 1, z)
            )
         ),
         self.lerp(v,
            self.lerp(u,
               self:grad(AAB, x, y, z - 1),
               self:grad(BAB, x - 1, y, z - 1)
            ),
            self.lerp(u,
               self:grad(ABB, x, y - 1, z - 1),
               self:grad(BBB, x - 1, y - 1, z - 1)
            )
         )
      )
                                                                                             .

Overall, the final output of this section is a weighted average of the gradient vectors at the 8 corners of the unit cube, which is the `perlin:noise` value for the input coordinates.

This section of the `perlin:noise` function defines a lookup table for the `dot_product` function and a gradient function that finds the dot product between a pseudorandom gradient vector and the vector from an input coordinate to a unit cube vertex.

`perlin.dot_product`: This table contains 16 entries, each corresponding to one of the 16 possible bit patterns that can be formed by three bits. Each entry is a function that takes three arguments `x`, `y`, and `z`, which represent the coordinates of a point, and returns the dot product between the pseudorandom gradient vector and the vector from the input point to a unit cube vertex in a particular direction. The directions are determined by the bit pattern associated with the function in the table.
Code

   perlin.dot_product = {
      [0x0] = function(x, y, z) return x + y end,
      [0x1] = function(x, y, z) return -x + y end,
      [0x2] = function(x, y, z) return x - y end,
      [0x3] = function(x, y, z) return -x - y end,
      [0x4] = function(x, y, z) return x + z end,
      [0x5] = function(x, y, z) return -x + z end,
      [0x6] = function(x, y, z) return x - z end,
      [0x7] = function(x, y, z) return -x - z end,
      [0x8] = function(x, y, z) return y + z end,
      [0x9] = function(x, y, z) return -y + z end,
      [0xA] = function(x, y, z) return y - z end,
      [0xB] = function(x, y, z) return -y - z end,
      [0xC] = function(x, y, z) return y + x end,
      [0xD] = function(x, y, z) return -y + z end,
      [0xE] = function(x, y, z) return y - x end,
      [0xF] = function(x, y, z) return -y - z end
   }
                                                                                             .

`perlin:grad(hash, x, y, z)`: This method takes four arguments: `hash`, an integer value between 0 and 255; and `x`, `y`, and `z`, which represent the coordinates of a point. The method computes the dot product between the pseudorandom gradient vector associated with the given `hash` value and the vector from the input point to a unit cube vertex. The `hash` value determines which gradient vector to use, and the direction of the vector is determined by the dot product function corresponding to the last three bits of the hash value. The method returns the computed dot product.
Code

   function perlin:grad(hash, x, y, z)
      return self.dot_product[bit32.band(hash, 0xF)](x, y, z)
   end
                                                                                             .

`perlin.fade(t)`: The `fade` function is used to smooth the final output of the `perlin:noise` function. It takes a single parameter `t`, which represents the input value to be smoothed. The function uses a quintic (degree-5) polynomial to gradually transition from 0 to 1 as `t` increases from 0 to 1. This helps to reduce the appearance of sharp edges and discontinuities in the output of the `perlin:noise` function.
Code

   function perlin.fade(t)
      return t * t * t * (t * (t * 6 - 15) + 10)
   end
                                                                                             .

`perlin.lerp(t, a, b)`: The `lerp` function is a linear interpolation function that is used to combine the weighted average of the dot products of gradient vectors and the input coordinate. It takes three parameters: `t`, `a`, and `b`. The function returns the linear interpolation of `a` and `b` at the ratio of `t`, wh ere `t` ranges from 0 to 1. The resulting value is a weighted average of `a` and `b` wh ere `t` determines the weighting of the two values.
Code

   function perlin.lerp(t, a, b)
      return a + t * (b - a)
   end
                                                                                             .

`noise`, `dot_product`, `grad`, `fade` and `lerp` are all functions defined within the `perlin` table using the colon syntax (`[/B]perlin:function()`[/B]). This makes them methods of the `perlin` table, so they can be called using the `:`[/B] syntax (`[/B]perlin:method()`[/B]).

Doing an analysis like this is a big step in understanding the algorithms I need to use for scripting. I put a little extra effort into organizing and formatting my notes to share here. I thought people might find this sort of thing helpful, and it sort of fits in with the theme of this thread. If there's a positive response, I'll try to do more of these in cases wh ere I've gotten a script to working as intended.
  Details E-Mail
emme
Posts: 718
Filters: 8
As you mentioned, ChatGPT can be quite helpful with these things, not only for writing scripts and functions, but for explaining them as well. Also, check out shadertoy.com if you haven't already. The shaders are written in GLSL, but there are a lot of great examples to learn from. Good luck with scripting smile:)
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Yes, it's been really helpful (if occasionally frustrating) for improving my understanding of Lua. I've also found my way to shadertoy.com many times looking for ways to do certain things. I also recommend it, and ChatGPT can help convert the GLSL to Lua if you come across anything good there. Thanks, emme!
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
It's been a long time since I thought of posting here. The lack of discussion, while forgivable, has caused my to throw my scripting questions at ChatGPT or Copilot this past year. Today I had a chat I thought I'd share here. It might help others digging into the same topics, or even prompt some forum input.

Right. So, here you go:

Understanding Noise Generation Seeds (and then Diving Deeper)


I am working on noise generation scripts using Lua in Filter Forge. While I was at it, I found myself wondering what a seed exactly is. In terms of the noise generated, what does the seed do (break down the function in terms of it effects)?


Copilot: Certainly! Let's dive into the fascinating world of seeds in noise generation.

**What is a Seed?**
A seed, in the context of noise generation algorithms like Stable Diffusion (and even in other areas like procedural terrain generation), is simply a numerical value. It acts as the starting point for generating noise patterns. Here's the plain and simple breakdown:

1. **Seed Is Not the Noise Image Itself**: The seed itself is not the noise pattern you see in the final image. Instead, it's like the initial spark that sets the whole process in motion.

2. **Not All Parameters**: Contrary to misconceptions, the seed doesn't encapsulate all the parameters used for image generation. It's just the starting point, not the entire recipe.

3. **Not Tied to Specific Prompts or Characteristics**: The seed remains detached fr om specific text prompts, poses, clothing, backgrounds, or artistic styles. It's more about the underlying randomness.

**How Does It Work?**
When you provide a seed, the noise generator algorithm takes that number and uses it to create an initial noise pattern. But here's the interesting part: the noise isn't truly random. It's pseudo-random. In other words, given the same seed, the algorithm will reliably produce the same noise pattern every time. This repeatability opens up some cool possibilities:

- **Consistent Reproduction**: If you input the same seed, along with the same prompt and all the other parameters you used initially, you can reliably reproduce a generated image across multiple sessions. It's like having a secret recipe for noise!

- **Sharing and Tweaking**: Users can work off someone else's generated image by starting fr om the same seed, prompt, and parameters. Plus, minor tweaks—like adjusting the prompt or parameters—allow you to explore variations without drastically altering the overall composition.

- **Seed Characteristics**: Some seeds have been identified as having a higher probability of producing images with specific color palettes or compositions. Knowing these "lucky" seeds can give you a better chance of getting the output you desire.

So, in summary, seeds are like the cosmic dice roll that determines the initial noise, and fr om there, the algorithm weaves its magic to create the final image.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Heh. Guess that was too long to post. Rather than a series of clips, I'll just share the text for anyone interested. ^_^;

For the full transcript:
Understanding Noise Generation Seeds (and then Diving Deeper)
  Details E-Mail

Join Our Community!

Filter Forge has a thriving, vibrant, knowledgeable user community. Feel free to join us and have fun!

33,712 Registered Users
+19 new in 30 days!

153,534 Posts
+31 new in 30 days!

15,348 Topics
+72 new in year!

Create an Account

Online Users Last minute:

6 unregistered users.