// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
import java.util.Random; // I can't stand arbitrary output
import java.util.TreeMap; // FloorEntry/CeilEntry is needed for sampling
// Image parameters: This is the stuff that's easy to change
final int W = 2400, H = 1350;
final int w = W, h = H;
final long seed = 0x09f911029D74E35BL;
final long seed2 = 0xD84156C5635688C0L;
final long seed3 = 0xDECAFF;
final int noiseCount = 512;
final int cloudFreq = 16;
final float baser = 32, basey = 96;
final float dist1 = .002, dist2 = .0002;
final int nth = 0;
// Other locals
PImage img = createImage(w, h, RGB);
// Set up our noise and run it.
void setup() {
size(W, H);
img.loadPixels();
Combiner earth = new Combiner((new Octave())
.addOctave(1.0, new Worley(seed, noiseCount, nth, dist1))
.addOctave(1./12, new Worley(seed + 1, noiseCount * 4, nth, dist2))
).addLevel(0, new ColorSampler(0xFF0000FF))
.addLevel(.6, new ColorSampler(0xFF00FFFF))
.addLevel(.8, new ColorSampler(0xFFFFFEB4))
.addLevel(1.4, new Combiner((new Octave())
.addOctave(1./1, new Perlin(seed2, noiseCount / 4).square().square().square())
.addOctave(1./2, new Perlin(seed2, noiseCount / 2))
.addOctave(1./4, new Perlin(seed2, noiseCount * 1))
.addOctave(1./8, new Perlin(seed2, noiseCount * 2))
.addOctave(1./16, new Perlin(seed2, noiseCount * 4))
).addLevel(0, new ColorSampler(0xFF006000))
.addLevel(.5, new ColorSampler(0xFF008000))
.addLevel(1, new ColorSampler(0xFF00C000))
);
Combiner clouds = new Combiner((new Octave())
.addOctave(2.0, new Perlin(seed3, cloudFreq * 1).square())
.addOctave(1./2, new Perlin(seed3, cloudFreq * 2).square())
.addOctave(1./4, new Perlin(seed3, cloudFreq * 4))
.addOctave(1./8, new Perlin(seed3, cloudFreq * 8))
).addLevel(1, earth)
.addLevel(2, new ColorSampler(0xFFFFFFFF))
.addLevel(3, new ColorSampler(0xFFC0C0C0));
Sampler pic = clouds;
int ind = 0;
for (int y = 0; y < h; ++y) {
float yf = y / (float)h;
for (int x = 0; x < w; ++x)
img.pixels[ind++] = pic.get(x / (float)w, yf);
}
img.updatePixels();
image(img, 0, 0, W, H);
save("cloudy-world.png");
}
// Utility functions, boilerplate boilerplate boilerplate
static double sqr(double x) { return x*x; }
static void arrins(int[] arr, int x, int ind) {
for (int i = arr.length - 1; i > ind; --i)
arr[i] = arr[i-1];
arr[ind] = x;
} // This function exists because Java generics don't like primitives.
// This will work in Java 7. OHWAITNOITWONT
static void arrins(double[] arr, double x, int ind) {
for (int i = arr.length - 1; i > ind; --i)
arr[i] = arr[i-1];
arr[ind] = x;
}
float clamp(float v, float min, float max) {
return v < min? min : v > max? max : v;
}
float sigmoid(float a) {
return 1 / (1 + exp(-4*a+2));
}
float slerp(float x, float y, float a) {
return lerp(x, y, sin(a * PI/2));
}
color mergeColor2(color c1, color c2, float a) {
int r1 = (c1 & 0xFF0000) >> 16, g1 = (c1 & 0xFF00) >> 8, b1 = (c1 & 0xFF);
int r2 = (c2 & 0xFF0000) >> 16, g2 = (c2 & 0xFF00) >> 8, b2 = (c2 & 0xFF);
if (a <= .5) {
color m = color(max(r1, (r1 + r2)/2), max(g1, (g1 + g2)/2), max(b1, (b1 + b2)/2));
return lerpColor(c1, m, 2 * a);
}
color m = color(max(r2, (r1 + r2)/2), max(g2, (g1 + g2)/2), max(b2, (b1 + b2)/2));
return lerpColor(m, c2, a * 2 - 1);
}
color mergeColor(color c1, color c2, float a) {
int r1 = (c1 & 0xFF0000) >> 16, g1 = (c1 & 0xFF00) >> 8, b1 = (c1 & 0xFF);
int r2 = (c2 & 0xFF0000) >> 16, g2 = (c2 & 0xFF00) >> 8, b2 = (c2 & 0xFF);
color m = color(max(r1, r2), max(g1, g2), max(b1, b2));
return (a < .5)? lerpColor(c1, m, 2 * a) : lerpColor(m, c2, a * 2 - 1);
}
color slerpColor(color c1, color c2, float a) {
return mergeColor(c1, c2, sigmoid(a));
}
// Interfaces, classes
static interface Noise {
public float get(float x, float y);
}
// This is where the actual logic starts.
// This is a class to sample one octave of worley noise.
static class Worley implements Noise {
// These are the variables needed by the actual image logic
double[][] points;
int k;
double max;
// This creates our points, scattering them randomly according to the seed.
public Worley(long seed, int npts, int k, double max) {
Random worlrand = new Random(seed);
points = new double[npts][2];
this.k = k;
this.max = max;
for (int i = 0; i < points.length; ++i) {
points[i][0] = worlrand.nextDouble();
points[i][1] = worlrand.nextDouble();
}
}
// This samples a point by computing the squared distance to the nearest k points,
// then returning the kth closest
public float get(float x, float y) {
double[] d2 = new double[k + 1];
int[] ptnum = new int[k + 1];
for (int i = 0; i <= k; ++i) {
d2[i] = Double.POSITIVE_INFINITY;
ptnum[i] = 0;
}
for (int i = 0; i < points.length; ++i) {
double d2i = sqr(x - points[i][0]) + sqr(y - points[i][1]);
for (int j = 0; j <= k; ++j)
if (d2i < d2[j]) {
arrins(d2, d2i, j);
arrins(ptnum, i, j);
break;
}
}
return (float)(d2[k] / max);
}
}
class Perlin implements Noise {
float[][] grid;
int size;
Perlin(long seed, int dim) {
Random r = new Random(seed);
grid = new float[dim][dim];
size = dim;
for (int i = 0; i < dim; ++i)
for (int j = 0; j < dim; ++j)
grid[i][j] = (float)r.nextDouble();
}
Perlin square() {
for (int i = 0; i < size; ++i)
for (int j = 0; j < size; ++j)
grid[i][j] *= grid[i][j];
return this;
}
float get(float x, float y) {
float i = clamp(y, 0, 1) * (size - 1);
float j = clamp(x, 0, 1) * (size - 1);
int in = (int)floor(i), ix = (int)ceil(i);
int jn = (int)floor(j), jx = (int)ceil(j);
return slerp(
slerp(grid[in][jn], grid[in][jx], (j - jn)),
slerp(grid[ix][jn], grid[ix][jx], (j - jn)),
(i - in)
);
}
}
class Octave implements Noise {
ArrayList octaves = new ArrayList();
ArrayList factors = new ArrayList();
public Octave addOctave(double fac, Noise n) {
octaves.add(n);
factors.add(fac);
return this;
}
float get(float x, float y) {
double accum = 0;
for (int i = 0; i < octaves.size(); ++i)
accum += factors.get(i) * octaves.get(i).get(x, y);
return (float)accum;
}
}
interface Interpolator {
color erp(color x, color y, float a);
}
class LinearInterpolator implements Interpolator {
color erp(color x, color y, float a) {
return lerpColor(x, y, a);
}
}
interface Sampler {
color get(float x, float y);
}
class ColorSampler implements Sampler {
color c;
public ColorSampler(color c) { this.c = c; }
public color get(float x, float y) { return c; }
}
class Combiner implements Sampler {
private class SampLerp {
public Sampler s;
public Interpolator i;
public SampLerp(Sampler ss, Interpolator si) { s = ss; i = si; }
}
TreeMap subsamplers = new TreeMap();
Noise sampleNoise;
public Combiner(Noise n) { sampleNoise = n; }
public Combiner addLevel(float threshold, Sampler s) { return addLevel(threshold, s, new LinearInterpolator()); }
public Combiner addLevel(float threshold, Sampler s, Interpolator i) { subsamplers.put(threshold, new SampLerp(s, i)); return this; }
public color get(float x, float y) {
float noise = sampleNoise.get(x, y);
Float less = subsamplers.floorKey(noise);
Float more = subsamplers.ceilingKey(noise);
if (less == null) less = more;
if (less == more || more == null)
return subsamplers.get(less).s.get(x, y);
SampLerp sl = subsamplers.get(less);
return sl.i.erp(sl.s.get(x, y), subsamplers.get(more).s.get(x, y), (noise - less)/(more - less));
}
}