YOUR ACCOUNT

Login or Register to post new topics or replies
David Roberson
Artist
Posts: 404
Filters: 36
A few weeks ago, I stumbled across an example Spherical Noise Map Generator using C++/libnoise. That got me to wondering if FF would be capable of doing it, too. I posted a Feature Request and emme helpfully supplied an example Map Script, pointing out that it could be further enhanced to multi-octave noise, and other cool things.

spherical_noise_v001.ffxml

I remembered one of the Script API I saved was for multi-octave perlin noise and went back to study it, for help making emme's example work along the same lines. Unfortunately, I'm pretty much a beginner with Lua, and it's been years since I've done any real coding, so I could not see a direct path to follow. It seems that the "multi-octave" portion of the script is already using a z-coordinate, so that struck me as a conflict even before I realized emme's 3-coordinate example sets up the variables in a very different way. As in, I barely understand the logic of what it's doing, so I can't follow the transition from my 2-coordinate multi-octave example to 3-coordinates.

Maybe somebody can help me with that. The API I'm referencing is: Script API - Multi-Octave Perlin.ffxml. This is going to be a learning project for me, and other features will come to mind, or variations along the same lines (I've mentioned exploring a way to do a Whorley noise spherical map generator, for example). I'm just not sure how much I have to study before I can figure this out on my own.

Whatever happens, I'll try to keep track of my progress here, for the benefit of anyone interested in the same challenge.
  Details E-Mail
emme
Posts: 718
Filters: 8
I added some features to the script: octaves, roughness/detail, distortion and noise-profile. This multi-octave implementation probably isn't exactly the "correct" way to do it - I just did it the quick and dirty way smile:)

I think there is also a 2D Worley noise example script in the Noise-API help section. Not sure how easy it'll be to convert into 3D, but my guess would be, not super difficult. I'll check it out at some point.

In the original thread, you mentioned texture baking in Blender - I'll answer it here. After creating a UV-sphere, create a new material in the shading editor and add some texturing. Next, add a new empty image texture (alt+n -> texture -> image texture) and click the New -button. In the render properties tab, select Cycles for your render engine and you should see a Bake tab below. Select the bake type ect. and click Bake. You can now use the baked texture on your object or export it to disk.

spherical_noise_v002.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Thank you, emme! I'll check out your added features now. I discovered pretty quickly that learning Lua is going to be a little harder than I first thought. The documentation I've found so far is a bit dry and quite a bit shy of tutorials I can pick up on quickly. I still think it's worth learning, and I hope the forums here will help with that. I already have a few ideas of what features I'd like the component to have, starting with the usual: probably map inputs for color, roughness, contrast, and possibly distortion, with scale (already there), angle, details (now added) and variation. I think I'd also like to be able to pan through the 3 coordinates interactively to explore and position the noise. I did pick up the whorley noise API, today. I'll be interested to see what you come up with--you'll probably figure it out way ahead of me smile:D .

As for Blender, the answer helps. I imagine I'd have already learned how to do it if I'd experimented with texture baking beyond learning that it could be done. The first time is always a bit daunting.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Emme, I'm no judge of best implementation, so I wouldn't criticize your roughness/detail and multi-octave method. It seems to work well enough for basic use. I can say that the difference between your method and the API example is greater than my analysis skills can parse. The multi-octave perlin example does a lot with the input values in the prepare() function to generate octave derived values used in the get_sample() function. The noise itself is ultimately used as the alpha matrix in a blend_normal() function that returns the composite foreground and background maps integration.

The map blending is not a high priority for me, when it comes to expanding on your script, since I'd have to supply spherically mapped inputs to get a usable result. Even though the same is true when it comes to mapped Roughness and Contrast, there are ways to mitigate their influence if I employ a circle curve gradient that masks out the top and bottom portions of the supplied maps. The effects I can get that way are useful in a lot of cases. Not that I'm getting any closer to implementing Roughness or Contrast in your script. I don't even have an example for how to do contrast adjustments, mathematically.

My scripting adventures may come to a quick end. It's looking like something I can't pick up on my own. I just can't find the kind of instruction or examples I can follow, and my math skills are probably not good enough to solve graphics problems without explicit examples. I'm only able to look at the script and understand what it's doing. I can't adapt it to something else. Sigh.

In any case, thanks again for your help, and your excellent examples.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I wrote a nice little post detailing my first update to emme's script, and it got discarded during preview as I was logged off the forum. Bother. Just to recap, what I remember, I found it easy to add offsets to the x, y and z coordinates via grayscale input. I tested it out with perlin noise inputs in seamless and non-seamless modes and the results were both seamless, so adding the values to the initial coordinate declarations worked fine. Obviously, the offsets can produce additional distortion, so extreme results are possible. I'm posting the script code for quick review, with the ffxml attached below.

I still need some tutoring or an example to follow up on adding Contrast or Roughness (independent of Details) to this. I just don't know the math for the first, and my example of the second is implemented very differently from emme's approach. I sort of understand what it's doing, but not well enough to adapt it.

Code
function prepare()
   oct_max = get_intslider_input(NOISE_OCTAVES)
   detail = 4 - get_slider_input(DETAIL) * 2 + 0.01
   scale = get_slider_input(NOISE_SCALE) * 10 + 1
   distort = get_slider_input(NOISE_DISTORT) *2
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   set_perlin_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   local v = 0
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   local s = 500
   local amp = 1
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) * scale + vosx
   local ny = math.sin(x) * math.sin(y) * scale + vosy
   local nz = math.cos(y) * scale + vosz
   for oct = 1, oct_max do
      local d1 = get_perlin_noise(nx+1,ny,nz,s) * distort
      local d2 = get_perlin_noise(nx+2,ny,nz,s) * distort
      local d3 = get_perlin_noise(nx+3,ny,nz,s) * distort
      if oct == 1 then v = get_perlin_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_perlin_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      s = s / 2
      amp = amp / detail
   end
   v = get_sample_curve(x,y,v,NOISE_PROFILE)
   return v,v,v,1
end;


Script API - Spherical Noise v.001.1.ffxml
  Details E-Mail
emme
Posts: 718
Filters: 8
Added mappable detail (or roughness) and distortion. The noise profile should work for mappable contrast, but I just tried mapping it, and it breaks the seamlessness for some reason. Could be that the curve gets sampled incorrectly with non-square images, I'll have to look into that. Anyways, contrast is easy to adjust after the script, so I just added a mapped tone curve for that. Let me know if works ok.

Like you mentioned, all the input maps need to be in the same spherical format in order to maintain seamlessness. Mapping with seamless 2D perlin won't be seamless on a sphere - even if it looks seamless in 2D.

Pretty cool to play with the xyz offsets you added btw. Really helps with visualizing the projection smile:)

spherical_noise_v003.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
A quick little progress update, here's some thoughts on Brightness/Contrast adjustment adapted fr om an online source. I revised the examples from the two articles I found into rough Lua code, without changing any of the values given. I only updated the variable names to fit in with other scripting I've dealt with. I've noted which values before the code blocks. I assume I'll need to convert the values for a 0-1 range before proceeding?

Image Adjustment links to the Articles (copyright © 2008, 2010, 2015 Francis G. Loch) included.

Brightness Adjustment

Adjusting the brightness of an image is one of the easiest image processing operations that can be done. All that is involved is adding the desired change in brightness to each of the red, green and blue color components.

In pseudo-code it would go something like this (unstated, but assuming value range of 0-255)(also assuming brightness is a slider value, so technically declared in prepare() and omitted here):

Code
function get_sample(x,y)
  local r, g, b, a = get_sample_map(x, y, COLOR)
  local nr = truncate(r + brightness)
  local ng = truncate(g + brightness)
  local nb = truncate(b + brightness)
  return nr, nb, ng, a
end;


Contrast Adjustment

The first step is to calculate a contrast correction factor which is given by the following formula (assuming the value in question is RGB in the 0-255 value range):

Code
   f = 259 * (c + 255) / 255 * (259 - c)


In order for the algorithm to function correctly the value for the contrast correction factor (F [or f, in the line above]) needs to be stored as a floating point number and not as an integer. The value C [c] in the formula denotes the desired level of contrast.

The next step is to perform the actual contrast adjustment itself. The following formula shows the adjustment in contrast being made to the red component of a color:

Code
   r = f * (r - 128) + 128


Translating the above formulas into pseudo-code would give something like this (assuming the value of contrast will be in the range of -255 to +255, wh ere negative values will decrease the amount of contrast and, conversely, positive values will increase the amount of contrast):

Code
  factor = (259 * (contrast + 255)) / (255 * (259 - contrast))

  function get_sample(x,y)
    local r, g, b, a = get_sample_map(x, y, COLOR)
    local nr = truncate(factor * (r  - 128) + 128)
    local ng = truncate(factor * (g - 128) + 128)
    local nb = truncate(factor * (b - 128) + 128)
    return nr, ng, nb, a
  end;


The function truncate() just ensures that the new values of red, green and blue are within the valid range of 0 to 255.

The function truncate() in pseudo-code (assuming value range of 0-255):

Code
  function truncate(value)
     If value < 0 Then value = 0
     If value > 255 Then value = 255
     Return value
  end;


As always, your thoughts are welcome. This is just a nudge in the direction of a -- hoped for -- mapped contrast control. That, and a mapped roughness control are my two main objectives for now.
  Details E-Mail
emme
Posts: 718
Filters: 8
You might be overthinking it smile:)

You can already do mapped contrast with existing components. Since the operation is not directional or size dependent , it won't break seamlessness - as long as you use a spherically seamless input maps. Check my last post.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I'm sure I am, emme. I've been known to do that. Got the stubbornness gene from both parents, I guess! I missed your update with the mapped details/roughness and distortion while I was working, so my update this time contains my own solution for mapped roughness. I played a little with the details formula to get in a little extra roughness at the high end. I'll post the code for those who just want a glance at the script, before the attached ffxml with my current experiments in it.

One last thought, how do you enter a tab inside the script editor? Or do you compose the script outside it and just paste in the final version when you're done?

Code
function prepare()
   oct_max = get_intslider_input(NOISE_OCTAVES)
--   detail = 4 - get_slider_input(NOISE_DETAIL) * 2 + 0.01 -- mapped alternate
--   scale = get_slider_input(NOISE_SCALE) * 10 + 1 -- mapped alternate
   distort = get_slider_input(NOISE_DISTORT) * 2
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   set_perlin_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   local v = 0
   local scale = get_sample_grayscale(x, y, SCALE) -- * 10 + 1 -- unsuitable range
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   local detail = 3.75 - get_sample_grayscale(x, y, DETAIL) * 2 + 0.01
   local s = 500
   local amp = 1
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) * scale + vosx
   local ny = math.sin(x) * math.sin(y) * scale + vosy
   local nz = math.cos(y) * scale + vosz
   for oct = 1, oct_max do
      local d1 = get_perlin_noise(nx+1,ny,nz,s) * distort
      local d2 = get_perlin_noise(nx+2,ny,nz,s) * distort
      local d3 = get_perlin_noise(nx+3,ny,nz,s) * distort
      if oct == 1 then v = get_perlin_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_perlin_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      s = s / 2
      amp = amp / detail
   end
   v = get_sample_curve(x,y,v,NOISE_PROFILE)
   return v,v,v,1
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Apart from adding mapped distortion to my working script, I have not made any new progress, so this may be the final version I'll end up using unless I come up with something new I need the script to do. I've wondered about working with multiple noise instances to be blended into the main result, in keeping with the recommendation to make one Map Script to do what you need rather than have multiple Map Scripts in one filter. Even so, I have to prototype the composite with regular components to figure out exactly what I'm trying to accomplish before working out the script to do it internally.

Like before, here's the current script code, with the attached ffxml below:

Code
function prepare()
   oct_max = get_intslider_input(NOISE_OCTAVES)
--   detail = 4 - get_slider_input(NOISE_DETAIL) * 2 + 0.01 -- mapped alternate
--   scale = get_slider_input(NOISE_SCALE) * 10 + 1 -- mapped alternate
--   distort = get_slider_input(NOISE_DISTORT) * 2 -- mapped alternate
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   set_perlin_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   local v = 0
   local scale = get_sample_grayscale(x, y, SCALE) -- * 10 + 1 -- unsuitable range
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   local detail = 3.75 - get_sample_grayscale(x, y, DETAIL) * 2 + 0.01
   local distortion = get_sample_grayscale(x, y, DISTORTION) * 2
   local s = 500
   local amp = 1
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) * scale + vosx
   local ny = math.sin(x) * math.sin(y) * scale + vosy
   local nz = math.cos(y) * scale + vosz
   for oct = 1, oct_max do
      local d1 = get_perlin_noise(nx+1,ny,nz,s) * distortion
      local d2 = get_perlin_noise(nx+2,ny,nz,s) * distortion
      local d3 = get_perlin_noise(nx+3,ny,nz,s) * distortion
      if oct == 1 then v = get_perlin_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_perlin_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      s = s / 2
      amp = amp / detail
   end
   v = get_sample_curve(x,y,v,NOISE_PROFILE)
   return v,v,v,1
end;


Script API - Spherical Noise.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Hey, emme. I've confirmed that mapped curves do break spherical seamlessness, unless their sources are spherically mapped. Even then, some curve utilities, like Repeat, still break it.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I had a system crash and lost my working ffxml, so I retrieved the one I posted here last. That's when I discovered it was also out of date. So, just a quick repost with the final tweaks, for anyone interested in the prototypes so far. Versions are noted inside, (most of my changes were to emme's v.002, so hers is the only v.003 script in it) so I dropped the version of the ffxml itself. The final change was making Distortion grayscale accept HDR values, since distortions in the +/-1 range are useful, and mapped values beyond it can be useful in some cases. I thought of adding strength sliders -- one for Distortion and a Percentage for the offsets -- to adjust the influence of mapped input, but decided that could be handled by adjustment components beforehand. It's easy enough to add if anyone feels they'd be useful, internally, in spite of being redundant when mapped inputs are not used.

Script API - Spherical Noise.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Oops! Pointed out the wrong bad guy. Repeat did not break seamlessness after all. It was an Offset mapping of Amplitude that got weird. It didn't really break seamlessness as populate the background with fractional offsets of the source noise. It technically doesn't break the seam at top and bottom, it just creates an unwanted tiling within the perimeter.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I've been losing my work file in spite of regular saves, so I'm posting the new version while I'm still in the Filter Editor. I've checked that it did save this time, so I might be right that Projects are messing up the file somehow, but this time I made sure I was closed out of any open Projects.

The change was simply adding Foreground/Background map inputs, with my noise assigned to Opacity, with an HDR option for the inputs. Like emme said, an easy thing to add once I decided it was still worth having. I'd almost forgotten it was one of my original goals!

Here's the script code, with the attached ffxml, below:
Code
function prepare()
   oct_max = get_intslider_input(NOISE_OCTAVES)
--   detail = 4 - get_slider_input(NOISE_DETAIL) * 2 + 0.01 -- mapped alternate
--   scale = get_slider_input(NOISE_SCALE) * 10 + 1 -- mapped alternate
--   distort = get_slider_input(NOISE_DISTORT) * 2 -- mapped alternate
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   set_perlin_noise_seed(get_intslider_input(NOISE_SEED))
   if (get_checkbox_input(HDR)) then
      hdr = true
   else
      hdr = false
   end
end;

function get_sample(x, y)
   local r, g, b, a = get_sample_map(x, y, BACKGROUND)
   local r2, g2, b2, a2 = get_sample_map(x, y, FOREGROUND)
   local v = 0
   local scale = get_sample_grayscale(x, y, SCALE) -- * 10 + 1 -- unsuitable range
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   local detail = 3.75 - get_sample_grayscale(x, y, DETAIL) * 2 + 0.01
   local distortion = get_sample_grayscale(x, y, DISTORTION) * 2
   local s = 500
   local amp = 1
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) * scale + vosx
   local ny = math.sin(x) * math.sin(y) * scale + vosy
   local nz = math.cos(y) * scale + vosz
   for oct = 1, oct_max do
      local d1 = get_perlin_noise(nx+1,ny,nz,s) * distortion
      local d2 = get_perlin_noise(nx+2,ny,nz,s) * distortion
      local d3 = get_perlin_noise(nx+3,ny,nz,s) * distortion
      if oct == 1 then v = get_perlin_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_perlin_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      s = s / 2
      amp = amp / detail
   end
   v = get_sample_curve(x,y,v,NOISE_PROFILE)
   local opacity = v
   r, g, b, a = blend_normal(r, g, b, a, r2, g2, b2, a2, opacity, hdr)
   return r, g, b, a
end;


Script API - Spherical Noise.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Since I lost what I was working on, once I finished reconstructing what I could still remember (last post), I thought I'd begin looking into other types of spherical noise. I quickly composed my own version of Spherical Uniform Noise and got it working in color and grayscale modes. Sadly, the results don't differ any from the API - Uniform Noise. You just get a uniform size noise, without polar distortion like I expected. Still, it was just a warm-up before trying to tackle something more challenging, like Cell Noise. I'm not yet sure if I can adapt the example to this, but at the moment it's the only kind I have an example for.

I'd be thrilled if anyone can provide (or post links to) scripts or coding samples for scripting/programming other types of Worley noise. I can't bug emme for everything (though some times I can't help myself smile:D)!

Just the code snip, this time:
Code
function prepare()
   set_noise_seed(get_intslider_input(SEED))
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   color = get_checkbox_input(COLOR)
end;

function get_sample(x, y)
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) -- * scale
   local ny = math.sin(x) * math.sin(y) -- * scale
   local nz = math.cos(y) -- * scale
   local v = get_noise(nx,ny,nz)
   local r = get_noise(nx,ny,nz+1)
   local g = get_noise(nx,ny,nz+2)
   local b = get_noise(nx,ny,nz+3)
   if color then
      return r,g,b,1
   else
      return v,v,v,1
   end
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Well, a little bit of progress is often followed by a lot of confusion. I quickly got lost trying to work out how to do Worley cells in 3D. I don't really know how to explain my confusion other than by commenting on the API script, and seeing if someone can point me back in the right direction. I also experimented, quasi-logically to see if I could at least make the 2D cells seamless, but there doesn't seem to be set_noise_seamless() or set_noise_seamless_region() functions to call.

Code
function prepare()
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2)
   set_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   -- following block generates spherical coordinates...
      -- local x = x * aspect * math.pi
      -- local y = y * math.pi
      -- local nx = math.cos(x) * math.sin(y) * scale
      -- local ny = math.sin(x) * math.sin(y) * scale
      -- local nz = math.cos(y) * scale
   -- this part gets replaced with worley noise algorhythm
      -- local v = get_perlin_noise(nx,ny,nz,500)
   local sx, sy = x / scale , y / scale -- add sz = z / scale?
   local cell_x, cell_y = math.floor(sx), math.floor(sy) -- add cell_z = math.floor(sz)?

   local offset_x, offset_y
   -- add offset_z -- no idea how this integrates with: for offset_z=-1,1 do ... add nest?
   -- in nest, add dz = cell_z + offset_z + get_noise(?) or replace z value in dx/dy get_noise with cell_z + offset_z?
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, 0)
         local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, 1)
         local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2) -- add in (sz - dz)^2 somehow? get cube root somehow?
         min_dist = math.min(dist, min_dist) -- not even a clue here
      end
   end

   local a = 1
   min_dist = 1.0 - min_dist
   return min_dist, min_dist, min_dist, a
   -- obviously, this is  changed from v to min_dist for each channel
      -- return v,v,v,1
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I've been scouring the web for help on Worley noise, but the examples are in different scripting or coding languages and are nowhere near as succinct and clear as the API script I got from the Noise API page on the FF site. I'll keep looking over what I find, but I'm getting more new questions than answers. For example, how to implement different distance formulas for Euclidean, Manhattan, Chebyshev (sp?), etc.

These are the key to the various types of Worley noise you can get. A great overview of this is in the following video: Coding in the Cabana 4: Worley Noise

The video mentions several online resources you'd come across searching the web, which I've seen (I can retrieve the links if prompted, but again, they're not terribly hard to find). The real trick is really understanding what these pages contain. It's really quite the rabbit hole. To those who have become knowledgeable about this stuff, how did you learn it? I don't think you can google your way to expertise. If there are more explicit tutorials and examples out there, they're not turning up in my searches. I will keep sharing anything I learn, as I go, however.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Well, my "analysis" of Worley noise all falls apart the instant I finally notice I can't reference the global value z... unlike the x,y args in the get_sample(), it simply doesn't exist. So obvious it slipped past me entirely that the z coordinate I'm thinking of is only introduced in the noise function.

So, yeah, just ignore that whole post.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
A little progress... I was partly on the right track with my analysis, after all. Once I provided a z coordinate, I was able to generate 3d Worley noise. It's still cartesian, rather than spherical, but the feature points used to calculate the distances are now arranged in x,y,z coordinate space.

Here's the code:
Code
function prepare()
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2)
   set_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   local z_coord = get_sample_grayscale(x, y, Z_COORDINATE)
   local sx, sy = x / scale , y / scale
   local sz = z_coord / scale
   local cell_x, cell_y = math.floor(sx), math.floor(sy)
   local cell_z = math.floor(sz)

   local offset_x, offset_y, offset_z
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)
            min_dist = math.min(dist, min_dist)
         end
      end
   end

   local a = 1
   min_dist = 1.0 - min_dist
   return min_dist, min_dist, min_dist, a
end;


Here's the output I got:

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
More progress! Once I had a bit more of a handle on 3d Worley noise (above), I was able to work out how to integrate emme's spherical approach. I'm not sure if "local nz = math.cos(y) * z" is really, technically, the right way to do this, but it works, with x,y remapped the default way. I was pretty surprised when it worked. I guess I'm starting to understand the math better, though I'm sure the research I've done has helped get it through my head.

Scaling needed a little adjusting, and the multiplier I went with doesn't zoom in quite as far as it could. I just liked seeing a few cells at the maximum Scale. I'll share the ffxml in my next post. I still need to figure out how to address the nth point as a variable, to get other permutations, i.e. 2nd closest, 3rd closest, etc. point. I also need to figure out how to apply the other distance formulas for Manhattan, Chebyshev, etc. That's where the other Worley type noises we're familiar come from, with formula variations. So, still work to do!

Here's the code:
Code
function prepare()
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2) * 5
   set_noise_seed(get_intslider_input(NOISE_SEED))
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
end;

function get_sample(x, y)
   local x = x * aspect * math.pi
   local y = y * math.pi
   local z = get_sample_grayscale(x, y, Z_COORDINATE)
   local nx = math.cos(x) * math.sin(y)
   local ny = math.sin(x) * math.sin(y)
   local nz = math.cos(y) * z
   local sx, sy, sz = nx / scale , ny / scale, nz / scale
   local cell_x, cell_y, cell_z = math.floor(sx), math.floor(sy), math.floor(sz)

   local offset_x, offset_y, offset_z
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)
            min_dist = math.min(dist, min_dist)
         end
      end
   end

   local a = 1
   min_dist = 1.0 - min_dist
   return min_dist, min_dist, min_dist, a
end;


And here's the example output:

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Okay, I went ahead and added map inputs for Foreground/Background, and a Profile curve. The next step would be to implement Offset X/Y/Z, Detail and Distortion like my Spherical Perlin, but I'm not sure how they'd fit into the structure of my Spherical Cell Noise. I may have to see if emme has some advice, there, before proceeding. I'll skip the code this time and just share the output example. You can get the Map Script from the ffxml I'll be posting right after this.

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
One note of warning, the Z Coordinate is an HDR grayscale input, but it really should be a Value input; plugging in a grayscale image will give you a sort of tiled distortion, so just use it like a Value slider.

Here's my current work file:

Script API - Spherical Noise.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
In the hope of adding Detail and Distortion, I've tried a few things, including making a get_worley_noise() function, but my inexperience with Lua, or rustiness with coding and scripting in general, have been tripping me up. With the function, I'm having trouble with referring x,y,z parameters to the function and returning them as x,y,z values to the get_sample() that calls it. As for Detail and Distortion, I'm not sure where to apply the modifiers so they'll have the desired effect. I'm mostly breaking the distance mapping, in the way's I've tried so far. I'm pretty talented at breaking code, as opposed to fixing it. So, if there are any willing volunteers, I'll leave those objectives out there for anyone interested in lending a hand. I wish I was good enough at this to do it on my own, but I'm relying mostly on my intuition and scripting doesn't lend itself to intuitive approaches very often.

Oh, since a bit part of the goal here is learning, along the way, I'd be interested in any help with Lua. If you just post a sample function like what I'm after, I'm game to try and apply the lesson to my goal, myself. No, the forum is not seeing a lot of traffic, so it might take a while for anyone to bite. I'll keep trying until help arrives or I eventually figure it out. It got me halfway where I wanted with Spherical Worley Noise. Help is always appreciated, though.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I guess I spoke too soon. My get_worley_noise() function works after all. I just had some issue with the editing I was doing at the original time that was throwing up nil errors. Before scrapping the idea, I made one last attempt with a fresh cut-and-paste into the script editor, and it worked. I must have typoed a variable somewhere, before. I can never spot them. So, here's the script using the function (I'll see if there are other things I can do as far as Details and Distortion, now that I have this working):

Code
function prepare()
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2)
   set_noise_seed(get_intslider_input(NOISE_SEED))
end;

function get_sample(x, y)
   local a = 1
   z = get_sample_grayscale(x, y, Z_COORDINATE)
   
   v = get_worley_noise(x,y,z)

   return v,v,v,a
end;

function get_worley_noise(x,y,z)
   local sx, sy, sz = x / scale , y / scale, z / scale
   local cell_x, cell_y, cell_z = math.floor(sx), math.floor(sy), math.floor(sz)

   local offset_x, offset_y, offset_z
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)
            min_dist = math.min(dist, min_dist)
         end
      end
   end
   min_dist = 1.0 - min_dist
   return min_dist
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Yay! Persistence pays off!

Once I had my Worley noise isolated, the previous approach to Detail and Distortion worked again. I now have my Spherical Cell Noise module caught up to my Spherical Perlin Noise. I'm going to tinker with it a bit more, before posting the ffxml, but here's the final script and a preview of the output:

Code
function prepare()
   oct_max = get_intslider_input(OCTAVES)
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2) * 5
   set_noise_seed(get_intslider_input(NOISE_SEED))
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   if (get_checkbox_input(HDR)) then
      hdr = true
   else
      hdr = false
   end
end;

function get_sample(x, y)
   local r, g, b, a = get_sample_map(x, y, BACKGROUND)
   local r2, g2, b2, a2 = get_sample_map(x, y, FOREGROUND)
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   local amp = 1
   local s = scale
   local detail = 3.75 - get_sample_grayscale(x, y, DETAIL) * 2 + 0.01
   local distortion = get_sample_grayscale(x, y, DISTORTION) * 2
   local x = x * aspect * math.pi
   local y = y * math.pi
   local z = get_sample_grayscale(x, y, Z_COORDINATE)
   local nx = math.cos(x) * math.sin(y) + vosx
   local ny = math.sin(x) * math.sin(y) + vosy
   local nz = math.cos(y) * z + vosz
   for oct = 1, oct_max do
      local d1 = get_worley_noise(nx+1,ny,nz,s) * distortion
      local d2 = get_worley_noise(nx+2,ny,nz,s) * distortion
      local d3 = get_worley_noise(nx+3,ny,nz,s) * distortion
      if oct == 1 then v = get_worley_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_worley_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      s = s / 2
      amp = amp / detail
   end
   v = get_sample_curve(x,y,v,PROFILE)
   local opacity = v
   r, g, b, a = blend_normal(r, g, b, a, r2, g2, b2, a2, opacity, hdr)
   return r, g, b, a
end;

function get_worley_noise(x,y,z,s)
   local sx, sy, sz = x / s , y / s, z / s
   local cell_x, cell_y, cell_z = math.floor(sx), math.floor(sy), math.floor(sz)

   local offset_x, offset_y, offset_z
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)
            min_dist = math.min(dist, min_dist)
         end
      end
   end
   min_dist = 1.0 - min_dist
   return min_dist
end;


  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Well, I did a little tinkering with my spherically mapped noises and submitted the final results to the Library. You should see them popping up in a few days, at most. The only thing I didn't really do, do to my lack of experience, was optimize the distance algorithms for the Worley noise. To do that, I have to use a sorted table and do a little more searching on the subject of Worley noises. In both cases, I hope to learn what I need to, since I still want to make other types of spherically mapped noise. I figured the work I've already done might help someone else if I made them available in the Library.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I'm kind of at a crossroads, with this project. I've gone about as far as I can working with the API Noise and valuable help fr om emme. Now I'm confronted with the challenges I discovered in the process of researching noise generation online. So, before starting anything today, I'm taking a moment to organize some of my thoughts. I'll begin with the loose threads I've already mentioned.

Quote
I still need to figure out how to address the nth point as a variable, to get other permutations, i.e. 2nd closest, 3rd closest, etc. point. I also need to figure out how to apply the other distance formulas for Manhattan, Chebyshev, etc. That's where the other Worley type noises we're familiar come from, with formula variations. So, still work to do!


And:

Quote
The only thing I didn't really do, do to my lack of experience, was optimize the distance algorithms for the Worley noise. To do that, I have to use a sorted table and do a little more searching on the subject of Worley noises.


The problem I'm having has to do with a feature of Worley noise I've seen in every example, except for the API Worley noise I actually used. In most implementations of Worley noise, there is a set of steps devoted to creating, distributing and querying the feature points, which are distance mapped in Euclidean, Manhattan, Chebyshev, etc. method. The API Worley Noise does not seem to openly address that step. I can't see where it determines which point is closest to a given point. I'd have to know that to impement a QuadTree -- the optimization I mentioned last. I'm pretty sure a QuadTree could be implemented in Lua; the structure is similar to Tech Noise Scrippet shared in the forums by ThreeDee (I can't find the thread at the moment, but I'll attach a sample image.Edit: You can find it here) There are other variations of Cell noise created by mapping to the nth closest point (1st, 2nd, 3rd, etc.). Those are also dependent on identifying and sorting seed points, by relative distance.

It's unfortunate that I'm such a novice at graphics scripting; I'm asking for help a lot, which has to bother people at times. I do make progress on my own, provided I have something to lean on, wh ere my own expertise is nil. I do look for answers outside the forum, but there are some very smart people here who do help out on occasion, So, I'll keep asking. But in a lot of ways, I'm flooding the forum (on my own thread, so why not?) mostly to keep track of what I'm doing, and how I'm doing it. If it helps others, fantastic. If it's annoying, well, I'm sorry for that. It gets me through, day to day, though, so it's worth it to me.

If you're still reading, and feel helpful (and of course have the knowledge to share and time to share it) I'd like to see other noise examples roughly on par with the API Worley Noise. I do wish FF had provided scripted examples for each of the Worley-based noises in FF. Collecting and preserving those here is one of the objectives of this thread. I think that's everything on my mind this morning. Today is going to be spent surfing for noise algorithms I can, hopefully, adapt for this project.

TTFN

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
So I finally stumbled across this thread -- Home > Forums > Beta 10.0 > Math: F1, F2, F3 + noise -- which clarified the whole F1, F2, F3 and Euclidian Distance, Manhattan Distance, or Chebyshev Distance terminology for the internal Worley noises. So now all I need is a script example for implementing each of these features in my spherical noise generator.

Quote
No. F1 is the closest feature point, F2 is the second closest point, F3 is the third closest point etc. (closest means "closest to the current sample coordinates). For every sample, the Worley noise calculates all feature points near the sample point, then sorts them by the distance to the sample point -- where the distance is not necessarily Euclidean.

Various Worley-based components use different distance functions. IIRC, Blocks uses Chebyshev, Pyramids uses Manhattan, Stones uses Superquadratic, Techno uses Manhattan or Chebyshev, Cells and Chaffs are Euclidean or Superquadratic.


I could not post a response in the Beta 10.0 thread, and it's already 11 years old, according to the last post, so it's a bit of a stretch, but I'm hoping to catch Vladimir Golovin's attention with this request for additional noise APIs to help with my project. I don't know if it really counts as a Feature Request, so I've held off on starting that kind of thread. I'll keep studying and trying to figure this out on my own, but I'm thinking I'll have to start over from scratch. The implementation of Worley noise I find elsewhere is done very differently, exposing the points to nth order sorting. I'm still looking for examples of other distance functions. I find references to them, but no examples of the formulas themselves in use.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Another victory for persistence! I had been looking for examples of Worley using different distance calculations when I should have just googled the four types I've come across: Euclidean, Manhattan, Chebyshev and Minkowski. That little shift in focus got me the results I was hoping for, and each of the formulas (with a partial exception for Minkowski) boiled down to one line of code.

I tested each one out and got decent results, though I'm clearly still missing whatever produces chaffs or stones. I did not find anything yet on superquadratic distance calculations. I did run into an odd problem when I tried to make the different distance methods controllable by an intslider. The "if" conditions generated a script error, saying "attempt to compare number with nil". I did a little testing assigning the intslider to set different rgba values and it worked outside the "for" loop, but not when it was inside it where the distance mapping needs to occur. Here's a simplified version of the script that's giving me trouble. Maybe someone else can figure out, or knows, what the problem is. Of course, I can just make a different map script for each type of distance mapping. It just seems like I should be able to switch modes in the same script.

Code
function prepare()
   -- v.002
   MIN_SCALE = 0.01
   scale = math.max(MIN_SCALE, get_slider_input(SCALE) / 2) * 5
   set_noise_seed(get_intslider_input(NOISE_SEED))
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   dtype = get_intslider_input(DISTANCE_TYPE)
   p = get_intslider_input(P)
end;

function get_sample(x, y)
   local x = x * aspect * math.pi
   local y = y * math.pi
   local z = get_sample_grayscale(x, y, Z_COORDINATE)
   local nx = math.cos(x) * math.sin(y)
   local ny = math.sin(x) * math.sin(y)
   local nz = math.cos(y) * z
   local sx, sy, sz = nx / scale , ny / scale, nz / scale
   local cell_x, cell_y, cell_z = math.floor(sx), math.floor(sy), math.floor(sz)

   local offset_x, offset_y, offset_z
   local min_dist = 100
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            if dtype == 1 then
               r,g,b,a = 1,0,0,1
            elseif dtype == 2 then
               r,g,b,a = 0,1,0,1
            elseif dtype == 3 then
               r,g,b,a = 0,0,1,1
            else
               r,g,b,a = 1,1,1,1
            end   
            local dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)
            min_dist = math.min(dist, min_dist)
         end
      end
   end

   -- local a = 1
   min_dist = 1.0 - min_dist
   -- return min_dist, min_dist, min_dist, a
   return r,g,b,a
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
I spent the afternoon debugging my script and eventually tracked down the source of the error I was getting. By this point, I'd moved my distance calculations to their own function, so it may not technically be necessary. But, it works, so I'm leaving it in there as a sub-function of my Worley noise. In the process of debugging, I broke down what each part of the script was doing with comments. I'm leaving those in for the moment too, since I was left with a couple questions I still need answered, or rather parts that I hope someone else can explain.

When I got frustrated, I took a break and got the Contrast snippet I posted earlier working, so I can add that in next. I'll be reexamining the API Perlin Noise method of doing Roughness next, too.

For now, though, I'll post the new Spherical Worley Noise script, which gives the option of using Euclidean, Manhattan, Chebyshev or Minkowski distance mapping. My work file is getting a bit cluttered, and I'll eventually upd ate my submissions to the Library with the final versions I come up with. If someone really wants to see an ffxml for this version (and save themselves the trouble of configuring inputs for the script I'm posting) then I'll drop a copy of the Map Script into a new file and post it.

Code
function prepare()
   -- v.006.5
   -- added distance mapping - Euclidean, Manhattan, Chebyshev, Minkowski
   -- prepare global noise constants
   oct_max = get_intslider_input(OCTAVES)
   set_noise_seed(get_intslider_input(NOISE_SEED))
   -- adjust for spherical mapping - constant
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   dtype = get_intslider_input(DISTANCE_TYPE)
   p = get_intslider_input(P)
   --- set hdr state
   if (get_checkbox_input(HDR)) then
      hdr = true
   else
      hdr = false
   end
end;

function get_sample(x, y)
   -- get map inputs
   local r, g, b, a = get_sample_map(x, y, BACKGROUND)
   local r2, g2, b2, a2 = get_sample_map(x, y, FOREGROUND)
   -- get offset map inputs or values
   local vosx = get_sample_grayscale(x, y, OFFSET_X)
   local vosy = get_sample_grayscale(x, y, OFFSET_Y)
   local vosz = get_sample_grayscale(x, y, OFFSET_Z)
   -- initialize noise amplitude
   local amp = 1
   -- get scale map or value and prevent s = 0
   local s = get_sample_grayscale(x, y, SCALE)/2 * 5
   if s == 0 then
      s = 0.001
   end
   -- get detail and distortion map inputs or values
   local detail = 3.75 - get_sample_grayscale(x, y, DETAIL) * 2 + 0.01
   local distortion = get_sample_grayscale(x, y, DISTORTION) * 2
   -- remap to sphere, get value for z
   -- map input not viable, but value input necessary
   local x = x * aspect * math.pi
   local y = y * math.pi
   local z = get_sample_grayscale(x, y, Z_COORDINATE)
   local nx = math.cos(x) * math.sin(y) + vosx
   local ny = math.sin(x) * math.sin(y) + vosy
   local nz = math.cos(y) * z + vosz
   
   -- get and distort each octave of spheremapped noise
   for oct = 1, oct_max do
      local d1 = get_worley_noise(nx+1,ny,nz,s) * distortion
      local d2 = get_worley_noise(nx+2,ny,nz,s) * distortion
      local d3 = get_worley_noise(nx+3,ny,nz,s) * distortion
      -- modify 1st octave fully
      -- then modify each octave's frequency and amplitude proportionately
      if oct == 1 then v = get_worley_noise(nx+d1,ny+d2,nz+d3,s) else
         v = (v + get_worley_noise(nx+d1/oct,ny+d2/oct,nz+d3/oct,s) * amp ) / (1 + amp)
      end
      nz = nz * 2
      -- decrease scale for next octave
      s = s / 2
      -- decrease amplitude for next octave
      amp = amp / detail
   end
   --- get profile curve - internal tonemapping
   v = get_sample_curve(x,y,v,PROFILE)
   -- set opacity to noise values
   local opacity = v
   -- blend foreground with background
   r, g, b, a = blend_normal(r, g, b, a, r2, g2, b2, a2, opacity, hdr)
   return r, g, b, a
end;

function get_worley_noise(x,y,z,s)
   -- adjust x,y,z for scale
   local sx, sy, sz = x / s , y / s, z / s
   -- partition x,y,z into "integer" cells
   local cell_x, cell_y, cell_z = math.floor(sx), math.floor(sy), math.floor(sz)
   -- create offset variables for x,y,z
   local offset_x, offset_y, offset_z
   -- se t benchmark minimum distance
   local min_dist = 10000
   -- nested loop through x,y,z coordinate space via incremental offsets each  
   for offset_x=-1,1 do
      for offset_y=-1,1 do
         for offset_z=-1,1 do
            -- what is really happening here?
            local dx = cell_x + offset_x + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dy = cell_y + offset_y + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            local dz = cell_z + offset_z + get_noise(cell_x + offset_x, cell_y + offset_y, cell_z + offset_z)
            -- option: create array of distances to seed points here?
            -- option: get distance of random point from current point?
            -- option: assign distances to array of distances for nth point distances?
            local dist = distcalc(sx,sy,sz,dx,dy,dz)
            -- compare distance to benchmark minimum distance for lesser value
            min_dist = math.min(dist, min_dist)
         end
      end
   end
   -- what does this adjustment accomplish?
   min_dist = 1.0 - min_dist
   -- return value of current point
   return min_dist
end;

function distcalc(sx,sy,sz,dx,dy,dz)
   local dist = 0 -- returns nil value without this variable declaration
   if dtype == 1 then
      -- Euclidean
      dist = math.sqrt((sx - dx)^2 + (sy - dy)^2 + (sz - dz)^2)   
   elseif dtype == 2 then
      -- Chebyshev
      dist = math.max(math.abs(sx - dx), math.abs(sy - dy), math.abs(sz - dz))   
   elseif dtype == 3 then
      -- Manhattan
      dist = (math.abs(sx - dx) + math.abs(sy - dy) + math.abs(sz - dz)) / 1.5   
   else
      -- Minkowski
      local pe = 1/p
      dist = (math.abs(sx - dx)^p + math.abs(sy - dy)^p + math.abs(sz - dz)^p)^pe
   end   
   return dist
end;
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Okay, I hit a stopping point early today. I was seeing what I could accomplish working with the API Multi-Octave Perlin and discovered that the way it handles Roughness is not compatible with spherical mapping. Remapping was not that hard, but there is a stretching artifact along the "equator" that, at best, could only be minimized -- not eliminated. There also seems to be an issue with scale settings for completely zoomed in. What worked in Cartesian space, without modifications to the input scale, does not work even with modifications to scale in the sphere map.

I was able to map Roughness and Contrast (which was easy enough to add), so there's at least a v.001 that works. The ffxml has a v.002 started, current to where I left off. I'll post the code and file for anyone who wants to take a crack at this. I'm sure there are many people on the forums who would fare much better than I.

Code
function prepare()
--   v.002
--   constants
   AMPLITUDE_CORRECTION_FACTOR = 1.731628995
   ROUGHNESS_THRESHOLD = 0.00001
   REMAINDER_THRESHOLD = 0.00001
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2
   set_perlin_noise_seamless(SEAMLESS)
   set_perlin_noise_seamless_region(SEAMLESS_REGION_WIDTH, SEAMLESS_REGION_HEIGHT)

--   input values
   details = get_slider_input(DETAILS) * 10 + 1
   OCTAVES_COUNT = math.floor(details)
   NOISE_SIZE = get_slider_input(SCALE)
   set_perlin_noise_seed(get_intslider_input(NOISE_VARIATION))
end;

function get_sample(x, y)
   local r, g, b, a = get_sample_map(x, y, BACKGROUND)
   local noise_r, noise_g, noise_b, noise_a = get_sample_map(x, y, NOISE)

   local remainder = details - OCTAVES_COUNT
   if (remainder > REMAINDER_THRESHOLD) then
      OCTAVES_COUNT = OCTAVES_COUNT + 1
   end
   
   local roughness = ROUGHNESS_THRESHOLD +
      get_sample_grayscale(x, y, ROUGHNESS) * (1.0 - ROUGHNESS_THRESHOLD)
   local contrast = (get_sample_grayscale(x, y, CONTRAST) * 2) - 1
   local factor = (259 * (contrast + 1)) / (1 * (259 - contrast))
   
   OCTAVES = {}
   local cell_size = (0.01 + NOISE_SIZE * 0.99) * 150
   local scale = roughness
   local octave_index
   for octave_index = 1, OCTAVES_COUNT do
      if (scale < ROUGHNESS_THRESHOLD) then
         OCTAVES_COUNT = octave_index - 1
         break
      end
      OCTAVES[octave_index] = {cell_size, scale}
      cell_size = cell_size * 0.5
      scale = scale * roughness
   end
   
   if (remainder >= 0.001) then
      OCTAVES[OCTAVES_COUNT][2] = OCTAVES[OCTAVES_COUNT][2] * remainder
   end

   NORM_FACTOR = 0
   for octave_index = 1, OCTAVES_COUNT do
      NORM_FACTOR = NORM_FACTOR + OCTAVES[octave_index][2] ^ 2
   end
   NORM_FACTOR = 1 / math.sqrt(NORM_FACTOR)

   -- ins ert spheremapping calculations
   local x = x * aspect * math.pi
   local y = y * math.pi
   local nx = math.cos(x) * math.sin(y) -- * scale + vosx
   local ny = math.sin(x) * math.sin(y) -- * scale + vosy
   -- local nz = math.cos(y) * math.pi -- * scale + vosz
   local alpha = 0
   
   for octave_index = 1, OCTAVES_COUNT do
      -- this minimizes stretching the most, still has artifacts
      local nz = octave_index * math.cos(y) * math.pi
      local size = OCTAVES[octave_index][1]
      local opacity = OCTAVES[octave_index][2]
      -- local d1 = get_perlin_noise(nx+1,ny,nz,size) -- * distortion
      -- local d2 = get_perlin_noise(nx+2,ny,nz,size) -- * distortion
      -- local d3 = get_perlin_noise(nx+3,ny,nz,size) -- * distortion
      -- local noise_z = octave_index
      -- -- ins ert spheremapped x,y,z coordinates
      --  -- produces stretching along equator
      alpha = alpha + opacity * (2 * get_perlin_noise(nx,ny,nz,size) - 1)
      -- alpha = alpha + opacity * (2 * get_perlin_noise(nx+d1,ny+d2,nz+d3,size) - 1)
   end
   
   alpha = (alpha * NORM_FACTOR + AMPLITUDE_CORRECTION_FACTOR) *
      (0.5 / AMPLITUDE_CORRECTION_FACTOR)
   local alpha = truncate(factor * (alpha - 0.5) + 0.5)
   
   return blend_normal(r, g, b, a, noise_r, noise_g, noise_b, noise_a, alpha)
   -- return size,size,size,1
end;

function truncate(value)
   if value <= 0 then value = 0 end
   if value >= 1 then val ue = 1 end
   return val ue
end;


Script API - Multi-Octave Perlin v.001.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Having run into a couple problems I could not solve, at present, I went ahead and spent part of the day updating my spherical noise scripts and posting the updates. Since it could take a while for those updates to go through, I'm wrapping up by posting copies here for anyone who doesn't want to wait.

Spherical Perlin Noise Map Script.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Here is the other. It's slower, by a minute, but now offers four different variations (Euclidean, Manhattan, Chebyshev and Minkowski), as noted a couple posts ago. Manhattan gives a result most similar to Pyramids. Chebyshev falls somewhere between blocks and Techno -- and Minkowski offers a range between Euclidean (Cell noise) and Chebyhsev. Once you apply any of the possible distortions and levels of detail, they tend to become even more similar. Oh well.

Spherical Worley Noise Map Script.ffxml
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Having reached the limit of what I could do with my current level of scripting ability, I've sort of drifted off into the land of Processing.org having found a massive resource in the videos of Daniel Shiffman. Having once studied things like Java and Javascript Processing should be much easier for me to pick up and use than Lua. My experience with C/C++ is more out of date, and it's difficult to find tutorials for Lua graphics scripting. I'm hoping to be able to take some of what I learn in Processing and apply them in Lua, at which point I'll come back to projects like the one I started here.

The Worley noise I created only has F1 distance mapping. I was not able to grasp how the script identified the closest feature point in its distance calculations. I will keep tabs on this thread, in case someone offers assistance with F2, F3 and F4 distance mapping. I don't know if they can apply equally to the four types of Worley noise I was able to get through different distance algorithms. That's something I'll need clarified too. I hope I'll be able to figure it all out eventually, but maybe I'll get help from the forum along the way. If either of those things happen, I'll take update my Spherical Worley Noise. If you've read through the thread this far, thank you and hopefully you found some of it useful.

TTFN
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
So, it's been a year and change since I thought about this, but I came back to it today and find the problem still stumps me:

Quote
David Roberson wrote:
I was seeing what I could accomplish working with the API Multi-Octave Perlin and discovered that the way it handles Roughness is not compatible with spherical mapping. Remapping was not that hard, but there is a stretching artifact along the "equator"...


I compared this to a few other noise scripts I've done or used as examples, to try and understand why this seems to happen only with Perlin noise called using get_perlin_noise(). Or rather, the version in Egret's API Perlin Noise script. The octave blending emme provided an example did not have this problem. I would be inclined to keep using that, but the Roughness just isn't as nice as Egret's. I do hope an FF team member can chime in here, since Egret's implementation was based on the FF version.

I'll post a pic of what I'm talking about, since I'm just throwing this out there again to see if someone can shed some light on why this is happening or how to fix it.

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Last night I did a bit of online research and found a different implementation of improved Perlin noise adapted for lua. So, today I worked on an update to my Perlin Spherical Noise map script. I have been able to generate a sphere and a spherical map using it. I discovered that the noise did not tile correctly as a flat plane, however, so I'm posting the code to see if anyone can suggest a way to make it properly seamless and not just repeating or completely hashed. The options to see the default presentation and my attempt to apply SEAMLESS_WIDTH and SEAMLESS_HEIGHT are commented off.

Code
-- 3d perlin sphere  or map -- improved perlin noise
function prepare()
-- constants
   -- AMPLITUDE_CORRECTION_FACTOR = 1.731628995
      -- only used in depricated statement
   ROUGHNESS_THRESHOLD = 0.00001
   REMAINDER_THRESHOLD = 0.00001
   aspect = OUTPUT_HEIGHT / OUTPUT_WIDTH * 2

-- input values
   details = get_slider_input(DETAILS) * 10 + 0.0001
   grain = (get_slider_input(GRAIN) * 5) + 0.0001
   OCTAVES_COUNT = math.floor(details)
   NOISE_SIZE = math.log( get_slider_input(SCALE) + 0.0000000001)
   -- NOISE_SIZE = (1 - get_slider_input(SCALE)) * (5 * grain) + 0.0001
   
-- sphere block
   radius = get_slider_input(RADIUS)

   angle = math.rad(get_angle_input(ROTATION))
   cosa1 = math.cos(angle)
   sina1 = math.sin(angle)

   tilt = math.rad(get_angle_input(TILT))
   cosa2 = math.cos(tilt)
   sina2 = math.sin(tilt)
-- end

-- noise block
    --[[
      https://gist.githubusercontent.com/kymckay/25758d37f8e3872e1636d90ad41fe2ed/raw/1c647169a6729713f8987506b2e5c75a23b14969/perlin.lua
      Implemented as described here:
      http://flafla2.github.io/2014/08/09/perlinnoise.html
   ]]--

   perlin = {}
   perlin.p = {}

   --[[
      -- hash lookup table as defined by Ken Perlin
      -- this is a randomly arranged array of all numbers fr om 0 - 255 inclusive
      local permutation = {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
      }

      -- p is used to hash unit cube coordinates to [0, 255]
      for i = 0, 255 do
         convert to 0 based index table
         perlin.p[i] = permutation[i + 1]
         repeat the array to avoid buffer overflow in hash function
         perlin.p[i + 256] = permutation[i + 1]
      end
   ]]--

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

   -- return range: [ - 1, 1]
   function perlin:noise(x, y, z)
      y = y or 0
      z = z or 0

   --[[
      -- seamless tiling method needs revision - not part of original adaptation, attempt here produced overwhelming artifacts
      -- check for seamlessness
      if SEAMLESS then
         x = x % SEAMLESS_REGION_WIDTH
         y = y % SEAMLESS_REGION_HEIGHT
         z = z % (SEAMLESS_REGION_WIDTH + SEAMLESS_REGION_HEIGHT) * 0.5
      end
   ]]--

      -- calculate the "unit cube" that the point asked will be located in
      local xi = bit32.band(math.floor(x), 255)
      local yi = bit32.band(math.floor(y), 255)
      local zi = bit32.band(math.floor(z), 255)

      -- next we calculate the location (from 0 to 1) in that cube
      x = x - math.floor(x)
      y = y - math.floor(y)
      z = z - math.floor(z)

      -- we also fade the location to smooth the result
      local u = self.fade(x)
      local v = self.fade(y)
      local w = self.fade(z)

      -- hash all 8 unit cube coordinates surrounding input coordinate
      local p = self.p
      local A, AA, AB, AAA, ABA, AAB, ABB, B, BA, BB, BAA, BBA, BAB, BBB
      A = p[xi ] + yi
      AA = p[A ] + zi
      AB = p[A + 1 ] + zi
      AAA = p[ AA ]
      ABA = p[ AB ]
      AAB = p[ AA + 1 ]
      ABB = p[ AB + 1 ]

      B = p[xi + 1] + yi
      BA = p[B ] + zi
      BB = p[B + 1 ] + zi
      BAA = p[ BA ]
      BBA = p[ BB ]
      BAB = p[ BA + 1 ]
      BBB = p[ BB + 1 ]

      -- take the weighted average between all 8 unit cube coordinates
      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)
            )
         )
      )
   end

   --[[
      gradient function finds dot product between pseudorandom gradient vector
      and the vector from input coordinate to a unit cube vertex.
   ]]--
   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
   }
   function perlin:grad(hash, x, y, z)
      return self.dot_product[bit32.band(hash, 0xF)](x, y, z)
   end

   -- fade function is used to smooth final output
   function perlin.fade(t)
      return t * t * t * (t * (t * 6 - 15) + 10)
   end

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

   -- perlin octaves initialization
   remainder = details - OCTAVES_COUNT
   if (remainder > REMAINDER_THRESHOLD) then
      OCTAVES_COUNT = OCTAVES_COUNT + 1
   end
   
   --[[
      local roughness = ROUGHNESS_THRESHOLD +
         get_slider_input(ROUGHNESS) * (1.0 - ROUGHNESS_THRESHOLD)
      
      OCTAVES = {}
      local cell_size = (0.01 + NOISE_SIZE * 0.99) * grain -- 5
      local scale = roughness
      local octave_index
      for octave_index = 1, OCTAVES_COUNT do
         if (scale < ROUGHNESS_THRESHOLD) then
            OCTAVES_COUNT = octave_index - 1
            break
         end
         OCTAVES[octave_index] = {cell_size, scale}
         cell_size = cell_size * 2.0
         scale = scale * roughness
      end
      
      if (remainder >= 0.001) then
         OCTAVES[OCTAVES_COUNT][2] = OCTAVES[OCTAVES_COUNT][2] * remainder
      end

      NORM_FACTOR = 0
      for octave_index = 1, OCTAVES_COUNT do
         NORM_FACTOR = NORM_FACTOR + OCTAVES[octave_index][2] ^ 2
      end
      NORM_FACTOR = 1 / math.sqrt(NORM_FACTOR)
   ]]--
-- end
   if  get_checkbox_input(MODE) then
      mode = 1
   else
      mode = 2
   end
   --[[
   -- seamless tiling method needs revision - not part of original adaptation; change to "else mode = 0" to see default tiling
   set_perlin_noise_seamless(SEAMLESS)
   set_perlin_noise_seamless_region(SEAMLESS_REGION_WIDTH, SEAMLESS_REGION_HEIGHT)
   ]]--
end;


function get_sample(x, y)
   local roughness = ROUGHNESS_THRESHOLD +
      get_sample_grayscale(x, y, ROUGHNESS) * (1.0 - ROUGHNESS_THRESHOLD)
   local contrast = (get_sample_grayscale(x, y, CONTRAST) * 2) - 1
   local factor = (259 * (contrast + 1)) / (1 * (259 - contrast))
   local n = 0
      --[[
         full implementation of mappped roughness and contrast requires coordinate remapping to the sphere.
         this is already in use elsewhere, so it will be implemented after spherical map generation is complete.
         block may move to conditional block per usage.
      ]]--

   -- image generation
   -- sphere block
   if mode == 1 then
      px = (x * 2.0) - 1.0
      py = (y * 2.0) - 1.0
      px = px / radius
      py = py / radius
      local len = math.sqrt((px * px) + (py * py))
      if len > 1.0 then return 0, 0, 0, 0 end

      z = - math.sqrt(1.0 - ((px * px) + (py * py)))

      local tz = (cosa2 * z) - (sina2 * py)
      local ty = (sina2 * z) + (cosa2 * py)
      z = tz
      py = ty

      local tx = (cosa1 * px) - (sina1 * z)
      local tz = (sina1 * px) + (cosa1 * z)
      px = tx
      z = tz
   end
   -- end

   -- spherical map block
   if mode == 2 then
      local x = x * aspect * math.pi
      local y = y * math.pi
      nx = math.cos(x) * math.sin(y)
      ny = math.sin(x) * math.sin(y)
      nz = math.cos(y)
   end
   -- end

   -- noise generation
   OCTAVES = {}
   local cell_size = (0.01 + NOISE_SIZE * 0.99) * grain -- 5
   local scale = roughness
   local octave_index
   for octave_index = 1, OCTAVES_COUNT do
      if (scale < ROUGHNESS_THRESHOLD) then
         OCTAVES_COUNT = octave_index - 1
         break
      end
      OCTAVES[octave_index] = {cell_size, scale}
      cell_size = cell_size * 2.0
      scale = scale * roughness
   end
   
   if (remainder >= 0.001) then
      OCTAVES[OCTAVES_COUNT][2] = OCTAVES[OCTAVES_COUNT][2] * remainder
   end

   NORM_FACTOR = 0
   for octave_index = 1, OCTAVES_COUNT do
      NORM_FACTOR = NORM_FACTOR + OCTAVES[octave_index][2] ^ 2
   end
   NORM_FACTOR = 1 / math.sqrt(NORM_FACTOR)
   local octave_index
   for octave_index = 1, OCTAVES_COUNT do
      local size = OCTAVES[octave_index][1]
      local opacity = OCTAVES[octave_index][2]
      if mode == 1 then
         n = n + opacity * perlin:noise(px * size, py * size, z * size) -- (2 * -- - 1)
      elseif mode == 2 then
         n = n + opacity * perlin:noise(nx * size, ny * size, nz * size) -- (2 * -- - 1)
      else   
         local z = octave_index
         n = n + opacity * perlin:noise(x * size, y * size, z * size) -- (2 * -- - 1)
      end
   end
   n = (n + 1.0) * 0.5
--[[
   n = (n * NORM_FACTOR + AMPLITUDE_CORRECTION_FACTOR) *
      (0.5 / AMPLITUDE_CORRECTION_FACTOR)
            -- causes blowout - getting reasonable results without it
]]--
   n = truncate(factor * (n - 0.5) + 0.5)
   n = get_sample_curve(x, y, n, PROFILE)
   return n, n, n, 1
end;


function truncate(value)
   if value <= 0 then value = 0 end
   if value >= 1 then value = 1 end
   return value
end;


My attempt to impose seamlessness seems to conflict with the method used to implement octaves. It still produces noise, but in repeating blocks diminishing in octaves as well. That suggests two probable issues to resolve; first is seamless blending of the base noise, and second, getting properly scaled seamless tiling for each octave, to the lim it of octave_index. At the moment, I have no ideas on how to do that.
  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Looking at the spherical Perlin I produced, I had one idea regarding the mapping required for a seamless plane; basically, I need to get mapping coordinates for a torus, which curves in 3d space to create a perfect 2d planar mapping. I'll see if I can find the right function for this, but I'm making a note of it here in case someone else wants to try this idea out.

  Details E-Mail
David Roberson
Artist
Posts: 404
Filters: 36
Since the standard Perlin Noise works for most of the things I'd need to be seamless-squared, I set that part of my scripting aside to focus on what could be done with the equirectangular sphere maps and the spherical projection I came up with so far. The goal of a good, fully procedural planet generator is still a bit of work away. I do have a nice preview of what can be done with these new map scripts. Using a downloaded Earth map, I made some very nice clouds and atmosphere using two map scripts, a few Perlin Noises (mostly to make stars and offset a Profile Gradient), a couple of curves and Scale components (to adjust the Earth map to a square workspace), and a few Math components.

  Details E-Mail

Join Our Community!

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

33,711 Registered Users
+18 new in 30 days!

153,531 Posts
+36 new in 30 days!

15,347 Topics
+72 new in year!

Create an Account

Online Users Last minute:

21 unregistered users.