// 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)); } }