abstract class Filter { abstract void invalidateInput(int i); abstract float valueAt(int i); } class GaussianBlurFilter extends Filter { int halfWidth; float[] kernel; float kernelSum; float[] input; float[] cache; GaussianBlurFilter(float[] input, int sigma) { this.input = input; cache = new float[input.length]; for (int i = 0; i < cache.length; i++) cache[i] = Float.NaN; halfWidth = (int)ceil(sigma); kernel = new float[halfWidth * 2 + 1]; float twoSigmaSquared = 2 * pow(sigma, 2); for (int i = -halfWidth; i < halfWidth; i++) { kernel[i + halfWidth] = 1.0 / (PI * twoSigmaSquared) * exp(-1.0 * pow(i, 2) / twoSigmaSquared); } kernelSum = 0; for (int i = 0; i < kernel.length; i++) { kernelSum += kernel[i]; } } void invalidateInput(int i) { for (int j = 0; j < kernel.length; j++) { int cacheIndex = i + j - halfWidth; if (cacheIndex < 0) cacheIndex += cache.length; if (cacheIndex >= cache.length) cacheIndex -= cache.length; cache[cacheIndex] = Float.NaN; } } float valueAt(int i) { if (Float.isNaN(cache[i])) { if (Float.isNaN(input[i])) return Float.NaN; float sum = 0; for (int j = 0; j < kernel.length; j++) { int inputJ = (i + j - halfWidth); if (inputJ < 0) inputJ += input.length; if (inputJ >= input.length) inputJ -= input.length; if (!Float.isNaN(input[inputJ])) { sum += kernel[j] * input[inputJ]; } } cache[i] = sum / kernelSum; } return cache[i]; } } class NothingFilter extends Filter { float[] input; NothingFilter(float[] input) { this.input = input; } void invalidateInput(int i) { } float valueAt(int i) { return input[i]; } } int step; int replacement; float previousYOffsetTarget; float nextYOffsetTarget; float[] yOffsets; ArrayList filters; int stepsPerTarget = 12; void setup() { size(320, 240); smooth(); frameRate(30); yOffsets = new float[width]; previousYOffsetTarget = 0; nextYOffsetTarget = 0; step = stepsPerTarget + 1; replacement = 0; filters = new ArrayList(); filters.add(new NothingFilter(yOffsets)); for (int i = 1; i < 6; i++) filters.add(new GaussianBlurFilter(yOffsets, i)); } void draw() { background(255); stroke(0); strokeWeight(2); for (int i = 0; i < filters.size(); i++) { Filter eachFilter = (Filter)filters.get(i); float baseline = height * (i + 1) / (filters.size() + 1); float previousY = 0; for (int j = 0; j < yOffsets.length; j++) { float eachOffset = eachFilter.valueAt(j); if (Float.isNaN(eachOffset)) { previousY = 0; } else { line(j, baseline + previousY, j + 1, baseline + eachOffset); previousY = eachOffset; } } } if (step > stepsPerTarget) { step = 1; previousYOffsetTarget = nextYOffsetTarget; nextYOffsetTarget = random(24) - 12; } else step++; float nextYOffset = previousYOffsetTarget + (nextYOffsetTarget - previousYOffsetTarget) * step / stepsPerTarget; yOffsets[replacement] = nextYOffset; for (int i = 0; i < filters.size(); i++) { Filter each = (Filter)filters.get(i); each.invalidateInput(replacement); } for (int i = 0; i < 8; i++) { int index = (replacement + i + 1) % yOffsets.length; yOffsets[index] = Float.NaN; for (int j = 0; j < filters.size(); j++) { Filter each = (Filter)filters.get(j); each.invalidateInput(index); } } replacement++; if (replacement >= yOffsets.length) replacement = 0; }