Simplex Noise in N (!) Dimensions - (Not) Having fun...

The goal: Squeezing everything out of an implementation possible, make a “super-universal” implementation…

Somehow almost archieved… (close enough ;D)

Mind, that this implementation doesn’t really alow you N-dimensional Simplex Noise generation, but allow you 1 to 32 dimensional Simplex Noise. But since your computer needs a hell lot of memory to compute some SimplexNoise with 33 dimensions, it wouldn’t even be possible at reasonable sizes (the sizes of the dimensions themselves) today.

So here is an animated gif, animating through all the layers of a 3-Dimensional Noise. I don’t know any way to visualize a 4-Dimensional Noise, but you can be sure, that that works.

Little generational improvement (Does not look that celly now):

http://dl.dropbox.com/u/45530199/Programs/SimplexNoiseN/image35.gif

http://dl.dropbox.com/u/45530199/Programs/SimplexNoiseN/image37.gif

WARNING!
Bevore reading the source code, keep in mind, that the source is totally CRACY… I needed Callbacks for example. For these callbacks I had to create Anonymus Inner functions… keep that in mind :slight_smile:
If you have any Idea on how to do that different… I’d like to know :slight_smile:
The code is almost uncommented.
(Most important code is under SimplexNoiseLayerN.java)

Source on Github.

Import it to blender as a temporal 3D density function and render as smoke or some other cool way :slight_smile:

Nice work btw!

Note that the structure of a simplex makes any dimensions beyond 5 not really useful.

If I’d know how to do that :smiley:

Oh okey then… I don’t need 5 dimensions anyway :slight_smile:

Use 4D noise to ‘animate’ the noise on the surface of a sphere. It’s barely a true visualization of the 4th dimension, as it’s basically the intersection of a surface through 3 spatial dimensions and 1 time dimension.

Yes… And animated clouds / particles (with a > 0 test = particle creating) and the 4th dimension = time would also work… But what I wanted to say is, that I’m not able to put the 4 dimensional noise into for example gif or anything like that.

One of the downsides of simplex noise is that it’s very apparant that you’re near an ‘integer’ in some dimension. In your GIFs that’s very visible, as at certain ‘depths’ there are distinct patterns. The whole point of noise is that is has a few patterns as possible.

One way to fix this is to sample your noise from a higher spatial dimension.

You’re now basically doing:
2 spatial dimensions + time (for animation)

What you should be doing:
taking an arbitrary plane (2D) through a volume (3D) and sample across the surface of that plane + time (for animation)

All the artifacts will be gone, unless the plane’s normal has a length on an axis near 0.0 or 1.0

… By an arbitrary plane whose normal isn’t “showing nearly in the same direction” as an axis?
So just a “leaning” plane?

And yes. The artifacts bug me right now… But I don’t know enough math stuff for doing something like that :confused:

A quick-and-dirty (and understandable) way to sample points on a plane, would be like this:

Imagine what you’re doing now:
[x] you define a rectangle (0…w, 0…h) and interpolate in two dimensions, to sample noise.

What you could do instead:
[x] create a random normal: normalize(random()-0.5,random()-0.5,random()-0.5)
[x] rinse and repeat, until all axis of the normal are not in the range -0.1…+0.1
[x] create a transformation matrix (4x4)
[x] rotate the matrix along the normal (axis-angle rotation)
[x] you define a rectangle (0…w, 0…h) and interpolate in two dimensions, feed it into the matrix and use the output to sample noise.

Yes, it’s hilariously inefficient, but it gives you a rough idea how to rotate a surface in a volume.

I attempted to add a bit about defects in wiki page I’m working on. The easy way reduce the impact is to make sure that the cells of multiple computations are not aligned.

Isn’t that comparable to sampling a transformed plane?

No, I think he is talking about each layer being “rotated” or having different cell alignments. Btw, I’m just implementing something like you suggested :slight_smile:

Wow… This worked pretty well :slight_smile:

Here is what now is generated:

http://dl.dropbox.com/u/45530199/Programs/SimplexNoiseN/image34.gif

Looks not-celly… (Used Riven’s advice)

But how’d you go with creating something like that for 2 dimensions?

The same, just one spatial dimension less. :persecutioncomplex:

I never use a higher dimension except for animation purposes. Taking a non-aligned planer slice just shifts the directions of the defects a bit. But whatever works is all good. (Note that any affine transformed planar slice just requires some one time computation of the various deltas which can be used in the loop). Ignoring animation a simple way to most avoid defects is to simply have the initial largest scale (lowest frequency) sampling range both far-ish from the origin (never sample collapsing around the origin) and not integer aligned and smaller scale just move closer and closer by the scale factor(s). Walking straight through an extra dimension adds uniform evolution. Walking around an axis in the higher dimension is interesting for having varying the rate of evolution in a linear manner (slowest near the axis and fastest at the most distant). It should be noted that a very common source of defects is to have a bad hashing function.

Erm… no… : Sorry, totally misunderstood you … heh… ::slight_smile: Yeah, you’re right, but I wanted to know, how to “simulate a plane, walking through the noise”, without having a volume (3 Dimensions)…
It’s true, that there are some changes in the settings for the different SimplexNoiseLayers, but it’s more than that,
here is the code for generating the Gif: (I’m sorry, this is only testing code, so it really looks awful and is packed inside one single function (the main() funciton))


public static void main(String[] args) {
	final int[] dims = { 64, 256, 256 };
	final int tilex = 1;
	final int tiley = 1;
	System.out.println("Creating.");
	long now = System.currentTimeMillis();
	final Random rand = new Random();
	final FloatInterpolation interpolator = new FloatInterpolationFunc() {
		@Override
		protected float func(float t) {
			return 3*t*t - 2*t*t*t;
		}
	};
	final MatrixNf content = new SimplexNoiseN(new SimplexNoiseLayerN[] {
			new SimplexNoiseLayerN(64, rand, interpolator, dims),
			new SimplexNoiseLayerN(32, rand, interpolator, dims),
			new SimplexNoiseLayerN(8, rand, interpolator, dims),
			new SimplexNoiseLayerN(4, rand, interpolator, dims)
	}, new float[] {
			64,
			32,
			8,
			4
	}, true, dims).get();
	long then = System.currentTimeMillis();

	// Creating plane:
	int[][] plane = new int[dims[1]][dims[2]];
	int x0 = 0;
	int y0 = 0;
	int x1 = dims[1]-1;
	int y1 = dims[2]-1;
	plane[x0][y0] = Math.abs(rand.nextInt() % dims[0]);
	plane[x1][y0] = Math.abs(rand.nextInt() % dims[0]);
	plane[x1][y1] = Math.abs(rand.nextInt() % dims[0]);
	plane[x0][y1] = Math.abs(rand.nextInt() % dims[0]);
	int edge00 = plane[x0][y0];
	int edge10 = plane[x1][y0];
	int edge11 = plane[x1][y1];
	int edge01 = plane[x0][y1];
	for (int x = 0; x < plane.length; x++) {
		for (int y = 0; y < plane[x].length; y++) {
			if (!( (x == x0 && y == y0)
				|| (x == x1 && y == y0)
				|| (x == x1 && y == y1)
				|| (x == x0 && y == y1) )) {
				float tx = x / (Math.abs(x0-x1));
				float ty = y / (Math.abs(y0-y1));
				int val0 = Math.round(interpolator.interpolate(tx, edge00, 0, edge10, 1));
				int val1 = Math.round(interpolator.interpolate(tx, edge01, 0, edge11, 1));
				plane[x][y] = Math.round(interpolator.interpolate(ty, val0, 0, val1, 1));
			}
		}
	}
	System.out.println("Creation finished (" + (then-now) + " ms).");
	System.out.println("Converting to Image.");
	BufferedImage[] imgs = new BufferedImage[dims[0]];
	for (int layer = 0; layer < dims[0]; layer++) {
		BufferedImage img = new BufferedImage(dims[1]*tilex, dims[2]*tiley, BufferedImage.TYPE_INT_ARGB);
		for (int x = 0; x < dims[1]*tilex; x++) {
			for (int y = 0; y < dims[2]*tiley; y++) {
				int greyscale = (int)(((content.get((layer + plane[x][y]) % dims[0], x%dims[1], y%dims[2])+1)*0.5) * 255);
				img.setRGB(x, y, toRGB(greyscale, greyscale, greyscale, 255));
			}
		}
		imgs[layer] = img;
	}
	now = System.currentTimeMillis();
	System.out.println("Converting finished (" + (now-then) + " ms).");
	String directory = "simplex_noise";
	File dir = new File(directory);
	if (!dir.exists()) {
		dir.mkdir();
	}
	String filename = directory + "/" + "image";
	String suffix = ".gif";
	int i = 0;
	File file;
	do {
		i++;
		file = new File(filename + i + suffix);
	} while (file.exists());
	try {
		file.createNewFile();
	} catch (IOException e1) {
		e1.printStackTrace();
	}
	then = System.currentTimeMillis();
	System.out.println("Writing to file.");
	ImageOutputStream stream = null;
	try {
		stream = new FileImageOutputStream(file);
		GifSequenceWriter writer = new GifSequenceWriter(stream, BufferedImage.TYPE_INT_RGB, 64, true);
		for (BufferedImage img: imgs) {
			writer.writeToSequence(img);
		}
		writer.close();
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		if (stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	now = System.currentTimeMillis();
	System.out.println("Finished writing (to " + filename + i + suffix + ")(" + (now-then) + " ms).");
}

The important part is the plane[][] matrix, whose edges get filled by random values, and then the values in between (the rest) are interpolated.

The values inside the plane[][] are modifying which “layer” to choose, when writing the Noise to a BufferedImage array.

It is supposed to act like a plane, which is (what is propably the problem) moved downwards through the noise… (My head hurts too much right now (yeah… implementing something like that is REALLY NOT EASY) for writing an implementation which is not based on values inside a 2D array, but is “vector based”.)

The strategy was to get a transformed plane (2D) in a volume (3D).

When you reduce the number of dimensions, you get: a transformed line (1D) on a plane (2D)

Notes: calculating polynomials directly is not efficient and is inaccurate. Simplest change is to use Horner’s method: 3*t*t - 2*t*t*t becomes: t2=t*t; t2*(3-2*t);

Also I think I saw a recursive power calculation with integers…iteration is you’re friend.

Yes. Already changed that, was a quick-implementation-issue.

Yeah… quick-implementation too… (OMFG I already thing recursive :open_mouth: shit…)
Actually, only because I first didn’t calculate 2^n for the possibilities, but n! (which is wrong…)… where ! was implemented recursive. And then I only changed it a bit, so it calculated integer-power.

But nice, you’ve read the code :open_mouth: :slight_smile:

I misunderstood this one.
With the cells are not aligned, I thought about the cells being rotated, and I didn’t knew how that could be possible… heh…

So now I understood your post (after reading the wiki page hehe… ;D ), and added offsets for each layer in the SimplexNoiseLayerN-combiner SimplexNoiseN… Looks so much better now :smiley:

http://dl.dropbox.com/u/45530199/Programs/SimplexNoiseN/image35.gif

Thank you :slight_smile:
EDIT: appreciate
EDIT2: How freaking cool does this look? :smiley:

http://dl.dropbox.com/u/45530199/Programs/SimplexNoiseN/image37.gif