Part of this was thinking about noise algorithms and how to get more interesting results from a stateless generator. So, I applied a useful technique from sound synthesis: using functions to distort other functions (varying the input X & Y coordinates of a noise function by results taken from another). The results, in my opinion, are much more interesting than simply summing different octaves of coherent noise. I attached the results below. It's a fairly large image, so I also put it in a spoiler block.
Distorted noise - 80 test runs
I also have test code written in the Python programming language. It uses OpenSimplex to to generate noise and a fork of Python Imaging Library ("Pillow") to display results. Install those libraries, copy & paste the code into a python script (with python set to run as an intaractive shell). 'test()' should produce distorted noise with decent random parameters. The other more complicated function is more controllable. Code: Select all
from PIL import Image
from random import randint
from random import random as rand
from math import sin
from math import cos
from math import pi as PI
from opensimplex import OpenSimplex
# Range of OpenSimplex appears to be [0 +- sqrt(.75)]. For obvious reasons, I prefer [-1:1].
NOISE_NORM_FACTOR = 1 / .75**.5
# Run a randomized test using some OK-ish parameters
def test(scale=4/512, size=512, ofs=None, seed_s=None, seed_a=None, seed_b=None):
if seed_s == None:
seed_s = randint(-2**31, 2**31)
if seed_a == None:
seed_a = randint(-2**31, 2**31)
if seed_b == None:
seed_b = randint(-2**31, 2**31)
s = OpenSimplex(seed_s)
a = OpenSimplex(seed_a)
b = OpenSimplex(seed_b)
res_ang = rand()**2 * 10
res_rad = rand()**2 * 10
min_rad = rand()
max_rad = min_rad + rand()
if ofs == None:
ofs = (randint(-2**31, 2**31),randint(-2**31, 2**31))
testAngularDispNoise(s,a,b, scale, res_ang, res_rad, min_rad, max_rad, size, ofs)
# Run the more sophisticated noise function with random parameters
def test2(scale=4/512, size=512, ofs=None, seed_s=None, seed_a=None, seed_b=None, seed_r=None):
if seed_s == None:
seed_s = randint(-2**31, 2**31)
if seed_a == None:
seed_a = randint(-2**31, 2**31)
if seed_b == None:
seed_b = randint(-2**31, 2**31)
if seed_r == None:
seed_r = randint(-2**31, 2**31)
s = OpenSimplex(seed_s)
a = OpenSimplex(seed_a)
b = OpenSimplex(seed_b)
r = OpenSimplex(seed_r)
res_ang = rand()**2 * 10
res_rad = rand()**2 * 10
res_rel = rand()**2 * 10
#res_rel = 1
min_rad = rand()
max_rad = min_rad + rand()
rd = rand() * 16
#rd = 0
if ofs == None:
ofs = (randint(-2**31, 2**31),randint(-2**31, 2**31))
print("res_ang: " + str(res_ang))
print("res_rad: " + str(res_rad))
print("res_rel: " + str(res_rel))
print("min_rad: " + str(min_rad))
print("max_rad: " + str(max_rad))
print("rd: " + str(rd))
print("ofs: " + str(ofs))
testAngularDispNoise_raddistort(s,a,b,r, scale, res_ang, res_rad, res_rel, min_rad, max_rad, rd, size, ofs)
# Distort a noise function by deriving displacement vectors from a pair of other noise functions
def testAngularDispNoise(gen_out, gen_angle, gen_radius, scale_main, res_angle, res_radius, min_radius, max_radius, size=512, ofs=(0,0), f=None):
w = 0
h = 0
if type(size) is int:
w = size
h = size
else:
w = size[0]
h = size[1]
buf = [0] * (3*w*h)
for x in range(0,w):
for y in range(0,h):
_x = x+ofs[0]
_y = y+ofs[1]
angle = gen_angle.noise2d(_x*res_angle*scale_main, _y*res_angle*scale_main) * NOISE_NORM_FACTOR * PI
base_radius = (gen_radius.noise2d(_x*res_radius*scale_main, _y*res_radius*scale_main) * NOISE_NORM_FACTOR + 1) * .5
radius = base_radius * (max_radius-min_radius) + min_radius
xD = sin(angle) * radius;
yD = cos(angle) * radius;
v = (int)((gen_out.noise2d(_x*scale_main + xD, _y*scale_main + yD) * NOISE_NORM_FACTOR + 1) * 128)
if v < 0:
v = 0
elif v > 255:
v = 255
buf[3 * (x + y*w) + 2] = v
img = Image.frombytes('HSV', (w,h), bytes(buf))
if f != None:
_img = img.convert('RGB')
_img.save(f)
_img.close()
else:
img.show()
img.close()
# Similar to preceding function, but with the length of the displacement vector scaled by another noise function
def testAngularDispNoise_raddistort(gen_out, gen_angle, gen_radius, gen_rad, scale_main, res_angle, res_radius, res_rad, min_radius, max_radius, raddist, size=512, ofs=(0,0), f=None):
w = 0
h = 0
if type(size) is int:
w = size
h = size
else:
w = size[0]
h = size[1]
buf = [0] * (3*w*h)
for x in range(0,w):
for y in range(0,h):
_x = x+ofs[0]
_y = y+ofs[1]
rdist = 1 + (gen_rad.noise2d(_x*res_rad*scale_main, _y*res_rad*scale_main) * NOISE_NORM_FACTOR) * raddist
angle = gen_angle.noise2d(_x*res_angle*scale_main, _y*res_angle*scale_main) * NOISE_NORM_FACTOR * PI
base_radius = (gen_radius.noise2d(_x*res_radius*scale_main, _y*res_radius*scale_main) * NOISE_NORM_FACTOR + 1) * .5 * rdist
radius = base_radius * (max_radius-min_radius) + min_radius
xD = sin(angle) * radius;
yD = cos(angle) * radius;
v = (int)((gen_out.noise2d(_x*scale_main + xD, _y*scale_main + yD) * NOISE_NORM_FACTOR + 1) * 128)
if v < 0:
v = 0
elif v > 255:
v = 255
buf[3 * (x + y*w) + 2] = v
img = Image.frombytes('HSV', (w,h), bytes(buf))
if f != None:
_img = img.convert('RGB')
_img.save(f)
_img.close()
else:
img.show()
img.close()
To actually integrate it into a mod for use as a terrain generator, my inclination would be to implement a 2d modular synthesizer (which outputs tile & resource generation commands instead of sound), to allow for excessive flexibility. That said, I haven't actually decided to make the mod, but I want to put the idea out there in case anyone with a bit more time wants to pick it up.
EDIT: Added an extra function which takes the concept a bit farther by deriving displacement vector lengths from another noise function (with significantly more varied results), and a corresponding test function to play with it, "test2()".